package main // 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", 15: "Heavy Rain", 18: "Sleet", 19: "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) } } }