From 8fd3312b55623e52a21751391885aec82e5f666f Mon Sep 17 00:00:00 2001 From: Markus Teich Date: Tue, 21 Mar 2017 03:38:02 +0100 Subject: [PATCH] fixup openweathermap.org backend --- backends/openweather.go | 197 ------------------------- backends/openweathermap.org.go | 260 +++++++++++++++++++++++++++++++++ 2 files changed, 260 insertions(+), 197 deletions(-) delete mode 100644 backends/openweather.go create mode 100644 backends/openweathermap.org.go diff --git a/backends/openweather.go b/backends/openweather.go deleted file mode 100644 index 264707c..0000000 --- a/backends/openweather.go +++ /dev/null @@ -1,197 +0,0 @@ -package backends - -import ( - "encoding/json" - "flag" - "fmt" - "github.com/schachmat/wego/iface" - "io/ioutil" - "log" - "net/http" - "strings" - "time" -) - -type openWeatherConfig struct { - apiKey string - lang string - debug bool -} - -type openWeatherResponse struct { - Cod string `json:"cod"` - Message float64 `json:"message"` - Cnt int `json:"cnt"` - City cityResponseBlock `json:"city"` - List []listBlock `json:"list"` -} - -type listBlock struct { - Dt int64 `json:"dt"` - Dt_txt string `json:"dt_txt"` - //Main section - Main struct { - Temp float32 `json:"temp"` - Temp_min float32 `json:"temp_min"` - Temp_max float32 `json:"temp_max"` - Pressure float32 `json:"pressure"` - Sea_level float32 `json:"sea_level"` - Grnd_level float32 `json:"grnd_level"` - Humidity int `json:"humidity"` - Temp_kf float32 `json:"temp_kf"` - } `json:"main"` - - Clouds struct { - All int `json:"all"` - } `json:"clouds"` - - Weather []struct { - Description string `json:"description"` - Icon string `json:"icon"` - ID int `json:"id"` - Main string `json:"main"` - } `json:"weather"` - - Wind struct { - Speed float32 `json:"speed"` - Deg float32 `json:"deg"` - } `json:"wind"` - - Rain struct { - MM3h float32 `json:"3h"` - } `json:"rain"` -} - -type cityResponseBlock struct { - Id int `json:"id"` - Name string `json:"name"` - Country string `json:"country"` -} - -const ( - openweatherUri = "http://api.openweathermap.org/data/2.5/forecast?lat=%s&lon=%s&appid=%s&units=metric&lang=%s" -) - -func (ow *openWeatherConfig) Setup() { - flag.StringVar(&ow.apiKey, "openweather-api-key", "", "openweather backend: the api `KEY` to use") - flag.StringVar(&ow.lang, "openweather-lang", "en", "openweather backend: the `LANGUAGE` to request to openweather") - flag.BoolVar(&ow.debug, "openweather-debug", false, "openweather backend: print raw requests and responses") -} - -func (ow *openWeatherConfig) fetch(url string) (*openWeatherResponse, error) { - res, err := http.Get(url) - if ow.debug { - fmt.Printf("Fetching for %s \n", url) - } - if err != nil { - return nil, fmt.Errorf(" Unable to get (%s) %v", url, err) - } - defer res.Body.Close() - body, err := ioutil.ReadAll(res.Body) - if err != nil { - return nil, fmt.Errorf("Unable to read response body (%s): %v", url, err) - } - - if ow.debug { - fmt.Printf("Response (%s) %s", url, string(body)) - } - - var resp openWeatherResponse - if err = json.Unmarshal(body, &resp); err != nil { - return nil, fmt.Errorf("Unable to unmarshal response (%s): %v\nThe json body is: %s", url, err, string(body)) - } - return &resp, nil -} - -func (ow *openWeatherConfig) parseDaily(dataInfo []listBlock, numdays int) []iface.Day { - var openWeather []iface.Day - var day *iface.Day - - for _, data := range dataInfo { - slot, err := ow.parseCond(data) - if err != nil { - log.Println("Error parsing hourly weather condition:", err) - continue - } - if day == nil { - day = new(iface.Day) - day.Date = slot.Time - } - if day.Date.Day() == slot.Time.Day() { - day.Slots = append(day.Slots, slot) - } - if day.Date.Day() != slot.Time.Day() { - openWeather = append(openWeather, *day) - if len(openWeather) >= numdays { - break - } - day = new(iface.Day) - day.Date = slot.Time - day.Slots = append(day.Slots, slot) - } - - } - return openWeather -} - -func (ow *openWeatherConfig) parseCond(dataInfo listBlock) (iface.Cond, error) { - var ret iface.Cond - codemap := map[int]iface.WeatherCode{ - 500: iface.CodeLightRain, - 501: iface.CodeHeavyRain, - 502: iface.CodeHeavyRain, - 503: iface.CodeHeavyRain, - 800: iface.CodeSunny, - 802: iface.CodePartlyCloudy, - 803: iface.CodePartlyCloudy, - 804: iface.CodePartlyCloudy, - } - ret.Code = iface.CodeUnknown - ret.Desc = dataInfo.Weather[0].Main - ret.Humidity = &(dataInfo.Main.Humidity) - ret.TempC = &(dataInfo.Main.Temp) - if &dataInfo.Wind.Deg != nil { - p := int(dataInfo.Wind.Deg) - ret.WinddirDegree = &p - } - if &(dataInfo.Wind.Speed) != nil && (dataInfo.Wind.Speed) > 0 { - windSpeed := (dataInfo.Wind.Speed * 3.6) - ret.WindspeedKmph = &(windSpeed) - } - if val, ok := codemap[dataInfo.Weather[0].ID]; ok { - ret.Code = val - } - - if &dataInfo.Rain.MM3h != nil { - mmh := (dataInfo.Rain.MM3h / 1000) / 3 - ret.PrecipM = &mmh - } - - ret.Time = time.Unix(dataInfo.Dt, 0) - - return ret, nil -} - -func (ow *openWeatherConfig) Fetch(location string, numdays int) iface.Data { - var ret iface.Data - s := strings.Split(location, ",") - - lat, lon := s[0], s[1] - - resp, err := ow.fetch(fmt.Sprintf(openweatherUri, lat, lon, ow.apiKey, ow.lang)) - if err != nil { - log.Fatalf("Failed to fetch weather data: %v\n", err) - } - ret.Current, err = ow.parseCond(resp.List[0]) - ret.Location = fmt.Sprintf("%s", resp.City.Name) - - if err != nil { - log.Fatalf("Failed to fetch weather data: %v\n", err) - } - ret.Forecast = ow.parseDaily(resp.List, numdays) - return ret -} - -func init() { - iface.AllBackends["openweather"] = &openWeatherConfig{} -} diff --git a/backends/openweathermap.org.go b/backends/openweathermap.org.go new file mode 100644 index 0000000..db06502 --- /dev/null +++ b/backends/openweathermap.org.go @@ -0,0 +1,260 @@ +package backends + +import ( + "encoding/json" + "flag" + "fmt" + "github.com/schachmat/wego/iface" + "io/ioutil" + "log" + "net/http" + "regexp" + "strings" + "time" +) + +type openWeatherConfig struct { + apiKey string + lang string + debug bool +} + +type openWeatherResponse struct { + Cod string `json:"cod"` + City struct { + Name string `json:"name"` + Country string `json:"country"` + } `json:"city"` + List []dataBlock `json:"list"` +} + +type dataBlock struct { + Dt int64 `json:"dt"` + Main struct { + TempMin float32 `json:"temp_min"` + TempMax float32 `json:"temp_max"` + Humidity int `json:"humidity"` + } `json:"main"` + + Weather []struct { + Description string `json:"description"` + ID int `json:"id"` + } `json:"weather"` + + Wind struct { + Speed float32 `json:"speed"` + Deg float32 `json:"deg"` + } `json:"wind"` + + Rain struct { + MM3h float32 `json:"3h"` + } `json:"rain"` +} + +const ( + openweatherURI = "http://api.openweathermap.org/data/2.5/forecast?%s&appid=%s&units=metric&lang=%s" +) + +func (c *openWeatherConfig) Setup() { + flag.StringVar(&c.apiKey, "owm-api-key", "", "openweathermap backend: the api `KEY` to use") + flag.StringVar(&c.lang, "owm-lang", "en", "openweathermap backend: the `LANGUAGE` to request from openweathermap") + flag.BoolVar(&c.debug, "owm-debug", false, "openweathermap backend: print raw requests and responses") +} + +func (c *openWeatherConfig) fetch(url string) (*openWeatherResponse, error) { + res, err := http.Get(url) + if c.debug { + fmt.Printf("Fetching %s\n", url) + } + if err != nil { + return nil, fmt.Errorf(" Unable to get (%s) %v", url, err) + } + defer res.Body.Close() + body, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, fmt.Errorf("Unable to read response body (%s): %v", url, err) + } + + if c.debug { + fmt.Printf("Response (%s):\n%s\n", url, string(body)) + } + + var resp openWeatherResponse + if err = json.Unmarshal(body, &resp); err != nil { + return nil, fmt.Errorf("Unable to unmarshal response (%s): %v\nThe json body is: %s", url, err, string(body)) + } + if resp.Cod != "200" { + return nil, fmt.Errorf("Erroneous response body: %s", string(body)) + } + return &resp, nil +} + +func (c *openWeatherConfig) parseDaily(dataInfo []dataBlock, numdays int) []iface.Day { + var forecast []iface.Day + var day *iface.Day + + for _, data := range dataInfo { + slot, err := c.parseCond(data) + if err != nil { + log.Println("Error parsing hourly weather condition:", err) + continue + } + if day == nil { + day = new(iface.Day) + day.Date = slot.Time + } + if day.Date.Day() == slot.Time.Day() { + day.Slots = append(day.Slots, slot) + } + if day.Date.Day() != slot.Time.Day() { + forecast = append(forecast, *day) + if len(forecast) >= numdays { + break + } + day = new(iface.Day) + day.Date = slot.Time + day.Slots = append(day.Slots, slot) + } + + } + return forecast +} + +func (c *openWeatherConfig) parseCond(dataInfo dataBlock) (iface.Cond, error) { + var ret iface.Cond + codemap := map[int]iface.WeatherCode{ + 200: iface.CodeThunderyShowers, + 201: iface.CodeThunderyShowers, + 210: iface.CodeThunderyShowers, + 230: iface.CodeThunderyShowers, + 231: iface.CodeThunderyShowers, + 202: iface.CodeThunderyHeavyRain, + 211: iface.CodeThunderyHeavyRain, + 212: iface.CodeThunderyHeavyRain, + 221: iface.CodeThunderyHeavyRain, + 232: iface.CodeThunderyHeavyRain, + 300: iface.CodeLightRain, + 301: iface.CodeLightRain, + 310: iface.CodeLightRain, + 311: iface.CodeLightRain, + 313: iface.CodeLightRain, + 321: iface.CodeLightRain, + 302: iface.CodeHeavyRain, + 312: iface.CodeHeavyRain, + 314: iface.CodeHeavyRain, + 500: iface.CodeLightShowers, + 501: iface.CodeLightShowers, + 502: iface.CodeHeavyShowers, + 503: iface.CodeHeavyShowers, + 504: iface.CodeHeavyShowers, + 511: iface.CodeLightSleet, + 520: iface.CodeLightShowers, + 521: iface.CodeLightShowers, + 522: iface.CodeHeavyShowers, + 531: iface.CodeHeavyShowers, + 600: iface.CodeLightSnow, + 601: iface.CodeLightSnow, + 602: iface.CodeHeavySnow, + 611: iface.CodeLightSleet, + 612: iface.CodeLightSleetShowers, + 615: iface.CodeLightSleet, + 616: iface.CodeLightSleet, + 620: iface.CodeLightSnowShowers, + 621: iface.CodeLightSnowShowers, + 622: iface.CodeHeavySnowShowers, + 701: iface.CodeFog, + 711: iface.CodeFog, + 721: iface.CodeFog, + 741: iface.CodeFog, + 731: iface.CodeUnknown, // sand, dust whirls + 751: iface.CodeUnknown, // sand + 761: iface.CodeUnknown, // dust + 762: iface.CodeUnknown, // volcanic ash + 771: iface.CodeUnknown, // squalls + 781: iface.CodeUnknown, // tornado + 800: iface.CodeSunny, + 801: iface.CodePartlyCloudy, + 802: iface.CodeCloudy, + 803: iface.CodeVeryCloudy, + 804: iface.CodeVeryCloudy, + 900: iface.CodeUnknown, // tornado + 901: iface.CodeUnknown, // tropical storm + 902: iface.CodeUnknown, // hurricane + 903: iface.CodeUnknown, // cold + 904: iface.CodeUnknown, // hot + 905: iface.CodeUnknown, // windy + 906: iface.CodeUnknown, // hail + 951: iface.CodeUnknown, // calm + 952: iface.CodeUnknown, // light breeze + 953: iface.CodeUnknown, // gentle breeze + 954: iface.CodeUnknown, // moderate breeze + 955: iface.CodeUnknown, // fresh breeze + 956: iface.CodeUnknown, // strong breeze + 957: iface.CodeUnknown, // high wind, near gale + 958: iface.CodeUnknown, // gale + 959: iface.CodeUnknown, // severe gale + 960: iface.CodeUnknown, // storm + 961: iface.CodeUnknown, // violent storm + 962: iface.CodeUnknown, // hurricane + } + + ret.Code = iface.CodeUnknown + ret.Desc = dataInfo.Weather[0].Description + ret.Humidity = &(dataInfo.Main.Humidity) + ret.TempC = &(dataInfo.Main.TempMin) + ret.FeelsLikeC = &(dataInfo.Main.TempMax) + if &dataInfo.Wind.Deg != nil { + p := int(dataInfo.Wind.Deg) + ret.WinddirDegree = &p + } + if &(dataInfo.Wind.Speed) != nil && (dataInfo.Wind.Speed) > 0 { + windSpeed := (dataInfo.Wind.Speed * 3.6) + ret.WindspeedKmph = &(windSpeed) + } + if val, ok := codemap[dataInfo.Weather[0].ID]; ok { + ret.Code = val + } + + if &dataInfo.Rain.MM3h != nil { + mmh := (dataInfo.Rain.MM3h / 1000) / 3 + ret.PrecipM = &mmh + } + + ret.Time = time.Unix(dataInfo.Dt, 0) + + return ret, nil +} + +func (c *openWeatherConfig) Fetch(location string, numdays int) iface.Data { + var ret iface.Data + loc := "" + + if len(c.apiKey) == 0 { + log.Fatal("No openweathermap.org API key specified.\nYou have to register for one at https://home.openweathermap.org/users/sign_up") + } + if matched, err := regexp.MatchString(`^-?[0-9]*(\.[0-9]+)?,-?[0-9]*(\.[0-9]+)?$`, location); matched && err == nil { + s := strings.Split(location, ",") + loc = fmt.Sprintf("lat=%s&lon=%s", s[0], s[1]) + } else if matched, err = regexp.MatchString(`^[0-9].*`, location); matched && err == nil { + loc = "zip=" + location + } else { + loc = "q=" + location + } + + resp, err := c.fetch(fmt.Sprintf(openweatherURI, loc, c.apiKey, c.lang)) + if err != nil { + log.Fatalf("Failed to fetch weather data: %v\n", err) + } + ret.Current, err = c.parseCond(resp.List[0]) + ret.Location = fmt.Sprintf("%s, %s", resp.City.Name, resp.City.Country) + + if err != nil { + log.Fatalf("Failed to fetch weather data: %v\n", err) + } + ret.Forecast = c.parseDaily(resp.List, numdays) + return ret +} + +func init() { + iface.AllBackends["openweathermap"] = &openWeatherConfig{} +}