-
Notifications
You must be signed in to change notification settings - Fork 479
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Pedro Pereira
committed
Sep 8, 2024
1 parent
3eb1d00
commit 7996aec
Showing
1 changed file
with
217 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,217 @@ | ||
package backends | ||
|
||
import ( | ||
"encoding/json" | ||
"flag" | ||
"fmt" | ||
"github.com/schachmat/wego/iface" | ||
"io" | ||
"log" | ||
"net/http" | ||
"regexp" | ||
"strings" | ||
"time" | ||
) | ||
|
||
type openmeteoConfig struct { | ||
apiKey string | ||
language string | ||
debug bool | ||
} | ||
|
||
type curCond struct { | ||
Time int64 `json:"time"` | ||
Interval int `json:"interval"` | ||
Temperature2M *float32 `json:"temperature_2m"` | ||
ApparentTemperature *float32 `json:"apparent_temperature"` | ||
IsDay int `json:"is_day"` | ||
WeatherCode int `json:"weather_code"` | ||
} | ||
|
||
type Daily struct { | ||
Time []int64 `json:"time"` | ||
WeatherCode []int `json:"weather_code"` | ||
Temperature2MMax []*float32 `json:"temperature_2m_max"` | ||
ApparentTemperatureMax []*float32 `json:"apparent_temperature_max"` | ||
Sunrise []int64 `json:"sunrise"` | ||
Sunset []int64 `json:"sunset"` | ||
} | ||
type HourlyUnits struct { | ||
Time string `json:"time"` | ||
Temperature2M string `json:"temperature_2m"` | ||
ApparentTemperature string `json:"apparent_temperature"` | ||
WeatherCode string `json:"weather_code"` | ||
} | ||
type Hourly struct { | ||
Time []int64 `json:"time"` | ||
Temperature2M []*float32 `json:"temperature_2m"` | ||
ApparentTemperature []*float32 `json:"apparent_temperature"` | ||
WeatherCode []int `json:"weather_code"` | ||
} | ||
|
||
type openmeteoResponse struct { | ||
Latitude float64 `json:"latitude"` | ||
Longitude float64 `json:"longitude"` | ||
GenerationtimeMs float64 `json:"generationtime_ms"` | ||
UtcOffsetSeconds int `json:"utc_offset_seconds"` | ||
Timezone string `json:"timezone"` | ||
TimezoneAbbreviation string `json:"timezone_abbreviation"` | ||
Elevation float64 `json:"elevation"` | ||
CurrentUnits struct { | ||
Time string `json:"time"` | ||
Interval string `json:"interval"` | ||
Temperature2M string `json:"temperature_2m"` | ||
ApparentTemperature string `json:"apparent_temperature"` | ||
IsDay string `json:"is_day"` | ||
WeatherCode string `json:"weather_code"` | ||
} `json:"current_units"` | ||
Current curCond `json:"current"` | ||
HourlyUnits HourlyUnits `json:"hourly_units"` | ||
Hourly Hourly `json:"hourly"` | ||
DailyUnits struct { | ||
Time string `json:"time"` | ||
WeatherCode string `json:"weather_code"` | ||
Temperature2MMax string `json:"temperature_2m_max"` | ||
ApparentTemperatureMax string `json:"apparent_temperature_max"` | ||
Sunrise string `json:"sunrise"` | ||
Sunset string `json:"sunset"` | ||
} `json:"daily_units"` | ||
Daily Daily | ||
} | ||
|
||
const ( | ||
openmeteoURI = "https://api.open-meteo.com/v1/forecast?" | ||
) | ||
|
||
var ( | ||
codemap = map[int]iface.WeatherCode{ | ||
0: iface.CodeSunny, | ||
1: iface.CodePartlyCloudy, | ||
2: iface.CodePartlyCloudy, | ||
3: iface.CodePartlyCloudy, | ||
45: iface.CodeFog, | ||
48: iface.CodeFog, | ||
51: iface.CodeLightRain, | ||
53: iface.CodeLightRain, | ||
55: iface.CodeLightRain, | ||
56: iface.CodeLightSleet, | ||
57: iface.CodeLightSleet, | ||
61: iface.CodeLightShowers, | ||
63: iface.CodeLightShowers, | ||
65: iface.CodeLightShowers, | ||
66: iface.CodeHeavyRain, | ||
67: iface.CodeHeavyRain, | ||
} | ||
) | ||
|
||
func (opmeteo *openmeteoConfig) Setup() { | ||
flag.StringVar(&opmeteo.apiKey, "openmeteo-api-key", "", "openmeteo backend: the api `KEY` to use if commercial usage") | ||
flag.BoolVar(&opmeteo.debug, "openmeteo-debug", false, "openmeteo backend: print raw requests and responses") | ||
} | ||
|
||
func (opmeteo *openmeteoConfig) parseDaily(dailyInfo Hourly) []iface.Day { | ||
var forecast []iface.Day | ||
var day *iface.Day | ||
|
||
for ind, dayTime := range dailyInfo.Time { | ||
|
||
//day := new(iface.Day) | ||
cond := new(iface.Cond) | ||
|
||
cond.Code = codemap[dailyInfo.WeatherCode[ind]] | ||
cond.TempC = dailyInfo.Temperature2M[ind] | ||
cond.FeelsLikeC = dailyInfo.ApparentTemperature[ind] | ||
cond.Time = time.Unix(dayTime, 0) | ||
|
||
if day == nil { | ||
day = new(iface.Day) | ||
day.Date = cond.Time | ||
} | ||
if day.Date.Day() == cond.Time.Day() { | ||
day.Slots = append(day.Slots, *cond) | ||
} | ||
if day.Date.Day() != cond.Time.Day() { | ||
forecast = append(forecast, *day) | ||
|
||
day = new(iface.Day) | ||
day.Date = cond.Time | ||
day.Slots = append(day.Slots, *cond) | ||
} | ||
} | ||
|
||
return forecast | ||
} | ||
|
||
func parseCurCond(current curCond) (ret iface.Cond) { | ||
|
||
ret.Time = time.Unix(current.Time, 0) | ||
|
||
ret.Code = iface.CodeUnknown | ||
if val, ok := codemap[current.WeatherCode]; ok { | ||
ret.Code = val | ||
} | ||
|
||
ret.TempC = current.Temperature2M | ||
ret.FeelsLikeC = current.ApparentTemperature | ||
return ret | ||
|
||
} | ||
|
||
func (opmeteo *openmeteoConfig) Fetch(location string, numdays int) iface.Data { | ||
var ret iface.Data | ||
var params []string | ||
var loc string | ||
|
||
if matched, err := regexp.MatchString(`^-?[0-9]*(\.[0-9]+)?,-?[0-9]*(\.[0-9]+)?$`, location); matched && err == nil { | ||
s := strings.Split(location, ",") | ||
loc = fmt.Sprintf("latitude=%s&longitude=%s", s[0], s[1]) | ||
} | ||
if len(location) > 0 { | ||
params = append(params, loc) | ||
} | ||
params = append(params, "current=temperature_2m,apparent_temperature,is_day,weather_code&hourly=temperature_2m,apparent_temperature,weather_code&daily=weather_code,temperature_2m_max,apparent_temperature_max,sunrise,sunset&timeformat=unixtime&forecast_days=3") | ||
|
||
requri := openmeteoURI + strings.Join(params, "&") | ||
|
||
res, err := http.Get(requri) | ||
if err != nil { | ||
log.Fatal("Unable to get weather data: ", err) | ||
} else if res.StatusCode != 200 { | ||
log.Fatal("Unable to get weather data: http status ", res.StatusCode) | ||
} | ||
defer res.Body.Close() | ||
|
||
body, err := io.ReadAll(res.Body) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
if opmeteo.debug { | ||
log.Println("Weather request:", requri) | ||
log.Println("Weather response:", string(body)) | ||
} | ||
|
||
var resp openmeteoResponse | ||
if err = json.Unmarshal(body, &resp); err != nil { | ||
log.Println(err) | ||
} | ||
|
||
ret.Current = parseCurCond(resp.Current) | ||
ret.Location = location | ||
|
||
forecast := opmeteo.parseDaily(resp.Hourly) | ||
|
||
if len(forecast) > 0 { | ||
forecast[0].Astronomy.Sunset = time.Unix(resp.Daily.Sunset[0], 0) | ||
forecast[0].Astronomy.Sunrise = time.Unix(resp.Daily.Sunrise[0], 0) | ||
ret.Forecast = forecast | ||
} | ||
|
||
//jcart, _ := json.MarshalIndent(ret, "", "\t") | ||
//fmt.Println(string(jcart)) | ||
return ret | ||
} | ||
|
||
func init() { | ||
iface.AllBackends["openmeteo"] = &openmeteoConfig{} | ||
} |