From 3960e17c2222366fb47366f5b5d46d768e2842c0 Mon Sep 17 00:00:00 2001 From: Nick White Date: Sat, 14 Nov 2020 15:20:48 +0000 Subject: Add module support, license, readme --- main.go | 258 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 258 insertions(+) create mode 100644 main.go (limited to 'main.go') diff --git a/main.go b/main.go new file mode 100644 index 0000000..9e9fab6 --- /dev/null +++ b/main.go @@ -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) + } + } +} -- cgit v1.2.3 From 02720f60f28cc422f219b7904c201a2700891959 Mon Sep 17 00:00:00 2001 From: Nick White Date: Mon, 28 Dec 2020 12:21:24 +0000 Subject: Add some sleet weather types --- main.go | 2 ++ 1 file changed, 2 insertions(+) (limited to 'main.go') diff --git a/main.go b/main.go index 9e9fab6..b93f141 100644 --- a/main.go +++ b/main.go @@ -121,6 +121,8 @@ var TypeDescription = map[int]string{ 12: "Light Rain", 14: "Heavy Rain Showers", 15: "Heavy Rain", + 16: "Sleet Showers", + 17: "Sleet Showers", 18: "Sleet", 19: "Hail Showers", 20: "Hail Showers", -- cgit v1.2.3 From e9397149ede17f7537296c52445bc7856f8cc987 Mon Sep 17 00:00:00 2001 From: Nick White Date: Sat, 2 Jan 2021 09:49:58 +0000 Subject: Add extra weather types --- main.go | 3 +++ 1 file changed, 3 insertions(+) (limited to 'main.go') diff --git a/main.go b/main.go index b93f141..29af82e 100644 --- a/main.go +++ b/main.go @@ -126,6 +126,9 @@ var TypeDescription = map[int]string{ 18: "Sleet", 19: "Hail Showers", 20: "Hail Showers", + 22: "Light Snow Showers", + 23: "Light Snow Showers", + 26: "Heavy Snow Showers", 28: "Thundery Showers", 29: "Thundery Showers", } -- cgit v1.2.3