go-meteologix/curweather.go

362 lines
11 KiB
Go
Raw Permalink Normal View History

// 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"
)
// 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 {
// Dewpoint represents the dewpoint in °C
Dewpoint *APIFloat `json:"dewpoint,omitempty"`
// HumidityRelative represents the relative humidity in percent
HumidityRelative *APIFloat `json:"humidityRelative,omitempty"`
// IsDay is true when it is currently daytime
IsDay *APIBool `json:"isDay"`
// Precipitation represents the current amount of precipitation
Precipitation *APIFloat `json:"prec,omitempty"`
2023-05-24 11:08:36 +02:00
// Precipitation10m represents the amount of precipitation over the last 10 minutes
Precipitation10m *APIFloat `json:"prec10m,omitempty"`
2023-05-24 11:08:36 +02:00
// Precipitation1h represents the amount of precipitation over the last hour
Precipitation1h *APIFloat `json:"prec1h,omitempty"`
2023-05-24 11:08:36 +02:00
// Precipitation24h represents the amount of precipitation over the last 24 hours
Precipitation24h *APIFloat `json:"prec24h,omitempty"`
// PressureMSL represents the pressure at mean sea level (MSL) in hPa
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"`
// 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
// 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"`
// WeatherSymbol is a text representation of the current weather
// conditions
WeatherSymbol *APIString `json:"weatherSymbol,omitempty"`
}
2023-05-23 20:00:42 +02:00
// CurrentWeatherByCoordinates returns the CurrentWeather values for the given coordinates
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))
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)
}
// 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,
fv: cw.Data.Dewpoint.Value,
}
if cw.Data.Dewpoint.Source != nil {
v.s = StringToSource(*cw.Data.Dewpoint.Source)
}
return v
}
// 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 {
if cw.Data.HumidityRelative == nil {
return Humidity{na: true}
}
v := Humidity{
dt: cw.Data.HumidityRelative.DateTime,
n: FieldHumidityRelative,
s: SourceUnknown,
fv: cw.Data.HumidityRelative.Value,
}
if cw.Data.HumidityRelative.Source != nil {
v.s = StringToSource(*cw.Data.HumidityRelative.Source)
}
return v
}
2023-05-24 11:08:36 +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
// 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 {
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,
fv: df.Value,
2023-05-24 11:08:36 +02:00
}
if df.Source != nil {
v.s = StringToSource(*df.Source)
}
return v
}
// 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,
fv: cw.Data.PressureMSL.Value,
}
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
}
// 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
}
// 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
}
// 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
}
// WeatherSymbol returns a text representation of the current weather
2023-06-06 15:56:34 +02:00
// as Condition.
// 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 {
if cw.Data.WeatherSymbol == nil {
2023-06-06 15:56:34 +02:00
return Condition{na: true}
}
2023-06-06 15:56:34 +02:00
v := Condition{
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
}
// WindDirection returns the wind direction data point as Direction.
// If the data point is not available in the CurrentWeather it will return
// Direction in which the "not available" field will be true.
func (cw CurrentWeather) WindDirection() Direction {
2023-06-23 12:05:40 +02:00
if cw.Data.WindDirection == nil {
return Direction{na: true}
}
v := Direction{
2023-06-23 12:05:40 +02:00
dt: cw.Data.WindDirection.DateTime,
n: FieldWindDirection,
s: SourceUnknown,
2023-06-23 12:05:40 +02:00
fv: cw.Data.WindDirection.Value,
}
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)
}
return v
}
// WindSpeed returns the average wind speed 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) WindSpeed() Speed {
2023-06-23 12:05:40 +02:00
if cw.Data.WindSpeed == nil {
return Speed{na: true}
}
v := Speed{
2023-06-23 12:05:40 +02:00
dt: cw.Data.WindSpeed.DateTime,
n: FieldWindSpeed,
s: SourceUnknown,
2023-06-23 12:05:40 +02:00
fv: cw.Data.WindSpeed.Value,
}
2023-06-23 12:05:40 +02:00
if cw.Data.WindSpeed.Source != nil {
v.s = StringToSource(*cw.Data.WindSpeed.Source)
}
return v
}