modules/weather/openweathermap/openweathermap.go
// Copyright 2020 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/*
Package openweathermap provides weather using the OpenWeatherMap API,
available at https://openweathermap.org/api.
*/
package openweathermap
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"time"
"github.com/soumya92/barista/modules/weather"
"github.com/martinlindhe/unit"
)
// Config represents open weather map API configuration (just the API key)
// from which a weather.Provider can be built.
type Config string
// New creates a new OpenWeatherMap API configuration.
func New(apiKey string) Config {
return Config(apiKey)
}
// CityID queries OWM by city id. Recommended.
func (c Config) CityID(cityID string) weather.Provider {
return c.build([][2]string{
{"id", cityID},
})
}
// CityName queries OWM using a named city. Least accurate.
func (c Config) CityName(city, country string) weather.Provider {
return c.build([][2]string{
{"q", fmt.Sprintf("%s,%s", city, country)},
})
}
// Coords queries OWM using lat/lon co-ordinates.
func (c Config) Coords(lat, lon float64) weather.Provider {
return c.build([][2]string{
{"lat", fmt.Sprintf("%.6f", lat)},
{"lon", fmt.Sprintf("%.6f", lon)},
})
}
// Zipcode queries OWM using a zip code or post code and country.
func (c Config) Zipcode(zip, country string) weather.Provider {
return c.build([][2]string{
{"zip", fmt.Sprintf("%s,%s", zip, country)},
})
}
// Provider wraps an open weather map API url so that
// it can be used as a weather.Provider.
type Provider string
// Build builds a weather provider from the configuration.
func (c Config) build(query [][2]string) weather.Provider {
// Build the OWM URL.
qp := url.Values{}
qp.Add("appid", string(c))
for _, value := range query {
qp.Add(value[0], value[1])
}
owmURL := url.URL{
Scheme: "https",
Host: "api.openweathermap.org",
Path: "/data/2.5/weather",
RawQuery: qp.Encode(),
}
return Provider(owmURL.String())
}
// owmWeather represents an openweathermap json response.
type owmWeather struct {
Weather []struct {
ID int
Description string
}
Main struct {
Temp float64
Pressure float64
Humidity float64
}
Wind struct {
Speed float64
Deg float64
}
Clouds struct {
All float64
}
Sys struct {
Sunrise int64
Sunset int64
}
Name string
Dt int64
}
func getCondition(owmCondition int) weather.Condition {
switch owmCondition {
case 611, 612:
return weather.Sleet
case 701:
return weather.Mist
case 711, 751, 761, 762:
return weather.Smoke
case 721:
return weather.Haze
case 731:
return weather.Whirls
case 741:
return weather.Fog
case 800:
return weather.Clear
case 801, 802:
return weather.PartlyCloudy
case 803:
return weather.Cloudy
case 804:
return weather.Overcast
case 900, 781:
return weather.Tornado
case 901:
return weather.TropicalStorm
case 902:
return weather.Hurricane
case 903:
return weather.Cold
case 904:
return weather.Hot
case 905, 771:
return weather.Windy
case 906:
return weather.Hail
}
if owmCondition >= 200 && owmCondition < 300 {
return weather.Thunderstorm
} else if owmCondition >= 300 && owmCondition < 500 {
return weather.Drizzle
} else if owmCondition >= 500 && owmCondition < 600 {
return weather.Rain
} else if owmCondition >= 600 && owmCondition < 700 {
return weather.Snow
}
return weather.ConditionUnknown
}
// GetWeather gets weather information from OpenWeatherMap.
func (owm Provider) GetWeather() (weather.Weather, error) {
response, err := http.Get(string(owm))
if err != nil {
return weather.Weather{}, err
}
defer response.Body.Close()
o := owmWeather{}
err = json.NewDecoder(response.Body).Decode(&o)
if err != nil {
return weather.Weather{}, err
}
if len(o.Weather) < 1 {
return weather.Weather{}, fmt.Errorf("Bad response from OWM")
}
return weather.Weather{
Location: o.Name,
Condition: getCondition(o.Weather[0].ID),
Description: o.Weather[0].Description,
Temperature: unit.FromKelvin(o.Main.Temp),
Humidity: float64(o.Main.Humidity) / 100.0,
Pressure: unit.Pressure(o.Main.Pressure) * unit.Millibar,
CloudCover: float64(o.Clouds.All) / 100.0,
Sunrise: time.Unix(o.Sys.Sunrise, 0),
Sunset: time.Unix(o.Sys.Sunset, 0),
Updated: time.Unix(o.Dt, 0),
Wind: weather.Wind{
Speed: unit.Speed(o.Wind.Speed) * unit.MetersPerSecond,
Direction: weather.Direction(int(o.Wind.Deg)),
},
Attribution: "OpenWeatherMap",
}, nil
}