2023-05-22 17:13:36 +02:00
|
|
|
// SPDX-FileCopyrightText: 2023 Winni Neessen <wn@neessen.dev>
|
|
|
|
//
|
|
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
|
|
|
|
package meteologix
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"net/url"
|
2023-05-23 20:00:42 +02:00
|
|
|
"strconv"
|
2023-05-22 17:13:36 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
// CurrentWeather represents the current weather API response
|
|
|
|
type CurrentWeather struct {
|
|
|
|
// Data holds the different APICurrentWeatherData points
|
|
|
|
Data APICurrentWeatherData `json:"data"`
|
|
|
|
// Latitude represents the GeoLocation latitude coordinates for the weather data
|
|
|
|
Latitude float64 `json:"lat"`
|
|
|
|
// Longitude represents the GeoLocation longitude coordinates for the weather data
|
|
|
|
Longitude float64 `json:"lon"`
|
|
|
|
// UnitSystem is the unit system that is used for the results (we default to metric)
|
|
|
|
UnitSystem string `json:"systemOfUnits"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// APICurrentWeatherData holds the different data points of the CurrentWeather as
|
|
|
|
// returned by the current weather API endpoints.
|
|
|
|
//
|
|
|
|
// Please keep in mind that different Station types return different values, therefore
|
|
|
|
// all values are represented as pointer type returning nil if the data point in question
|
|
|
|
// is not returned for the requested Station.
|
|
|
|
type APICurrentWeatherData struct {
|
2023-05-23 20:35:00 +02:00
|
|
|
// Dewpoint represents the dewpoint in °C
|
2023-05-24 22:00:47 +02:00
|
|
|
Dewpoint *APIFloat `json:"dewpoint,omitempty"`
|
2023-05-24 10:42:27 +02:00
|
|
|
// HumidityRelative represents the relative humidity in percent
|
2023-05-24 22:00:47 +02:00
|
|
|
HumidityRelative *APIFloat `json:"humidityRelative,omitempty"`
|
2023-06-21 10:29:09 +02:00
|
|
|
// IsDay is true when it is currently daytime
|
|
|
|
IsDay *APIBool `json:"isDay"`
|
2023-05-24 12:25:24 +02:00
|
|
|
// Precipitation represents the current amount of precipitation
|
2023-06-21 10:29:09 +02:00
|
|
|
Precipitation *APIFloat `json:"prec,omitempty"`
|
2023-05-24 11:08:36 +02:00
|
|
|
// Precipitation10m represents the amount of precipitation over the last 10 minutes
|
2023-06-21 10:29:09 +02:00
|
|
|
Precipitation10m *APIFloat `json:"prec10m,omitempty"`
|
2023-05-24 11:08:36 +02:00
|
|
|
// Precipitation1h represents the amount of precipitation over the last hour
|
2023-06-21 10:29:09 +02:00
|
|
|
Precipitation1h *APIFloat `json:"prec1h,omitempty"`
|
2023-05-24 11:08:36 +02:00
|
|
|
// Precipitation24h represents the amount of precipitation over the last 24 hours
|
2023-06-21 10:29:09 +02:00
|
|
|
Precipitation24h *APIFloat `json:"prec24h,omitempty"`
|
2023-05-24 12:25:24 +02:00
|
|
|
// PressureMSL represents the pressure at mean sea level (MSL) in hPa
|
2023-06-21 10:29:09 +02:00
|
|
|
PressureMSL *APIFloat `json:"pressureMsl,omitempty"`
|
2023-06-23 12:35:52 +02:00
|
|
|
// PressureQFE represents the pressure at station level (QFE) in hPa
|
|
|
|
PressureQFE *APIFloat `json:"pressure,omitempty"`
|
2023-06-23 16:38:56 +02:00
|
|
|
// SnowAmount represents the the amount of snow in kg/m3
|
|
|
|
SnowAmount *APIFloat `json:"snowAmount,omitempty"`
|
2023-06-26 12:21:36 +02:00
|
|
|
// SnowHeight represents the the height of snow in m
|
|
|
|
SnowHeight *APIFloat `json:"snowHeight,omitempty"`
|
2023-05-24 11:08:36 +02:00
|
|
|
// Temperature represents the temperature in °C
|
2023-06-23 18:34:25 +02:00
|
|
|
Temperature *APIFloat `json:"temp,omitempty"`
|
2023-06-23 12:05:40 +02:00
|
|
|
// WindDirection represents the direction from which the wind
|
2023-05-24 12:25:24 +02:00
|
|
|
// originates in degree (0=N, 90=E, 180=S, 270=W)
|
2023-06-23 12:05:40 +02:00
|
|
|
WindDirection *APIFloat `json:"windDirection,omitempty"`
|
|
|
|
// WindGust represents the wind gust speed in m/s
|
|
|
|
WindGust *APIFloat `json:"windGust,omitempty"`
|
|
|
|
// WindSpeed represents the wind speed in m/s
|
|
|
|
WindSpeed *APIFloat `json:"windSpeed,omitempty"`
|
2023-05-24 22:00:47 +02:00
|
|
|
// WeatherSymbol is a text representation of the current weather
|
|
|
|
// conditions
|
|
|
|
WeatherSymbol *APIString `json:"weatherSymbol,omitempty"`
|
2023-05-22 17:13:36 +02:00
|
|
|
}
|
|
|
|
|
2023-05-23 20:00:42 +02:00
|
|
|
// CurrentWeatherByCoordinates returns the CurrentWeather values for the given coordinates
|
2023-05-22 17:13:36 +02:00
|
|
|
func (c *Client) CurrentWeatherByCoordinates(la, lo float64) (CurrentWeather, error) {
|
|
|
|
var cw CurrentWeather
|
2023-05-23 20:00:42 +02:00
|
|
|
lat := strconv.FormatFloat(la, 'f', -1, 64)
|
|
|
|
lon := strconv.FormatFloat(lo, 'f', -1, 64)
|
|
|
|
u, err := url.Parse(fmt.Sprintf("%s/current/%s/%s", c.config.apiURL, lat, lon))
|
2023-05-22 17:13:36 +02:00
|
|
|
if err != nil {
|
|
|
|
return cw, fmt.Errorf("failed to parse current weather URL: %w", err)
|
|
|
|
}
|
|
|
|
uq := u.Query()
|
|
|
|
uq.Add("units", "metric")
|
|
|
|
u.RawQuery = uq.Encode()
|
|
|
|
|
|
|
|
r, err := c.httpClient.Get(u.String())
|
|
|
|
if err != nil {
|
|
|
|
return cw, fmt.Errorf("API request failed: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := json.Unmarshal(r, &cw); err != nil {
|
|
|
|
return cw, fmt.Errorf("failed to unmarshal API response JSON: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return cw, nil
|
|
|
|
}
|
|
|
|
|
2023-05-23 20:00:42 +02:00
|
|
|
// CurrentWeatherByLocation returns the CurrentWeather values for the given location
|
|
|
|
func (c *Client) CurrentWeatherByLocation(lo string) (CurrentWeather, error) {
|
|
|
|
gl, err := c.GetGeoLocationByName(lo)
|
|
|
|
if err != nil {
|
|
|
|
return CurrentWeather{}, fmt.Errorf("failed too look up geolocation: %w", err)
|
|
|
|
}
|
|
|
|
return c.CurrentWeatherByCoordinates(gl.Latitude, gl.Longitude)
|
|
|
|
}
|
|
|
|
|
2023-05-23 20:35:00 +02:00
|
|
|
// Dewpoint returns the dewpoint data point as Temperature.
|
|
|
|
// If the data point is not available in the CurrentWeather it will return
|
|
|
|
// Temperature in which the "not available" field will be true.
|
|
|
|
func (cw CurrentWeather) Dewpoint() Temperature {
|
|
|
|
if cw.Data.Dewpoint == nil {
|
|
|
|
return Temperature{na: true}
|
|
|
|
}
|
|
|
|
v := Temperature{
|
|
|
|
dt: cw.Data.Dewpoint.DateTime,
|
|
|
|
n: FieldDewpoint,
|
|
|
|
s: SourceUnknown,
|
2023-05-24 22:00:47 +02:00
|
|
|
fv: cw.Data.Dewpoint.Value,
|
2023-05-23 20:35:00 +02:00
|
|
|
}
|
|
|
|
if cw.Data.Dewpoint.Source != nil {
|
|
|
|
v.s = StringToSource(*cw.Data.Dewpoint.Source)
|
|
|
|
}
|
|
|
|
return v
|
|
|
|
}
|
2023-05-24 10:42:27 +02:00
|
|
|
|
|
|
|
// HumidityRelative returns the relative humidity data point as Humidity.
|
|
|
|
// If the data point is not available in the CurrentWeather it will return
|
|
|
|
// Humidity in which the "not available" field will be true.
|
|
|
|
func (cw CurrentWeather) HumidityRelative() Humidity {
|
2023-06-27 15:19:41 +02:00
|
|
|
if cw.Data.HumidityRelative == nil {
|
2023-05-24 10:42:27 +02:00
|
|
|
return Humidity{na: true}
|
|
|
|
}
|
|
|
|
v := Humidity{
|
|
|
|
dt: cw.Data.HumidityRelative.DateTime,
|
|
|
|
n: FieldHumidityRelative,
|
|
|
|
s: SourceUnknown,
|
2023-05-24 22:00:47 +02:00
|
|
|
fv: cw.Data.HumidityRelative.Value,
|
2023-05-24 10:42:27 +02:00
|
|
|
}
|
|
|
|
if cw.Data.HumidityRelative.Source != nil {
|
|
|
|
v.s = StringToSource(*cw.Data.HumidityRelative.Source)
|
|
|
|
}
|
|
|
|
return v
|
|
|
|
}
|
2023-05-24 11:08:36 +02:00
|
|
|
|
2023-06-21 10:29:09 +02:00
|
|
|
// IsDay returns true if it is currently day at queried location
|
|
|
|
func (cw CurrentWeather) IsDay() bool {
|
|
|
|
if cw.Data.IsDay == nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return cw.Data.IsDay.Value
|
|
|
|
}
|
|
|
|
|
2023-05-24 11:08:36 +02:00
|
|
|
// Precipitation returns the current amount of precipitation (mm) as Precipitation
|
|
|
|
// If the data point is not available in the CurrentWeather it will return
|
|
|
|
// Precipitation in which the "not available" field will be true.
|
|
|
|
//
|
|
|
|
// At this point of development, it looks like currently only the 1 Hour value
|
2023-05-24 22:00:47 +02:00
|
|
|
// is returned by the endpoint, so expect non-availability for any other Timespan
|
2023-05-24 11:08:36 +02:00
|
|
|
// at this point.
|
|
|
|
func (cw CurrentWeather) Precipitation(ts Timespan) Precipitation {
|
2023-05-24 22:00:47 +02:00
|
|
|
var df *APIFloat
|
2023-05-24 11:08:36 +02:00
|
|
|
var fn Fieldname
|
|
|
|
switch ts {
|
|
|
|
case TimespanCurrent:
|
|
|
|
df = cw.Data.Precipitation
|
|
|
|
fn = FieldPrecipitation
|
|
|
|
case Timespan10Min:
|
|
|
|
df = cw.Data.Precipitation10m
|
|
|
|
fn = FieldPrecipitation10m
|
|
|
|
case Timespan1Hour:
|
|
|
|
df = cw.Data.Precipitation1h
|
|
|
|
fn = FieldPrecipitation1h
|
|
|
|
case Timespan24Hours:
|
|
|
|
df = cw.Data.Precipitation24h
|
|
|
|
fn = FieldPrecipitation24h
|
|
|
|
default:
|
|
|
|
return Precipitation{na: true}
|
|
|
|
}
|
|
|
|
|
|
|
|
if df == nil {
|
|
|
|
return Precipitation{na: true}
|
|
|
|
}
|
|
|
|
v := Precipitation{
|
|
|
|
dt: df.DateTime,
|
|
|
|
n: fn,
|
|
|
|
s: SourceUnknown,
|
2023-05-24 22:00:47 +02:00
|
|
|
fv: df.Value,
|
2023-05-24 11:08:36 +02:00
|
|
|
}
|
|
|
|
if df.Source != nil {
|
|
|
|
v.s = StringToSource(*df.Source)
|
|
|
|
}
|
|
|
|
return v
|
|
|
|
}
|
2023-05-24 12:25:24 +02:00
|
|
|
|
|
|
|
// PressureMSL returns the pressure at mean sea level data point as Pressure.
|
|
|
|
// If the data point is not available in the CurrentWeather it will return
|
|
|
|
// Pressure in which the "not available" field will be true.
|
|
|
|
func (cw CurrentWeather) PressureMSL() Pressure {
|
|
|
|
if cw.Data.PressureMSL == nil {
|
|
|
|
return Pressure{na: true}
|
|
|
|
}
|
|
|
|
v := Pressure{
|
|
|
|
dt: cw.Data.PressureMSL.DateTime,
|
|
|
|
n: FieldPressureMSL,
|
|
|
|
s: SourceUnknown,
|
2023-05-24 22:00:47 +02:00
|
|
|
fv: cw.Data.PressureMSL.Value,
|
2023-05-24 12:25:24 +02:00
|
|
|
}
|
|
|
|
if cw.Data.PressureMSL.Source != nil {
|
|
|
|
v.s = StringToSource(*cw.Data.PressureMSL.Source)
|
|
|
|
}
|
|
|
|
return v
|
|
|
|
}
|
|
|
|
|
2023-06-23 12:35:52 +02:00
|
|
|
// PressureQFE returns the pressure at mean sea level data point as Pressure.
|
|
|
|
// If the data point is not available in the CurrentWeather it will return
|
|
|
|
// Pressure in which the "not available" field will be true.
|
|
|
|
func (cw CurrentWeather) PressureQFE() Pressure {
|
|
|
|
if cw.Data.PressureQFE == nil {
|
|
|
|
return Pressure{na: true}
|
|
|
|
}
|
|
|
|
v := Pressure{
|
|
|
|
dt: cw.Data.PressureQFE.DateTime,
|
|
|
|
n: FieldPressureQFE,
|
|
|
|
s: SourceUnknown,
|
|
|
|
fv: cw.Data.PressureQFE.Value,
|
|
|
|
}
|
|
|
|
if cw.Data.PressureQFE.Source != nil {
|
|
|
|
v.s = StringToSource(*cw.Data.PressureQFE.Source)
|
|
|
|
}
|
|
|
|
return v
|
|
|
|
}
|
|
|
|
|
2023-06-26 12:21:36 +02:00
|
|
|
// SnowAmount returns the amount of snow data point as Density.
|
2023-06-23 16:38:56 +02:00
|
|
|
// If the data point is not available in the CurrentWeather it will return
|
|
|
|
// Density in which the "not available" field will be true.
|
|
|
|
func (cw CurrentWeather) SnowAmount() Density {
|
|
|
|
if cw.Data.SnowAmount == nil {
|
|
|
|
return Density{na: true}
|
|
|
|
}
|
|
|
|
v := Density{
|
|
|
|
dt: cw.Data.SnowAmount.DateTime,
|
|
|
|
n: FieldSnowAmount,
|
|
|
|
s: SourceUnknown,
|
|
|
|
fv: cw.Data.SnowAmount.Value,
|
|
|
|
}
|
|
|
|
if cw.Data.SnowAmount.Source != nil {
|
|
|
|
v.s = StringToSource(*cw.Data.SnowAmount.Source)
|
|
|
|
}
|
|
|
|
return v
|
|
|
|
}
|
|
|
|
|
2023-06-26 12:21:36 +02:00
|
|
|
// SnowHeight returns the snow height data point as Height.
|
|
|
|
// If the data point is not available in the CurrentWeather it will return
|
|
|
|
// Height in which the "not available" field will be true.
|
|
|
|
func (cw CurrentWeather) SnowHeight() Height {
|
|
|
|
if cw.Data.SnowHeight == nil {
|
|
|
|
return Height{na: true}
|
|
|
|
}
|
|
|
|
v := Height{
|
|
|
|
dt: cw.Data.SnowHeight.DateTime,
|
|
|
|
n: FieldSnowHeight,
|
|
|
|
s: SourceUnknown,
|
|
|
|
fv: cw.Data.SnowHeight.Value,
|
|
|
|
}
|
|
|
|
if cw.Data.SnowHeight.Source != nil {
|
|
|
|
v.s = StringToSource(*cw.Data.SnowHeight.Source)
|
|
|
|
}
|
|
|
|
return v
|
|
|
|
}
|
|
|
|
|
2023-06-21 10:29:09 +02:00
|
|
|
// Temperature returns the temperature data point as Temperature.
|
|
|
|
// If the data point is not available in the CurrentWeather it will return
|
|
|
|
// Temperature in which the "not available" field will be true.
|
|
|
|
func (cw CurrentWeather) Temperature() Temperature {
|
|
|
|
if cw.Data.Temperature == nil {
|
|
|
|
return Temperature{na: true}
|
|
|
|
}
|
|
|
|
v := Temperature{
|
|
|
|
dt: cw.Data.Temperature.DateTime,
|
|
|
|
n: FieldTemperature,
|
|
|
|
s: SourceUnknown,
|
|
|
|
fv: cw.Data.Temperature.Value,
|
|
|
|
}
|
|
|
|
if cw.Data.Temperature.Source != nil {
|
|
|
|
v.s = StringToSource(*cw.Data.Temperature.Source)
|
|
|
|
}
|
|
|
|
return v
|
|
|
|
}
|
|
|
|
|
2023-05-24 22:00:47 +02:00
|
|
|
// WeatherSymbol returns a text representation of the current weather
|
2023-06-06 15:56:34 +02:00
|
|
|
// as Condition.
|
2023-05-24 22:00:47 +02:00
|
|
|
// If the data point is not available in the CurrentWeather it will return
|
2023-06-06 15:56:34 +02:00
|
|
|
// Condition in which the "not available" field will be true.
|
|
|
|
func (cw CurrentWeather) WeatherSymbol() Condition {
|
2023-05-24 22:00:47 +02:00
|
|
|
if cw.Data.WeatherSymbol == nil {
|
2023-06-06 15:56:34 +02:00
|
|
|
return Condition{na: true}
|
2023-05-24 22:00:47 +02:00
|
|
|
}
|
2023-06-06 15:56:34 +02:00
|
|
|
v := Condition{
|
2023-05-24 22:00:47 +02:00
|
|
|
dt: cw.Data.WeatherSymbol.DateTime,
|
|
|
|
n: FieldWeatherSymbol,
|
|
|
|
s: SourceUnknown,
|
|
|
|
sv: cw.Data.WeatherSymbol.Value,
|
|
|
|
}
|
|
|
|
if cw.Data.WeatherSymbol.Source != nil {
|
|
|
|
v.s = StringToSource(*cw.Data.WeatherSymbol.Source)
|
|
|
|
}
|
|
|
|
return v
|
|
|
|
}
|
|
|
|
|
2023-06-21 10:29:09 +02:00
|
|
|
// WindDirection returns the wind direction data point as Direction.
|
2023-05-24 12:25:24 +02:00
|
|
|
// If the data point is not available in the CurrentWeather it will return
|
|
|
|
// Direction in which the "not available" field will be true.
|
2023-06-21 10:29:09 +02:00
|
|
|
func (cw CurrentWeather) WindDirection() Direction {
|
2023-06-23 12:05:40 +02:00
|
|
|
if cw.Data.WindDirection == nil {
|
2023-05-24 12:25:24 +02:00
|
|
|
return Direction{na: true}
|
|
|
|
}
|
|
|
|
v := Direction{
|
2023-06-23 12:05:40 +02:00
|
|
|
dt: cw.Data.WindDirection.DateTime,
|
|
|
|
n: FieldWindDirection,
|
2023-05-24 12:25:24 +02:00
|
|
|
s: SourceUnknown,
|
2023-06-23 12:05:40 +02:00
|
|
|
fv: cw.Data.WindDirection.Value,
|
2023-05-24 12:25:24 +02:00
|
|
|
}
|
2023-06-23 12:05:40 +02:00
|
|
|
if cw.Data.WindDirection.Source != nil {
|
|
|
|
v.s = StringToSource(*cw.Data.WindDirection.Source)
|
|
|
|
}
|
|
|
|
return v
|
|
|
|
}
|
|
|
|
|
|
|
|
// WindGust returns the wind gust data point as Speed.
|
|
|
|
// If the data point is not available in the CurrentWeather it will return
|
|
|
|
// Speed in which the "not available" field will be true.
|
|
|
|
func (cw CurrentWeather) WindGust() Speed {
|
|
|
|
if cw.Data.WindGust == nil {
|
|
|
|
return Speed{na: true}
|
|
|
|
}
|
|
|
|
v := Speed{
|
|
|
|
dt: cw.Data.WindGust.DateTime,
|
|
|
|
n: FieldWindGust,
|
|
|
|
s: SourceUnknown,
|
|
|
|
fv: cw.Data.WindGust.Value,
|
|
|
|
}
|
|
|
|
if cw.Data.WindGust.Source != nil {
|
|
|
|
v.s = StringToSource(*cw.Data.WindGust.Source)
|
2023-05-24 12:25:24 +02:00
|
|
|
}
|
|
|
|
return v
|
|
|
|
}
|
|
|
|
|
2023-06-21 10:29:09 +02:00
|
|
|
// WindSpeed returns the average wind speed data point as Speed.
|
2023-05-24 12:25:24 +02:00
|
|
|
// If the data point is not available in the CurrentWeather it will return
|
|
|
|
// Speed in which the "not available" field will be true.
|
2023-06-21 10:29:09 +02:00
|
|
|
func (cw CurrentWeather) WindSpeed() Speed {
|
2023-06-23 12:05:40 +02:00
|
|
|
if cw.Data.WindSpeed == nil {
|
2023-05-24 12:25:24 +02:00
|
|
|
return Speed{na: true}
|
|
|
|
}
|
|
|
|
v := Speed{
|
2023-06-23 12:05:40 +02:00
|
|
|
dt: cw.Data.WindSpeed.DateTime,
|
|
|
|
n: FieldWindSpeed,
|
2023-05-24 12:25:24 +02:00
|
|
|
s: SourceUnknown,
|
2023-06-23 12:05:40 +02:00
|
|
|
fv: cw.Data.WindSpeed.Value,
|
2023-05-24 12:25:24 +02:00
|
|
|
}
|
2023-06-23 12:05:40 +02:00
|
|
|
if cw.Data.WindSpeed.Source != nil {
|
|
|
|
v.s = StringToSource(*cw.Data.WindSpeed.Source)
|
2023-05-24 12:25:24 +02:00
|
|
|
}
|
|
|
|
return v
|
|
|
|
}
|