diff options
Diffstat (limited to 'main.go')
-rw-r--r-- | main.go | 258 |
1 files changed, 258 insertions, 0 deletions
@@ -0,0 +1,258 @@ +// Copyright 2020 Nick White. +// Use of this source code is governed by the GPLv3 +// license that can be found in the LICENSE file. +package main + +// Note that this was the first Go I wrote. Look elsewhere for +// examples of best practise. + +// BUG: need to allow choice of met-office source (-b 0 doesn't work how i thought it would) +// TODO: allow free-text lookups of place names, rather than ids +// TODO: convert metoffice windspeed to mph +// TODO: split output into days +// TODO: add -n flag to only output a certain number of days +// TODO: human friendly dates + +import ( + "encoding/json" + "flag" + "fmt" + "io/ioutil" + "log" + "net/http" +) + +const metdefid = "310118" +const bbcdefid = "2654675" + +const meturl = "https://www.metoffice.gov.uk/public/data/PWSCache/BestForecast/Forecast/%s.json?concise=true" +const bbcurl = "https://weather-broker-cdn.api.bbci.co.uk/en/forecast/aggregated/%s" + +// BBC structures +type BBCResponse struct { + Forecasts []struct { + Detailed struct { + Reports []Report + } + } +} + +type Report struct { + EnhancedWeatherDescription string + ExtendedWeatherType int + FeelsLikeTemperatureC int + FeelsLikeTemperatureF int + GustSpeedKph int + GustSpeedMph int + Humidity int + LocalDate string + PrecipitationProbabilityInPercent int + PrecipitationProbabilityText string + Pressure int + TemperatureC int + TemperatureF int + Timeslot string + TimeslotLength int + Visibility string + WeatherType int + WeatherTypeText string + WindDescription string + WindDirection string + WindDirectionAbbreviation string + WindDirectionFull string + WindSpeedKph int + WindSpeedMph int +} + +// Met Office structures +type MetResponse struct { + BestFcst struct { + Forecast struct { + Location struct { + Days []Day `json:"Day"` + } + } + } +} + +type Day struct { + Date string `json:"@date"` + DayValues struct { + WeatherParameters WeatherParams + } + NightValues struct { + WeatherParameters WeatherParams + } + TimeSteps struct { + TimeStep []struct { + Time string `json:"@time"` + WeatherParameters WeatherParams + } + } +} + +type WeatherParams struct { + AQIndex int // Air Quality + F float64 // Feels Like Temperature + H int // Humidity + P int // Pressure + PP int // Precipitation Probability + T float64 // Temperature + UV int // Max UV Index + V int // Visibility + WD string // Wind Direction + WG float64 // Wind Gust + WS float64 // Wind Speed + WT int // Weather Type +} + +// TODO: complete this mapping +var TypeDescription = map[int]string{ + 0: "Clear Sky", + 1: "Sunny", + 2: "Partly Cloudy", + 3: "Sunny Intervals", + 5: "Mist", + 7: "Light Cloud", + 8: "Thick Cloud", + 9: "Light Rain Showers", + 10: "Light Rain Showers", + 11: "Drizzle", + 12: "Light Rain", + 14: "Heavy Rain Showers", + 15: "Heavy Rain", + 18: "Sleet", + 19: "Hail Showers", + 20: "Hail Showers", + 28: "Thundery Showers", + 29: "Thundery Showers", +} + +// Our prefered struct +type Weather struct { + date string + time string + temperature float64 + precipitation int + weathertype int + windspeed float64 +} + +var ( + bbc = flag.Bool("b", true, "use bbc as data source") + numdays = flag.Int("n", 2, "number of days to show") + verbose = flag.Bool("v", false, "verbose: show all weather details") +) + +func processBBC(b []byte) []Weather { + var r BBCResponse + var weather []Weather + var w Weather + err := json.Unmarshal(b, &r) + if err != nil { + log.Fatal(err) + } + + for _, f := range r.Forecasts { + for _, report := range f.Detailed.Reports { + w.date = report.LocalDate + w.time = report.Timeslot + w.temperature = float64(report.TemperatureC) + w.precipitation = report.PrecipitationProbabilityInPercent + w.weathertype = report.WeatherType + w.windspeed = float64(report.WindSpeedMph) + weather = append(weather, w) + } + } + return weather +} + +func processMet(b []byte) []Weather { + var r MetResponse + var weather []Weather + var w Weather + err := json.Unmarshal(b, &r) + if err != nil { + log.Fatal(err) + } + + for _, d := range r.BestFcst.Forecast.Location.Days { + w.date = d.Date + w.time = "Day " + w.temperature = d.DayValues.WeatherParameters.T + w.precipitation = d.DayValues.WeatherParameters.PP + w.weathertype = d.DayValues.WeatherParameters.WT + w.windspeed = d.DayValues.WeatherParameters.WS + weather = append(weather, w) + + w.date = d.Date + w.time = "Night " + w.temperature = d.DayValues.WeatherParameters.T + w.precipitation = d.DayValues.WeatherParameters.PP + w.weathertype = d.DayValues.WeatherParameters.WT + w.windspeed = d.DayValues.WeatherParameters.WS + weather = append(weather, w) + + for _, t := range d.TimeSteps.TimeStep { + w.date = d.Date + w.time = t.Time + w.temperature = t.WeatherParameters.T + w.precipitation = t.WeatherParameters.PP + w.weathertype = t.WeatherParameters.WT + w.windspeed = t.WeatherParameters.WS + weather = append(weather, w) + } + } + return weather +} + +func main() { + var err error + var id string + var resp *http.Response + var weather []Weather + + flag.Parse() + if flag.NArg() > 0 { + id = flag.Arg(0) + } else { + if *bbc { + id = bbcdefid + } else { + id = metdefid + } + } + + if *bbc { + resp, err = http.Get(fmt.Sprintf(bbcurl, id)) + } else { + resp, err = http.Get(fmt.Sprintf(meturl, id)) + } + if err != nil { + log.Fatal(err) + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + log.Fatalf("HTTP status code: %d\n", resp.StatusCode) + } + b, err := ioutil.ReadAll(resp.Body) + + if *bbc { + weather = processBBC(b) + } else { + weather = processMet(b) + } + + for _, w := range weather { + fmt.Printf("%s %s ", w.date, w.time) + desc, ok := TypeDescription[w.weathertype] + if !ok { + desc = fmt.Sprintf("%d", w.weathertype) + } + fmt.Printf("%18s, Temp: %4.1f°C, ", desc, w.temperature) + fmt.Printf("Rain: %2d%%, Wind: %4.1fmph\n", w.precipitation, w.windspeed) + if *verbose { + fmt.Printf(" %+v\n", w) + } + } +} |