398 lines
13 KiB
Go
398 lines
13 KiB
Go
// SPDX-FileCopyrightText: 2023 Winni Neessen <wn@neessen.dev>
|
|
//
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package meteologix
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
)
|
|
|
|
// ErrUnsupportedDirection is returned when a direction degree is given,
|
|
// that is not resolvable
|
|
var ErrUnsupportedDirection = "Unsupported direction"
|
|
|
|
// Observation represents the observation API response for a Station
|
|
type Observation struct {
|
|
// Altitude is the altitude of the station providing the Observation
|
|
Altitude *int `json:"ele,omitempty"`
|
|
// Data holds the different APIObservationData points
|
|
Data APIObservationData `json:"data"`
|
|
// Name is the name of the Station providing the Observation
|
|
Name string `json:"name"`
|
|
// Latitude represents the GeoLocation latitude coordinates for the Station
|
|
Latitude float64 `json:"lat"`
|
|
// Longitude represents the GeoLocation longitude coordinates for the Station
|
|
Longitude float64 `json:"lon"`
|
|
// StationID is the ID of the Station providing the Observation
|
|
StationID string `json:"stationId"`
|
|
}
|
|
|
|
// APIObservationData holds the different data points of the Observation as
|
|
// returned by the station observation 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 APIObservationData struct {
|
|
// Dewpoint represents the dewpoint in °C
|
|
Dewpoint *APIFloat `json:"dewpoint,omitempty"`
|
|
// DewPointMean represents the mean dewpoint in °C
|
|
DewpointMean *APIFloat `json:"dewpointMean,omitempty"`
|
|
// GlobalRadiation10m represents the sum of global radiation over the last
|
|
// 10 minutes in kJ/m²
|
|
GlobalRadiation10m *APIFloat `json:"globalRadiation10m,omitempty"`
|
|
// GlobalRadiation1h represents the sum of global radiation over the last
|
|
// 1 hour in kJ/m²
|
|
GlobalRadiation1h *APIFloat `json:"globalRadiation1h,omitempty"`
|
|
// GlobalRadiation24h represents the sum of global radiation over the last
|
|
// 24 hour in kJ/m²
|
|
GlobalRadiation24h *APIFloat `json:"globalRadiation24h,omitempty"`
|
|
// HumidityRelative represents the relative humidity in percent
|
|
HumidityRelative *APIFloat `json:"humidityRelative,omitempty"`
|
|
// Precipitation represents the current amount of precipitation
|
|
Precipitation *APIFloat `json:"prec,omitempty"`
|
|
// Precipitation10m represents the amount of precipitation over the last 10 minutes
|
|
Precipitation10m *APIFloat `json:"prec10m,omitempty"`
|
|
// Precipitation1h represents the amount of precipitation over the last hour
|
|
Precipitation1h *APIFloat `json:"prec1h,omitempty"`
|
|
// Precipitation24h represents the amount of precipitation over the last 24 hours
|
|
Precipitation24h *APIFloat `json:"prec24h,omitempty"`
|
|
// PressureMSL represents the air pressure at MSL / temperature adjusted (QFF) in hPa
|
|
PressureMSL *APIFloat `json:"pressureMsl,omitempty"`
|
|
// PressureQFE represents the pressure at station level (QFE) in hPa
|
|
PressureQFE *APIFloat `json:"pressure,omitempty"`
|
|
// Temperature represents the temperature in °C
|
|
Temperature *APIFloat `json:"temp,omitempty"`
|
|
// TemperatureMax represents the maximum temperature in °C
|
|
TemperatureMax *APIFloat `json:"tempMax,omitempty"`
|
|
// TemperatureMean represents the mean temperature in °C
|
|
TemperatureMean *APIFloat `json:"tempMean,omitempty"`
|
|
// TemperatureMin represents the minimum temperature in °C
|
|
TemperatureMin *APIFloat `json:"tempMin,omitempty"`
|
|
// Temperature5cm represents the temperature 5cm above ground in °C
|
|
Temperature5cm *APIFloat `json:"temp5cm,omitempty"`
|
|
// Temperature5cm represents the minimum temperature 5cm above
|
|
// ground in °C
|
|
Temperature5cmMin *APIFloat `json:"temp5cmMin,omitempty"`
|
|
// WindDirection represents the direction from which the wind
|
|
// originates in degree (0=N, 90=E, 180=S, 270=W)
|
|
WindDirection *APIFloat `json:"windDirection,omitempty"`
|
|
// WindSpeed represents the wind speed in knots (soon switched to m/s)
|
|
WindSpeed *APIFloat `json:"windSpeed,omitempty"`
|
|
}
|
|
|
|
// ObservationLatestByStationID returns the latest Observation values from the
|
|
// given Station
|
|
func (c *Client) ObservationLatestByStationID(si string) (Observation, error) {
|
|
var o Observation
|
|
u := fmt.Sprintf("%s/station/%s/observations/latest", c.config.apiURL, si)
|
|
r, err := c.httpClient.Get(u)
|
|
if err != nil {
|
|
return o, fmt.Errorf("API request failed: %w", err)
|
|
}
|
|
|
|
if err := json.Unmarshal(r, &o); err != nil {
|
|
return o, fmt.Errorf("failed to unmarshal API response JSON: %w", err)
|
|
}
|
|
|
|
return o, nil
|
|
}
|
|
|
|
// ObservationLatestByLocation performs a GeoLocation lookup of the location string, checks for any
|
|
// nearby weather stations (25 km radius) and returns the latest Observation values from the
|
|
// Stations with the shortest distance. It will also return the Station that was used for the query.
|
|
// It will throw an error if no station could be found in that queried location.
|
|
func (c *Client) ObservationLatestByLocation(l string) (Observation, Station, error) {
|
|
sl, err := c.StationSearchByLocationWithinRadius(l, 25)
|
|
if err != nil {
|
|
return Observation{}, Station{}, fmt.Errorf("failed search locations at given location: %w", err)
|
|
}
|
|
s := sl[0]
|
|
o, err := c.ObservationLatestByStationID(s.ID)
|
|
return o, s, err
|
|
}
|
|
|
|
// Dewpoint returns the dewpoint data point as Temperature
|
|
// If the data point is not available in the Observation it will return
|
|
// Temperature in which the "not available" field will be
|
|
// true.
|
|
func (o Observation) Dewpoint() Temperature {
|
|
if o.Data.Dewpoint == nil {
|
|
return Temperature{na: true}
|
|
}
|
|
return Temperature{
|
|
dt: o.Data.Dewpoint.DateTime,
|
|
n: FieldDewpoint,
|
|
s: SourceObservation,
|
|
fv: o.Data.Dewpoint.Value,
|
|
}
|
|
}
|
|
|
|
// DewpointMean returns the mean dewpoint data point as Temperature.
|
|
// If the data point is not available in the Observation it will return
|
|
// Temperature in which the "not available" field will be
|
|
// true.
|
|
func (o Observation) DewpointMean() Temperature {
|
|
if o.Data.DewpointMean == nil {
|
|
return Temperature{na: true}
|
|
}
|
|
return Temperature{
|
|
dt: o.Data.DewpointMean.DateTime,
|
|
n: FieldDewpointMean,
|
|
s: SourceObservation,
|
|
fv: o.Data.DewpointMean.Value,
|
|
}
|
|
}
|
|
|
|
// Temperature returns the temperature data point as Temperature.
|
|
// If the data point is not available in the Observation it will return
|
|
// Temperature in which the "not available" field will be
|
|
// true.
|
|
func (o Observation) Temperature() Temperature {
|
|
if o.Data.Temperature == nil {
|
|
return Temperature{na: true}
|
|
}
|
|
return Temperature{
|
|
dt: o.Data.Temperature.DateTime,
|
|
n: FieldTemperature,
|
|
s: SourceObservation,
|
|
fv: o.Data.Temperature.Value,
|
|
}
|
|
}
|
|
|
|
// TemperatureAtGround returns the temperature at ground level (5cm)
|
|
// data point as Temperature.
|
|
// If the data point is not available in the Observation it will return
|
|
// Temperature in which the "not available" field will be
|
|
// true.
|
|
func (o Observation) TemperatureAtGround() Temperature {
|
|
if o.Data.Temperature5cm == nil {
|
|
return Temperature{na: true}
|
|
}
|
|
return Temperature{
|
|
dt: o.Data.Temperature5cm.DateTime,
|
|
n: FieldTemperatureAtGround,
|
|
s: SourceObservation,
|
|
fv: o.Data.Temperature5cm.Value,
|
|
}
|
|
}
|
|
|
|
// TemperatureMax returns the maximum temperature so far data point as
|
|
// Temperature.
|
|
// If the data point is not available in the Observation it will return
|
|
// Temperature in which the "not available" field will be
|
|
// true.
|
|
func (o Observation) TemperatureMax() Temperature {
|
|
if o.Data.TemperatureMax == nil {
|
|
return Temperature{na: true}
|
|
}
|
|
return Temperature{
|
|
dt: o.Data.TemperatureMax.DateTime,
|
|
n: FieldTemperatureMax,
|
|
s: SourceObservation,
|
|
fv: o.Data.TemperatureMax.Value,
|
|
}
|
|
}
|
|
|
|
// TemperatureMin returns the minimum temperature so far data point as
|
|
// Temperature.
|
|
// If the data point is not available in the Observation it will return
|
|
// Temperature in which the "not available" field will be
|
|
// true.
|
|
func (o Observation) TemperatureMin() Temperature {
|
|
if o.Data.TemperatureMin == nil {
|
|
return Temperature{na: true}
|
|
}
|
|
return Temperature{
|
|
dt: o.Data.TemperatureMin.DateTime,
|
|
n: FieldTemperatureMin,
|
|
s: SourceObservation,
|
|
fv: o.Data.TemperatureMin.Value,
|
|
}
|
|
}
|
|
|
|
// TemperatureAtGroundMin returns the minimum temperature so far
|
|
// at ground level (5cm) data point as Temperature
|
|
// If the data point is not available in the Observation it will return
|
|
// Temperature in which the "not available" field will be
|
|
// true.
|
|
func (o Observation) TemperatureAtGroundMin() Temperature {
|
|
if o.Data.Temperature5cmMin == nil {
|
|
return Temperature{na: true}
|
|
}
|
|
return Temperature{
|
|
dt: o.Data.Temperature5cmMin.DateTime,
|
|
n: FieldTemperatureAtGroundMin,
|
|
s: SourceObservation,
|
|
fv: o.Data.Temperature5cmMin.Value,
|
|
}
|
|
}
|
|
|
|
// TemperatureMean returns the mean temperature data point as Temperature.
|
|
// If the data point is not available in the Observation it will return
|
|
// Temperature in which the "not available" field will be
|
|
// true.
|
|
func (o Observation) TemperatureMean() Temperature {
|
|
if o.Data.TemperatureMean == nil {
|
|
return Temperature{na: true}
|
|
}
|
|
return Temperature{
|
|
dt: o.Data.TemperatureMean.DateTime,
|
|
n: FieldTemperatureMean,
|
|
s: SourceObservation,
|
|
fv: o.Data.TemperatureMean.Value,
|
|
}
|
|
}
|
|
|
|
// HumidityRelative returns the relative humidity data point as float64.
|
|
// If the data point is not available in the Observation it will return
|
|
// Humidity in which the "not available" field will be
|
|
// true.
|
|
func (o Observation) HumidityRelative() Humidity {
|
|
if o.Data.HumidityRelative == nil {
|
|
return Humidity{na: true}
|
|
}
|
|
return Humidity{
|
|
dt: o.Data.HumidityRelative.DateTime,
|
|
n: FieldHumidityRelative,
|
|
s: SourceObservation,
|
|
fv: o.Data.HumidityRelative.Value,
|
|
}
|
|
}
|
|
|
|
// PressureMSL returns the relative pressure at mean seal level data point
|
|
// as Pressure.
|
|
// If the data point is not available in the Observation it will return
|
|
// Pressure in which the "not available" field will be
|
|
// true.
|
|
func (o Observation) PressureMSL() Pressure {
|
|
if o.Data.PressureMSL == nil {
|
|
return Pressure{na: true}
|
|
}
|
|
return Pressure{
|
|
dt: o.Data.PressureMSL.DateTime,
|
|
n: FieldPressureMSL,
|
|
s: SourceObservation,
|
|
fv: o.Data.PressureMSL.Value,
|
|
}
|
|
}
|
|
|
|
// PressureQFE returns the relative pressure at mean seal level data point
|
|
// as Pressure.
|
|
// If the data point is not available in the Observation it will return
|
|
// Pressure in which the "not available" field will be
|
|
// true.
|
|
func (o Observation) PressureQFE() Pressure {
|
|
if o.Data.PressureQFE == nil {
|
|
return Pressure{na: true}
|
|
}
|
|
return Pressure{
|
|
dt: o.Data.PressureQFE.DateTime,
|
|
n: FieldPressureQFE,
|
|
s: SourceObservation,
|
|
fv: o.Data.PressureQFE.Value,
|
|
}
|
|
}
|
|
|
|
// Precipitation returns the current amount of precipitation (mm) as
|
|
// Precipitation
|
|
// If the data point is not available in the Observation it will return
|
|
// Precipitation in which the "not available" field will be
|
|
// true.
|
|
func (o Observation) Precipitation(ts Timespan) Precipitation {
|
|
var df *APIFloat
|
|
var fn Fieldname
|
|
switch ts {
|
|
case TimespanCurrent:
|
|
df = o.Data.Precipitation
|
|
fn = FieldPrecipitation
|
|
case Timespan10Min:
|
|
df = o.Data.Precipitation10m
|
|
fn = FieldPrecipitation10m
|
|
case Timespan1Hour:
|
|
df = o.Data.Precipitation1h
|
|
fn = FieldPrecipitation1h
|
|
case Timespan24Hours:
|
|
df = o.Data.Precipitation24h
|
|
fn = FieldPrecipitation24h
|
|
default:
|
|
return Precipitation{na: true}
|
|
}
|
|
|
|
if df == nil {
|
|
return Precipitation{na: true}
|
|
}
|
|
return Precipitation{
|
|
dt: df.DateTime,
|
|
n: fn,
|
|
s: SourceObservation,
|
|
fv: df.Value,
|
|
}
|
|
}
|
|
|
|
// GlobalRadiation returns the current amount of global radiation as
|
|
// Radiation
|
|
// If the data point is not available in the Observation it will return
|
|
// Radiation in which the "not available" field will be
|
|
// true.
|
|
func (o Observation) GlobalRadiation(ts Timespan) Radiation {
|
|
var df *APIFloat
|
|
var fn Fieldname
|
|
switch ts {
|
|
case Timespan10Min:
|
|
df = o.Data.GlobalRadiation10m
|
|
fn = FieldGlobalRadiation10m
|
|
case Timespan1Hour:
|
|
df = o.Data.GlobalRadiation1h
|
|
fn = FieldGlobalRadiation1h
|
|
case Timespan24Hours:
|
|
df = o.Data.GlobalRadiation24h
|
|
fn = FieldGlobalRadiation24h
|
|
default:
|
|
return Radiation{na: true}
|
|
}
|
|
|
|
if df == nil {
|
|
return Radiation{na: true}
|
|
}
|
|
return Radiation{
|
|
dt: df.DateTime,
|
|
n: fn,
|
|
s: SourceObservation,
|
|
fv: df.Value,
|
|
}
|
|
}
|
|
|
|
// WindDirection returns the current direction from which the wind
|
|
// originates in degree (0=N, 90=E, 180=S, 270=W) as Direction.
|
|
// If the data point is not available in the Observation it will return
|
|
// Direction in which the "not available" field will be true.
|
|
func (o Observation) WindDirection() Direction {
|
|
if o.Data.WindDirection == nil {
|
|
return Direction{na: true}
|
|
}
|
|
return Direction{
|
|
dt: o.Data.WindDirection.DateTime,
|
|
n: FieldWindDirection,
|
|
s: SourceObservation,
|
|
fv: o.Data.WindDirection.Value,
|
|
}
|
|
}
|
|
|
|
// WindSpeed returns the current windspeed data point as Speed.
|
|
// If the data point is not available in the Observation it will return
|
|
// Speed in which the "not available" field will be true.
|
|
func (o Observation) WindSpeed() Speed {
|
|
if o.Data.WindSpeed == nil {
|
|
return Speed{na: true}
|
|
}
|
|
return Speed{
|
|
dt: o.Data.WindSpeed.DateTime,
|
|
n: FieldWindSpeed,
|
|
s: SourceObservation,
|
|
fv: o.Data.WindSpeed.Value * 0.5144444444,
|
|
}
|
|
}
|