summaryrefslogtreecommitdiff
path: root/main.go
diff options
context:
space:
mode:
Diffstat (limited to 'main.go')
-rw-r--r--main.go258
1 files changed, 258 insertions, 0 deletions
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)
+ }
+ }
+}