package main // TODO: allow free-text lookups of place names, rather than ids import ( "encoding/json" "flag" "fmt" "io/ioutil" "log" "net/http" ) const metdefid = "310004" 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" const mpsToMphMultiplier = 2.23693629 // 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 } var TypeDescription = map[int]string{ 0: "Clear Sky", 1: "Sunny", 2: "Partly Cloudy", 3: "Sunny Intervals", 4: "Unknown", 5: "Mist", 6: "Fog", 7: "Light Cloud", 8: "Thick Cloud", 9: "Light Rain Showers", 10: "Light Rain Showers", 11: "Drizzle", 12: "Light Rain", 13: "Heavy Rain Showers", 14: "Heavy Rain Showers", 15: "Heavy Rain", 16: "Sleet Showers", 17: "Sleet Showers", 18: "Sleet", 19: "Hail Showers", 20: "Hail Showers", 21: "Hail", 22: "Light Snow Showers", 23: "Light Snow Showers", 24: "Light Snow", 25: "Heavy Snow Showers", 26: "Heavy Snow Showers", 27: "Heavy Snow", 28: "Thundery Showers", 29: "Thundery Showers", 30: "Thunder", } // Our prefered struct type Weather struct { date string time string temperature float64 precipitation int weathertype int windspeed float64 } var ( src = flag.String("s", "bbc", "data source provider (valid options: 'bbc', 'metoffice')") 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 * mpsToMphMultiplier 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 * mpsToMphMultiplier 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 * mpsToMphMultiplier weather = append(weather, w) } } return weather } func main() { var err error var id string var url string var parsefunc func([]byte) []Weather var resp *http.Response var weather []Weather flag.Parse() switch *src { case "bbc": id = bbcdefid url = bbcurl parsefunc = processBBC case "metoffice": id = metdefid url = meturl parsefunc = processMet default: log.Fatalf("data source %s not supported; use either 'bbc' or 'metoffice'\n", *src) } if flag.NArg() > 0 { id = flag.Arg(0) } resp, err = http.Get(fmt.Sprintf(url, 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) weather = parsefunc(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) } } }