Major restructuring in favour of current weather reading

- Renamed types from ObservationXXX to generic type names
- Put generic types into their own files
- Added CurrentWeatherByCoordinates
- Added Source type and made it available for Temperature types
This commit is contained in:
Winni Neessen 2023-05-22 17:13:36 +02:00
parent e44e6f6024
commit 80a4d74032
Signed by: wneessen
GPG key ID: 385AC9889632126E
12 changed files with 857 additions and 568 deletions

127
curweather.go Normal file
View file

@ -0,0 +1,127 @@
// SPDX-FileCopyrightText: 2023 Winni Neessen <wn@neessen.dev>
//
// SPDX-License-Identifier: MIT
package meteologix
import (
"encoding/json"
"fmt"
"net/url"
)
// 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 *APIValue `json:"dewpoint,omitempty"`
// DewPointMean represents the mean dewpoint in °C
DewpointMean *APIValue `json:"dewpointMean,omitempty"`
// GlobalRadiation10m represents the sum of global radiation over the last
// 10 minutes in kJ/m²
GlobalRadiation10m *APIValue `json:"globalRadiation10m,omitempty"`
// GlobalRadiation1h represents the sum of global radiation over the last
// 1 hour in kJ/m²
GlobalRadiation1h *APIValue `json:"globalRadiation1h,omitempty"`
// GlobalRadiation24h represents the sum of global radiation over the last
// 24 hour in kJ/m²
GlobalRadiation24h *APIValue `json:"globalRadiation24h,omitempty"`
// HumidityRelative represents the relative humidity in percent
HumidityRelative *APIValue `json:"humidityRelative,omitempty"`
// Precipitation represents the current amount of precipitation
Precipitation *APIValue `json:"prec"`
// Precipitation10m represents the amount of precipitation over the last 10 minutes
Precipitation10m *APIValue `json:"prec10m"`
// Precipitation1h represents the amount of precipitation over the last hour
Precipitation1h *APIValue `json:"prec1h"`
// Precipitation24h represents the amount of precipitation over the last 24 hours
Precipitation24h *APIValue `json:"prec24h"`
// PressureMSL represents the pressure at mean sea level (MSL) in hPa
PressureMSL *APIValue `json:"pressureMsl"`
// PressureMSL represents the pressure at station level (QFE) in hPa
PressureQFE *APIValue `json:"pressure"`
*/
// Temperature represents the temperature in °C
Temperature *APIValue `json:"temp,omitempty"`
/*
// TemperatureMax represents the maximum temperature in °C
TemperatureMax *APIValue `json:"tempMax,omitempty"`
// TemperatureMean represents the mean temperature in °C
TemperatureMean *APIValue `json:"tempMean,omitempty"`
// TemperatureMin represents the minimum temperature in °C
TemperatureMin *APIValue `json:"tempMin,omitempty"`
// Temperature5cm represents the temperature 5cm above ground in °C
Temperature5cm *APIValue `json:"temp5cm,omitempty"`
// Temperature5cm represents the minimum temperature 5cm above
// ground in °C
Temperature5cmMin *APIValue `json:"temp5cmMin,omitempty"`
// Winddirection represents the direction from which the wind
// originates in degree (0=N, 90=E, 180=S, 270=W)
Winddirection *APIValue `json:"windDirection,omitempty"`
// Windspeed represents the wind speed in knots
Windspeed *APIValue `json:"windSpeed,omitempty"`
*/
}
// CurrentWeatherByCoordinates returns the CurrentWeather values for the
// given coordinates
func (c *Client) CurrentWeatherByCoordinates(la, lo float64) (CurrentWeather, error) {
var cw CurrentWeather
u, err := url.Parse(fmt.Sprintf("%s/current/%f/%f", c.config.apiURL, la, lo))
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
}
// 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,
v: cw.Data.Temperature.Value,
}
if cw.Data.Temperature.Source != nil {
v.s = StringToSource(*cw.Data.Temperature.Source)
}
return v
}

88
datatype.go Normal file
View file

@ -0,0 +1,88 @@
// SPDX-FileCopyrightText: 2023 Winni Neessen <wn@neessen.dev>
//
// SPDX-License-Identifier: MIT
package meteologix
import "time"
// Enum for different Fieldname values
const (
// FieldDewpoint represents the Dewpoint data point
FieldDewpoint Fieldname = iota
// FieldDewpointMean represents the TemperatureMean data point
FieldDewpointMean
// FieldGlobalRadiation10m represents the GlobalRadiation10m data point
FieldGlobalRadiation10m
// FieldGlobalRadiation1h represents the GlobalRadiation1h data point
FieldGlobalRadiation1h
// FieldGlobalRadiation24h represents the GlobalRadiation24h data point
FieldGlobalRadiation24h
// FieldHumidityRelative represents the HumidityRelative data point
FieldHumidityRelative
// FieldPrecipitation represents the Precipitation data point
FieldPrecipitation
// FieldPrecipitation10m represents the Precipitation10m data point
FieldPrecipitation10m
// FieldPrecipitation1h represents the Precipitation1h data point
FieldPrecipitation1h
// FieldPrecipitation24h represents the Precipitation24h data point
FieldPrecipitation24h
// FieldPressureMSL represents the PressureMSL data point
FieldPressureMSL
// FieldPressureQFE represents the PressureQFE data point
FieldPressureQFE
// FieldTemperature represents the Temperature data point
FieldTemperature
// FieldTemperatureAtGround represents the TemperatureAtGround data point
FieldTemperatureAtGround
// FieldTemperatureAtGroundMin represents the TemperatureAtGroundMin data point
FieldTemperatureAtGroundMin
// FieldTemperatureMax represents the TemperatureMax data point
FieldTemperatureMax
// FieldTemperatureMean represents the TemperatureMean data point
FieldTemperatureMean
// FieldTemperatureMin represents the TemperatureMin data point
FieldTemperatureMin
// FieldWinddirection represents the Winddirection data point
FieldWinddirection
// FieldWindspeed represents the Windspeed data point
FieldWindspeed
)
// Enum for different Timespan values
const (
// TimespanCurrent represents the moment of the last observation
TimespanCurrent Timespan = iota
// Timespan10Min represents the last 10 minutes
Timespan10Min
// Timespan1Hour represents the last hour
Timespan1Hour
// Timespan24Hours represents the last 24 hours
Timespan24Hours
)
// APIValue is the JSON structure of the weather data that is returned by the
// API endpoints
type APIValue struct {
DateTime time.Time `json:"dateTime"`
Source *string `json:"source,omitempty"`
Value float64 `json:"value"`
}
// Timespan is a type wrapper for an int type
type Timespan int
// WeatherData is a type that holds weather (Observation, Current Weather) data and
// can be wrapped into other types to provide type specific receiver methods
type WeatherData struct {
dt time.Time
n Fieldname
na bool
s Source
v float64
}
// Fieldname is a type wrapper for an int for field names
// of an Observation
type Fieldname int

85
direction.go Normal file
View file

@ -0,0 +1,85 @@
// SPDX-FileCopyrightText: 2023 Winni Neessen <wn@neessen.dev>
//
// SPDX-License-Identifier: MIT
package meteologix
import (
"fmt"
"math"
"time"
)
// WindDirAbbrMap is a map to associate a wind direction degree value with
// the abbreviated direction string
var WindDirAbbrMap = map[float64]string{
0: "N", 11.25: "NbE", 22.5: "NNE", 33.75: "NEbN", 45: "NE", 56.25: "NEbE",
67.5: "ENE", 78.75: "EbN", 90: "E", 101.25: "EbS", 112.5: "ESE", 123.75: "SEbE",
135: "SE", 146.25: "SEbS", 157.5: "SSE", 168.75: "SbE", 180: "S",
191.25: "SbW", 202.5: "SSW", 213.75: "SWbS", 225: "SW", 236.25: "SWbW",
247.5: "WSW", 258.75: "WbS", 270: "W", 281.25: "WbN", 292.5: "WNW",
303.75: "NWbW", 315: "NW", 326.25: "NWbN", 337.5: "NNW", 348.75: "NbW",
}
// WindDirFullMap is a map to associate a wind direction degree value with
// the full direction string
var WindDirFullMap = map[float64]string{
0: "North", 11.25: "North by East", 22.5: "North-Northeast",
33.75: "Northeast by North", 45: "Northeast", 56.25: "Northeast by East",
67.5: "East-Northeast", 78.75: "East by North", 90: "East",
101.25: "East by South", 112.5: "East-Southeast", 123.75: "Southeast by East",
135: "Southeast", 146.25: "Southeast by South", 157.5: "South-Southeast",
168.75: "South by East", 180: "South", 191.25: "South by West",
202.5: "South-Southwest", 213.75: "Southwest by South", 225: "Southwest",
236.25: "Southwest by West", 247.5: "West-Southwest", 258.75: "West by South",
270: "West", 281.25: "West by North", 292.5: "West-Northwest",
303.75: "Northwest by West", 315: "Northwest", 326.25: "Northwest by North",
337.5: "North-Northwest", 348.75: "North by West",
}
// Direction is a type wrapper of an WeatherData for holding directional
// values in WeatherData
type Direction WeatherData
// IsAvailable returns true if an Direction value was
// available at time of query
func (t Direction) IsAvailable() bool {
return !t.na
}
// DateTime returns true if an Direction value was
// available at time of query
func (t Direction) DateTime() time.Time {
return t.dt
}
// Value returns the float64 value of an Direction in degrees
// If the Direction is not available in the Observation
// Vaule will return math.NaN instead.
func (t Direction) Value() float64 {
if t.na {
return math.NaN()
}
return t.v
}
// String satisfies the fmt.Stringer interface for the Direction type
func (t Direction) String() string {
return fmt.Sprintf("%.0f°", t.v)
}
// Direction returns the abbreviation string for a given Direction type
func (t Direction) Direction() string {
if ds, ok := WindDirAbbrMap[t.v]; ok {
return ds
}
return ErrUnsupportedDirection
}
// DirectionFull returns the full string for a given Direction type
func (t Direction) DirectionFull() string {
if ds, ok := WindDirFullMap[t.v]; ok {
return ds
}
return ErrUnsupportedDirection
}

42
humidity.go Normal file
View file

@ -0,0 +1,42 @@
// SPDX-FileCopyrightText: 2023 Winni Neessen <wn@neessen.dev>
//
// SPDX-License-Identifier: MIT
package meteologix
import (
"fmt"
"math"
"time"
)
// Humidity is a type wrapper of an WeatherData for holding humidity
// values in WeatherData
type Humidity WeatherData
// IsAvailable returns true if an Humidity value was
// available at time of query
func (t Humidity) IsAvailable() bool {
return !t.na
}
// DateTime returns true if an Humidity value was
// available at time of query
func (t Humidity) DateTime() time.Time {
return t.dt
}
// String satisfies the fmt.Stringer interface for the Humidity type
func (t Humidity) String() string {
return fmt.Sprintf("%.1f%%", t.v)
}
// Value returns the float64 value of an Humidity
// If the Humidity is not available in the Observation
// Vaule will return math.NaN instead.
func (t Humidity) Value() float64 {
if t.na {
return math.NaN()
}
return t.v
}

View file

@ -7,91 +7,8 @@ package meteologix
import (
"encoding/json"
"fmt"
"math"
"time"
)
const (
// FieldDewpoint represents the Dewpoint data point
FieldDewpoint ObservationFieldName = iota
// FieldDewpointMean represents the TemperatureMean data point
FieldDewpointMean
// FieldGlobalRadiation10m represents the GlobalRadiation10m data point
FieldGlobalRadiation10m
// FieldGlobalRadiation1h represents the GlobalRadiation1h data point
FieldGlobalRadiation1h
// FieldGlobalRadiation24h represents the GlobalRadiation24h data point
FieldGlobalRadiation24h
// FieldHumidityRelative represents the HumidityRelative data point
FieldHumidityRelative
// FieldPrecipitation represents the Precipitation data point
FieldPrecipitation
// FieldPrecipitation10m represents the Precipitation10m data point
FieldPrecipitation10m
// FieldPrecipitation1h represents the Precipitation1h data point
FieldPrecipitation1h
// FieldPrecipitation24h represents the Precipitation24h data point
FieldPrecipitation24h
// FieldPressureMSL represents the PressureMSL data point
FieldPressureMSL
// FieldPressureQFE represents the PressureQFE data point
FieldPressureQFE
// FieldTemperature represents the Temperature data point
FieldTemperature
// FieldTemperatureAtGround represents the TemperatureAtGround data point
FieldTemperatureAtGround
// FieldTemperatureAtGroundMin represents the TemperatureAtGroundMin data point
FieldTemperatureAtGroundMin
// FieldTemperatureMax represents the TemperatureMax data point
FieldTemperatureMax
// FieldTemperatureMean represents the TemperatureMean data point
FieldTemperatureMean
// FieldTemperatureMin represents the TemperatureMin data point
FieldTemperatureMin
// FieldWinddirection represents the Winddirection data point
FieldWinddirection
// FieldWindspeed represents the Windspeed data point
FieldWindspeed
)
const (
// TimespanCurrent represents the moment of the last observation
TimespanCurrent Timespan = iota
// Timespan10Min represents the last 10 minutes
Timespan10Min
// Timespan1Hour represents the last hour
Timespan1Hour
// Timespan24Hours represents the last 24 hours
Timespan24Hours
)
// WindDirAbbrMap is a map to associate a wind direction degree value with
// the abbreviated direction string
var WindDirAbbrMap = map[float64]string{
0: "N", 11.25: "NbE", 22.5: "NNE", 33.75: "NEbN", 45: "NE", 56.25: "NEbE",
67.5: "ENE", 78.75: "EbN", 90: "E", 101.25: "EbS", 112.5: "ESE", 123.75: "SEbE",
135: "SE", 146.25: "SEbS", 157.5: "SSE", 168.75: "SbE", 180: "S",
191.25: "SbW", 202.5: "SSW", 213.75: "SWbS", 225: "SW", 236.25: "SWbW",
247.5: "WSW", 258.75: "WbS", 270: "W", 281.25: "WbN", 292.5: "WNW",
303.75: "NWbW", 315: "NW", 326.25: "NWbN", 337.5: "NNW", 348.75: "NbW",
}
// WindDirFullMap is a map to associate a wind direction degree value with
// the full direction string
var WindDirFullMap = map[float64]string{
0: "North", 11.25: "North by East", 22.5: "North-Northeast",
33.75: "Northeast by North", 45: "Northeast", 56.25: "Northeast by East",
67.5: "East-Northeast", 78.75: "East by North", 90: "East",
101.25: "East by South", 112.5: "East-Southeast", 123.75: "Southeast by East",
135: "Southeast", 146.25: "Southeast by South", 157.5: "South-Southeast",
168.75: "South by East", 180: "South", 191.25: "South by West",
202.5: "South-Southwest", 213.75: "Southwest by South", 225: "Southwest",
236.25: "Southwest by West", 247.5: "West-Southwest", 258.75: "West by South",
270: "West", 281.25: "West by North", 292.5: "West-Northwest",
303.75: "Northwest by West", 315: "Northwest", 326.25: "Northwest by North",
337.5: "North-Northwest", 348.75: "North by West",
}
// ErrUnsupportedDirection is returned when a direction degree is given,
// that is not resolvable
var ErrUnsupportedDirection = "Unsupported direction"
@ -100,8 +17,8 @@ var ErrUnsupportedDirection = "Unsupported direction"
type Observation struct {
// Altitude is the altitude of the station providing the Observation
Altitude *int `json:"ele,omitempty"`
// Data holds the different ObservationData points
Data ObservationData `json:"data"`
// 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
@ -112,110 +29,60 @@ type Observation struct {
StationID string `json:"stationId"`
}
// ObservationData holds the different data points of the Observation.
// 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 ObservationData struct {
type APIObservationData struct {
// Dewpoint represents the dewpoint in °C
Dewpoint *ObservationValue `json:"dewpoint,omitempty"`
Dewpoint *APIValue `json:"dewpoint,omitempty"`
// DewPointMean represents the mean dewpoint in °C
DewpointMean *ObservationValue `json:"dewpointMean,omitempty"`
DewpointMean *APIValue `json:"dewpointMean,omitempty"`
// GlobalRadiation10m represents the sum of global radiation over the last
// 10 minutes in kJ/m²
GlobalRadiation10m *ObservationValue `json:"globalRadiation10m,omitempty"`
GlobalRadiation10m *APIValue `json:"globalRadiation10m,omitempty"`
// GlobalRadiation1h represents the sum of global radiation over the last
// 1 hour in kJ/m²
GlobalRadiation1h *ObservationValue `json:"globalRadiation1h,omitempty"`
GlobalRadiation1h *APIValue `json:"globalRadiation1h,omitempty"`
// GlobalRadiation24h represents the sum of global radiation over the last
// 24 hour in kJ/m²
GlobalRadiation24h *ObservationValue `json:"globalRadiation24h,omitempty"`
GlobalRadiation24h *APIValue `json:"globalRadiation24h,omitempty"`
// HumidityRelative represents the relative humidity in percent
HumidityRelative *ObservationValue `json:"humidityRelative,omitempty"`
HumidityRelative *APIValue `json:"humidityRelative,omitempty"`
// Precipitation represents the current amount of precipitation
Precipitation *ObservationValue `json:"prec"`
Precipitation *APIValue `json:"prec"`
// Precipitation10m represents the amount of precipitation over the last 10 minutes
Precipitation10m *ObservationValue `json:"prec10m"`
Precipitation10m *APIValue `json:"prec10m"`
// Precipitation1h represents the amount of precipitation over the last hour
Precipitation1h *ObservationValue `json:"prec1h"`
Precipitation1h *APIValue `json:"prec1h"`
// Precipitation24h represents the amount of precipitation over the last 24 hours
Precipitation24h *ObservationValue `json:"prec24h"`
Precipitation24h *APIValue `json:"prec24h"`
// PressureMSL represents the pressure at mean sea level (MSL) in hPa
PressureMSL *ObservationValue `json:"pressureMsl"`
PressureMSL *APIValue `json:"pressureMsl"`
// PressureMSL represents the pressure at station level (QFE) in hPa
PressureQFE *ObservationValue `json:"pressure"`
PressureQFE *APIValue `json:"pressure"`
// Temperature represents the temperature in °C
Temperature *ObservationValue `json:"temp,omitempty"`
Temperature *APIValue `json:"temp,omitempty"`
// TemperatureMax represents the maximum temperature in °C
TemperatureMax *ObservationValue `json:"tempMax,omitempty"`
TemperatureMax *APIValue `json:"tempMax,omitempty"`
// TemperatureMean represents the mean temperature in °C
TemperatureMean *ObservationValue `json:"tempMean,omitempty"`
TemperatureMean *APIValue `json:"tempMean,omitempty"`
// TemperatureMin represents the minimum temperature in °C
TemperatureMin *ObservationValue `json:"tempMin,omitempty"`
TemperatureMin *APIValue `json:"tempMin,omitempty"`
// Temperature5cm represents the temperature 5cm above ground in °C
Temperature5cm *ObservationValue `json:"temp5cm,omitempty"`
Temperature5cm *APIValue `json:"temp5cm,omitempty"`
// Temperature5cm represents the minimum temperature 5cm above
// ground in °C
Temperature5cmMin *ObservationValue `json:"temp5cmMin,omitempty"`
Temperature5cmMin *APIValue `json:"temp5cmMin,omitempty"`
// Winddirection represents the direction from which the wind
// originates in degree (0=N, 90=E, 180=S, 270=W)
Winddirection *ObservationValue `json:"windDirection,omitempty"`
Winddirection *APIValue `json:"windDirection,omitempty"`
// Windspeed represents the wind speed in knots
Windspeed *ObservationValue `json:"windSpeed,omitempty"`
Windspeed *APIValue `json:"windSpeed,omitempty"`
}
// ObservationValue is the JSON structure of the Observation data that is
// returned by the API endpoints
type ObservationValue struct {
DateTime time.Time `json:"dateTime"`
Value float64 `json:"value"`
}
// ObservationField is a type that holds Observation data and can be wrapped
// into other types to provide type specific receiver methods
type ObservationField struct {
dt time.Time
n ObservationFieldName
na bool
v float64
}
// ObservationFieldName is a type wrapper for an int for field names
// of an Observation
type ObservationFieldName int
// ObservationDirection is a type wrapper of an ObservationField for
// holding directional values
type ObservationDirection ObservationField
// ObservationHumidity is a type wrapper of an ObservationField for
// holding humidity values
type ObservationHumidity ObservationField
// ObservationPrecipitation is a type wrapper for a precipitation value
// in an Observation
type ObservationPrecipitation ObservationField
// ObservationPressure is a type wrapper for a pressure value
// in an Observation
type ObservationPressure ObservationField
// ObservationRadiation is a type wrapper of an ObservationField for
// holding radiation values
type ObservationRadiation ObservationField
// ObservationSpeed is a type wrapper of an ObservationField for
// holding speed values
type ObservationSpeed ObservationField
// ObservationTemperature is a type wrapper of an ObservationField for
// holding temperature values
type ObservationTemperature ObservationField
// Timespan is a type wrapper for an int type
type Timespan int
// ObservationLatestByStationID returns the latest Observation values from the
// given Station
func (c *Client) ObservationLatestByStationID(si string) (Observation, error) {
@ -247,185 +114,196 @@ func (c *Client) ObservationLatestByLocation(l string) (Observation, Station, er
return o, s, err
}
// Dewpoint returns the dewpoint data point as ObservationTemperature
// Dewpoint returns the dewpoint data point as Temperature
// If the data point is not available in the Observation it will return
// ObservationTemperature in which the "not available" field will be
// Temperature in which the "not available" field will be
// true.
func (o Observation) Dewpoint() ObservationTemperature {
func (o Observation) Dewpoint() Temperature {
if o.Data.Dewpoint == nil {
return ObservationTemperature{na: true}
return Temperature{na: true}
}
return ObservationTemperature{
return Temperature{
dt: o.Data.Dewpoint.DateTime,
n: FieldDewpoint,
s: SourceObservation,
v: o.Data.Dewpoint.Value,
}
}
// DewpointMean returns the mean dewpoint data point as ObservationTemperature.
// DewpointMean returns the mean dewpoint data point as Temperature.
// If the data point is not available in the Observation it will return
// ObservationTemperature in which the "not available" field will be
// Temperature in which the "not available" field will be
// true.
func (o Observation) DewpointMean() ObservationTemperature {
func (o Observation) DewpointMean() Temperature {
if o.Data.DewpointMean == nil {
return ObservationTemperature{na: true}
return Temperature{na: true}
}
return ObservationTemperature{
return Temperature{
dt: o.Data.DewpointMean.DateTime,
n: FieldDewpointMean,
s: SourceObservation,
v: o.Data.DewpointMean.Value,
}
}
// Temperature returns the temperature data point as ObservationTemperature.
// Temperature returns the temperature data point as Temperature.
// If the data point is not available in the Observation it will return
// ObservationTemperature in which the "not available" field will be
// Temperature in which the "not available" field will be
// true.
func (o Observation) Temperature() ObservationTemperature {
func (o Observation) Temperature() Temperature {
if o.Data.Temperature == nil {
return ObservationTemperature{na: true}
return Temperature{na: true}
}
return ObservationTemperature{
return Temperature{
dt: o.Data.Temperature.DateTime,
n: FieldTemperature,
s: SourceObservation,
v: o.Data.Temperature.Value,
}
}
// TemperatureAtGround returns the temperature at ground level (5cm)
// data point as ObservationTemperature.
// data point as Temperature.
// If the data point is not available in the Observation it will return
// ObservationTemperature in which the "not available" field will be
// Temperature in which the "not available" field will be
// true.
func (o Observation) TemperatureAtGround() ObservationTemperature {
func (o Observation) TemperatureAtGround() Temperature {
if o.Data.Temperature5cm == nil {
return ObservationTemperature{na: true}
return Temperature{na: true}
}
return ObservationTemperature{
return Temperature{
dt: o.Data.Temperature5cm.DateTime,
n: FieldTemperatureAtGround,
s: SourceObservation,
v: o.Data.Temperature5cm.Value,
}
}
// TemperatureMax returns the maximum temperature so far data point as
// ObservationTemperature.
// Temperature.
// If the data point is not available in the Observation it will return
// ObservationTemperature in which the "not available" field will be
// Temperature in which the "not available" field will be
// true.
func (o Observation) TemperatureMax() ObservationTemperature {
func (o Observation) TemperatureMax() Temperature {
if o.Data.TemperatureMax == nil {
return ObservationTemperature{na: true}
return Temperature{na: true}
}
return ObservationTemperature{
return Temperature{
dt: o.Data.TemperatureMax.DateTime,
n: FieldTemperatureMax,
s: SourceObservation,
v: o.Data.TemperatureMax.Value,
}
}
// TemperatureMin returns the minimum temperature so far data point as
// ObservationTemperature.
// Temperature.
// If the data point is not available in the Observation it will return
// ObservationTemperature in which the "not available" field will be
// Temperature in which the "not available" field will be
// true.
func (o Observation) TemperatureMin() ObservationTemperature {
func (o Observation) TemperatureMin() Temperature {
if o.Data.TemperatureMin == nil {
return ObservationTemperature{na: true}
return Temperature{na: true}
}
return ObservationTemperature{
return Temperature{
dt: o.Data.TemperatureMin.DateTime,
n: FieldTemperatureMin,
s: SourceObservation,
v: o.Data.TemperatureMin.Value,
}
}
// TemperatureAtGroundMin returns the minimum temperature so far
// at ground level (5cm) data point as ObservationTemperature
// at ground level (5cm) data point as Temperature
// If the data point is not available in the Observation it will return
// ObservationTemperature in which the "not available" field will be
// Temperature in which the "not available" field will be
// true.
func (o Observation) TemperatureAtGroundMin() ObservationTemperature {
func (o Observation) TemperatureAtGroundMin() Temperature {
if o.Data.Temperature5cmMin == nil {
return ObservationTemperature{na: true}
return Temperature{na: true}
}
return ObservationTemperature{
return Temperature{
dt: o.Data.Temperature5cmMin.DateTime,
n: FieldTemperatureAtGroundMin,
s: SourceObservation,
v: o.Data.Temperature5cmMin.Value,
}
}
// TemperatureMean returns the mean temperature data point as ObservationTemperature.
// TemperatureMean returns the mean temperature data point as Temperature.
// If the data point is not available in the Observation it will return
// ObservationTemperature in which the "not available" field will be
// Temperature in which the "not available" field will be
// true.
func (o Observation) TemperatureMean() ObservationTemperature {
func (o Observation) TemperatureMean() Temperature {
if o.Data.TemperatureMean == nil {
return ObservationTemperature{na: true}
return Temperature{na: true}
}
return ObservationTemperature{
return Temperature{
dt: o.Data.TemperatureMean.DateTime,
n: FieldTemperatureMean,
s: SourceObservation,
v: 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
// ObservationHumidity in which the "not available" field will be
// Humidity in which the "not available" field will be
// true.
func (o Observation) HumidityRelative() ObservationHumidity {
func (o Observation) HumidityRelative() Humidity {
if o.Data.HumidityRelative == nil {
return ObservationHumidity{na: true}
return Humidity{na: true}
}
return ObservationHumidity{
return Humidity{
dt: o.Data.HumidityRelative.DateTime,
n: FieldHumidityRelative,
s: SourceObservation,
v: o.Data.HumidityRelative.Value,
}
}
// PressureMSL returns the relative pressure at mean seal level data point
// as ObservationPressure.
// as Pressure.
// If the data point is not available in the Observation it will return
// ObservationPressure in which the "not available" field will be
// Pressure in which the "not available" field will be
// true.
func (o Observation) PressureMSL() ObservationPressure {
func (o Observation) PressureMSL() Pressure {
if o.Data.PressureMSL == nil {
return ObservationPressure{na: true}
return Pressure{na: true}
}
return ObservationPressure{
return Pressure{
dt: o.Data.PressureMSL.DateTime,
n: FieldPressureMSL,
s: SourceObservation,
v: o.Data.PressureMSL.Value,
}
}
// PressureQFE returns the relative pressure at mean seal level data point
// as ObservationPressure.
// as Pressure.
// If the data point is not available in the Observation it will return
// ObservationPressure in which the "not available" field will be
// Pressure in which the "not available" field will be
// true.
func (o Observation) PressureQFE() ObservationPressure {
func (o Observation) PressureQFE() Pressure {
if o.Data.PressureQFE == nil {
return ObservationPressure{na: true}
return Pressure{na: true}
}
return ObservationPressure{
return Pressure{
dt: o.Data.PressureQFE.DateTime,
n: FieldPressureQFE,
s: SourceObservation,
v: o.Data.PressureQFE.Value,
}
}
// Precipitation returns the current amount of precipitation (mm) as
// ObservationPrecipitation
// Precipitation
// If the data point is not available in the Observation it will return
// ObservationPrecipitation in which the "not available" field will be
// Precipitation in which the "not available" field will be
// true.
func (o Observation) Precipitation(ts Timespan) ObservationPrecipitation {
var df *ObservationValue
var fn ObservationFieldName
func (o Observation) Precipitation(ts Timespan) Precipitation {
var df *APIValue
var fn Fieldname
switch ts {
case TimespanCurrent:
df = o.Data.Precipitation
@ -440,27 +318,28 @@ func (o Observation) Precipitation(ts Timespan) ObservationPrecipitation {
df = o.Data.Precipitation24h
fn = FieldPrecipitation24h
default:
return ObservationPrecipitation{na: true}
return Precipitation{na: true}
}
if df == nil {
return ObservationPrecipitation{na: true}
return Precipitation{na: true}
}
return ObservationPrecipitation{
return Precipitation{
dt: df.DateTime,
n: fn,
s: SourceObservation,
v: df.Value,
}
}
// GlobalRadiation returns the current amount of global radiation as
// ObservationRadiation
// Radiation
// If the data point is not available in the Observation it will return
// ObservationRadiation in which the "not available" field will be
// Radiation in which the "not available" field will be
// true.
func (o Observation) GlobalRadiation(ts Timespan) ObservationRadiation {
var df *ObservationValue
var fn ObservationFieldName
func (o Observation) GlobalRadiation(ts Timespan) Radiation {
var df *APIValue
var fn Fieldname
switch ts {
case Timespan10Min:
df = o.Data.GlobalRadiation10m
@ -472,293 +351,47 @@ func (o Observation) GlobalRadiation(ts Timespan) ObservationRadiation {
df = o.Data.GlobalRadiation24h
fn = FieldGlobalRadiation24h
default:
return ObservationRadiation{na: true}
return Radiation{na: true}
}
if df == nil {
return ObservationRadiation{na: true}
return Radiation{na: true}
}
return ObservationRadiation{
return Radiation{
dt: df.DateTime,
n: fn,
s: SourceObservation,
v: df.Value,
}
}
// Winddirection returns the current direction from which the wind
// originates in degree (0=N, 90=E, 180=S, 270=W) as ObservationDirection.
// 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
// ObservationDirection in which the "not available" field will be true.
func (o Observation) Winddirection() ObservationDirection {
// Direction in which the "not available" field will be true.
func (o Observation) Winddirection() Direction {
if o.Data.Winddirection == nil {
return ObservationDirection{na: true}
return Direction{na: true}
}
return ObservationDirection{
return Direction{
dt: o.Data.Winddirection.DateTime,
n: FieldWinddirection,
s: SourceObservation,
v: o.Data.Winddirection.Value,
}
}
// Windspeed returns the current windspeed data point as ObservationSpeed.
// Windspeed returns the current windspeed data point as Speed.
// If the data point is not available in the Observation it will return
// ObservationSpeed in which the "not available" field will be true.
func (o Observation) Windspeed() ObservationSpeed {
// Speed in which the "not available" field will be true.
func (o Observation) Windspeed() Speed {
if o.Data.Windspeed == nil {
return ObservationSpeed{na: true}
return Speed{na: true}
}
return ObservationSpeed{
return Speed{
dt: o.Data.Windspeed.DateTime,
n: FieldWindspeed,
s: SourceObservation,
v: o.Data.Windspeed.Value,
}
}
// IsAvailable returns true if an ObservationTemperature value was
// available at time of query
func (t ObservationTemperature) IsAvailable() bool {
return !t.na
}
// DateTime returns true if an ObservationTemperature value was
// available at time of query
func (t ObservationTemperature) DateTime() time.Time {
return t.dt
}
// Value returns the float64 value of an ObservationTemperature
// If the ObservationTemperature is not available in the Observation
// Vaule will return math.NaN instead.
func (t ObservationTemperature) Value() float64 {
if t.na {
return math.NaN()
}
return t.v
}
// String satisfies the fmt.Stringer interface for the ObservationTemperature type
func (t ObservationTemperature) String() string {
return fmt.Sprintf("%.1f°C", t.v)
}
// Celsius returns the ObservationTemperature value in Celsius
func (t ObservationTemperature) Celsius() float64 {
return t.v
}
// CelsiusString returns the ObservationTemperature value as Celsius
// formated string.
//
// This is an alias for the fmt.Stringer interface
func (t ObservationTemperature) CelsiusString() string {
return t.String()
}
// Fahrenheit returns the ObservationTemperature value in Fahrenheit
func (t ObservationTemperature) Fahrenheit() float64 {
return t.v*9/5 + 32
}
// FahrenheitString returns the ObservationTemperature value as Fahrenheit
// formated string.
func (t ObservationTemperature) FahrenheitString() string {
return fmt.Sprintf("%.1f°F", t.Fahrenheit())
}
// IsAvailable returns true if an ObservationHumidity value was
// available at time of query
func (t ObservationHumidity) IsAvailable() bool {
return !t.na
}
// DateTime returns true if an ObservationHumidity value was
// available at time of query
func (t ObservationHumidity) DateTime() time.Time {
return t.dt
}
// String satisfies the fmt.Stringer interface for the ObservationHumidity type
func (t ObservationHumidity) String() string {
return fmt.Sprintf("%.1f%%", t.v)
}
// Value returns the float64 value of an ObservationHumidity
// If the ObservationHumidity is not available in the Observation
// Vaule will return math.NaN instead.
func (t ObservationHumidity) Value() float64 {
if t.na {
return math.NaN()
}
return t.v
}
// IsAvailable returns true if an ObservationPrecipitation value was
// available at time of query
func (t ObservationPrecipitation) IsAvailable() bool {
return !t.na
}
// DateTime returns true if an ObservationPrecipitation value was
// available at time of query
func (t ObservationPrecipitation) DateTime() time.Time {
return t.dt
}
// String satisfies the fmt.Stringer interface for the ObservationPrecipitation type
func (t ObservationPrecipitation) String() string {
return fmt.Sprintf("%.1fmm", t.v)
}
// Value returns the float64 value of an ObservationPrecipitation
// If the ObservationPrecipitation is not available in the Observation
// Vaule will return math.NaN instead.
func (t ObservationPrecipitation) Value() float64 {
if t.na {
return math.NaN()
}
return t.v
}
// IsAvailable returns true if an ObservationPressure value was
// available at time of query
func (t ObservationPressure) IsAvailable() bool {
return !t.na
}
// DateTime returns true if an ObservationPressure value was
// available at time of query
func (t ObservationPressure) DateTime() time.Time {
return t.dt
}
// String satisfies the fmt.Stringer interface for the ObservationPressure type
func (t ObservationPressure) String() string {
return fmt.Sprintf("%.1fhPa", t.v)
}
// Value returns the float64 value of an ObservationPressure
// If the ObservationPressure is not available in the Observation
// Vaule will return math.NaN instead.
func (t ObservationPressure) Value() float64 {
if t.na {
return math.NaN()
}
return t.v
}
// IsAvailable returns true if an ObservationRadiation value was
// available at time of query
func (t ObservationRadiation) IsAvailable() bool {
return !t.na
}
// DateTime returns true if an ObservationRadiation value was
// available at time of query
func (t ObservationRadiation) DateTime() time.Time {
return t.dt
}
// Value returns the float64 value of an ObservationRadiation
// If the ObservationRadiation is not available in the Observation
// Vaule will return math.NaN instead.
func (t ObservationRadiation) Value() float64 {
if t.na {
return math.NaN()
}
return t.v
}
// String satisfies the fmt.Stringer interface for the ObservationRadiation type
func (t ObservationRadiation) String() string {
return fmt.Sprintf("%.0fkJ/m²", t.v)
}
// IsAvailable returns true if an ObservationSpeed value was
// available at time of query
func (t ObservationSpeed) IsAvailable() bool {
return !t.na
}
// DateTime returns true if an ObservationSpeed value was
// available at time of query
func (t ObservationSpeed) DateTime() time.Time {
return t.dt
}
// Value returns the float64 value of an ObservationSpeed in knots
// If the ObservationSpeed is not available in the Observation
// Vaule will return math.NaN instead.
func (t ObservationSpeed) Value() float64 {
if t.na {
return math.NaN()
}
return t.v
}
// String satisfies the fmt.Stringer interface for the ObservationSpeed type
func (t ObservationSpeed) String() string {
return fmt.Sprintf("%.0fkn", t.v)
}
// KMH returns the ObservationSpeed value in km/h
func (t ObservationSpeed) KMH() float64 {
return t.v * 1.852
}
// KMHString returns the ObservationSpeed value as formatted string in km/h
func (t ObservationSpeed) KMHString() string {
return fmt.Sprintf("%.1fkm/h", t.KMH())
}
// MPH returns the ObservationSpeed value in mi/h
func (t ObservationSpeed) MPH() float64 {
return t.v * 1.151
}
// MPHString returns the ObservationSpeed value as formatted string in mi/h
func (t ObservationSpeed) MPHString() string {
return fmt.Sprintf("%.1fmi/h", t.MPH())
}
// IsAvailable returns true if an ObservationDirection value was
// available at time of query
func (t ObservationDirection) IsAvailable() bool {
return !t.na
}
// DateTime returns true if an ObservationDirection value was
// available at time of query
func (t ObservationDirection) DateTime() time.Time {
return t.dt
}
// Value returns the float64 value of an ObservationDirection in degrees
// If the ObservationDirection is not available in the Observation
// Vaule will return math.NaN instead.
func (t ObservationDirection) Value() float64 {
if t.na {
return math.NaN()
}
return t.v
}
// String satisfies the fmt.Stringer interface for the ObservationDirection type
func (t ObservationDirection) String() string {
return fmt.Sprintf("%.0f°", t.v)
}
// Direction returns the abbreviation string for a given ObservationDirection type
func (t ObservationDirection) Direction() string {
if ds, ok := WindDirAbbrMap[t.v]; ok {
return ds
}
return ErrUnsupportedDirection
}
// DirectionFull returns the full string for a given ObservationDirection type
func (t ObservationDirection) DirectionFull() string {
if ds, ok := WindDirFullMap[t.v]; ok {
return ds
}
return ErrUnsupportedDirection
}

View file

@ -135,17 +135,17 @@ func TestClient_ObservationLatestByStationID_Dewpoint(t *testing.T) {
// Station ID
sid string
// Observation dewpoint
dp *ObservationTemperature
dp *Temperature
}{
{"K-Botanischer Garten", "199942", &ObservationTemperature{
{"K-Botanischer Garten", "199942", &Temperature{
dt: time.Date(2023, 0o5, 15, 20, 10, 0, 0, time.UTC),
v: 10.1,
}},
{"K-Stammheim", "H744", &ObservationTemperature{
{"K-Stammheim", "H744", &Temperature{
dt: time.Date(2023, 0o5, 15, 19, 30, 0, 0, time.UTC),
v: 9.7,
}},
{"All data fields", "all", &ObservationTemperature{
{"All data fields", "all", &Temperature{
dt: time.Date(2023, 0o5, 17, 7, 40, 0, 0, time.UTC),
v: 6.5,
}},
@ -197,11 +197,11 @@ func TestClient_ObservationLatestByStationID_DewpointMean(t *testing.T) {
// Station ID
sid string
// Observation dewpoint
t *ObservationTemperature
t *Temperature
}{
{"K-Botanischer Garten", "199942", nil},
{"K-Stammheim", "H744", nil},
{"All data fields", "all", &ObservationTemperature{v: 8.3}},
{"All data fields", "all", &Temperature{v: 8.3}},
{"No data fields", "none", nil},
}
c := New(withMockAPI())
@ -245,17 +245,17 @@ func TestClient_ObservationLatestByStationID_HumidityRealtive(t *testing.T) {
// Station ID
sid string
// Observation dewpoint
h *ObservationHumidity
h *Humidity
}{
{"K-Botanischer Garten", "199942", &ObservationHumidity{
{"K-Botanischer Garten", "199942", &Humidity{
dt: time.Date(2023, 0o5, 15, 20, 10, 0, 0, time.UTC),
v: 80,
}},
{"K-Stammheim", "H744", &ObservationHumidity{
{"K-Stammheim", "H744", &Humidity{
dt: time.Date(2023, 0o5, 15, 19, 30, 0, 0, time.UTC),
v: 73,
}},
{"All data fields", "all", &ObservationHumidity{
{"All data fields", "all", &Humidity{
dt: time.Date(2023, 0o5, 17, 7, 40, 0, 0, time.UTC),
v: 72,
}},
@ -306,17 +306,17 @@ func TestClient_ObservationLatestByStationID_PrecipitationCurrent(t *testing.T)
// Station ID
sid string
// Observation precipitation
p *ObservationPrecipitation
p *Precipitation
}{
{"K-Botanischer Garten", "199942", &ObservationPrecipitation{
{"K-Botanischer Garten", "199942", &Precipitation{
dt: time.Date(2023, 0o5, 15, 18, 0, 0, 0, time.UTC),
v: 0,
}},
{"K-Stammheim", "H744", &ObservationPrecipitation{
{"K-Stammheim", "H744", &Precipitation{
dt: time.Date(2023, 0o5, 15, 19, 30, 0, 0, time.UTC),
v: 0,
}},
{"All data fields", "all", &ObservationPrecipitation{
{"All data fields", "all", &Precipitation{
dt: time.Date(2023, 0o5, 17, 7, 30, 0, 0, time.UTC),
v: 0.1,
}},
@ -369,11 +369,11 @@ func TestClient_ObservationLatestByStationID_Precipitation10m(t *testing.T) {
// Station ID
sid string
// Observation precipitation
p *ObservationPrecipitation
p *Precipitation
}{
{"K-Botanischer Garten", "199942", &ObservationPrecipitation{v: 0}},
{"K-Stammheim", "H744", &ObservationPrecipitation{v: 0}},
{"All data fields", "all", &ObservationPrecipitation{v: 0.5}},
{"K-Botanischer Garten", "199942", &Precipitation{v: 0}},
{"K-Stammheim", "H744", &Precipitation{v: 0}},
{"All data fields", "all", &Precipitation{v: 0.5}},
{"No data fields", "none", nil},
}
c := New(withMockAPI())
@ -418,11 +418,11 @@ func TestClient_ObservationLatestByStationID_Precipitation1h(t *testing.T) {
// Station ID
sid string
// Observation precipitation
p *ObservationPrecipitation
p *Precipitation
}{
{"K-Botanischer Garten", "199942", &ObservationPrecipitation{v: 0}},
{"K-Stammheim", "H744", &ObservationPrecipitation{v: 0}},
{"All data fields", "all", &ObservationPrecipitation{v: 10.3}},
{"K-Botanischer Garten", "199942", &Precipitation{v: 0}},
{"K-Stammheim", "H744", &Precipitation{v: 0}},
{"All data fields", "all", &Precipitation{v: 10.3}},
{"No data fields", "none", nil},
}
c := New(withMockAPI())
@ -467,11 +467,11 @@ func TestClient_ObservationLatestByStationID_Precipitation24h(t *testing.T) {
// Station ID
sid string
// Observation precipitation
p *ObservationPrecipitation
p *Precipitation
}{
{"K-Botanischer Garten", "199942", &ObservationPrecipitation{v: 0}},
{"K-Stammheim", "H744", &ObservationPrecipitation{v: 0}},
{"All data fields", "all", &ObservationPrecipitation{v: 32.12}},
{"K-Botanischer Garten", "199942", &Precipitation{v: 0}},
{"K-Stammheim", "H744", &Precipitation{v: 0}},
{"All data fields", "all", &Precipitation{v: 32.12}},
{"No data fields", "none", nil},
}
c := New(withMockAPI())
@ -538,11 +538,11 @@ func TestClient_ObservationLatestByStationID_Temperature(t *testing.T) {
// Station ID
sid string
// Observation dewpoint
t *ObservationTemperature
t *Temperature
}{
{"K-Botanischer Garten", "199942", &ObservationTemperature{v: 13.4}},
{"K-Stammheim", "H744", &ObservationTemperature{v: 14.4}},
{"All data fields", "all", &ObservationTemperature{v: 10.8}},
{"K-Botanischer Garten", "199942", &Temperature{v: 13.4}},
{"K-Stammheim", "H744", &Temperature{v: 14.4}},
{"All data fields", "all", &Temperature{v: 10.8}},
{"No data fields", "none", nil},
}
c := New(withMockAPI())
@ -586,11 +586,11 @@ func TestClient_ObservationLatestByStationID_TemperatureAtGround(t *testing.T) {
// Station ID
sid string
// Observation dewpoint
t *ObservationTemperature
t *Temperature
}{
{"K-Botanischer Garten", "199942", nil},
{"K-Stammheim", "H744", &ObservationTemperature{v: 14.3}},
{"All data fields", "all", &ObservationTemperature{v: 15.4}},
{"K-Stammheim", "H744", &Temperature{v: 14.3}},
{"All data fields", "all", &Temperature{v: 15.4}},
{"No data fields", "none", nil},
}
c := New(withMockAPI())
@ -634,11 +634,11 @@ func TestClient_ObservationLatestByStationID_TemperatureMin(t *testing.T) {
// Station ID
sid string
// Observation dewpoint
t *ObservationTemperature
t *Temperature
}{
{"K-Botanischer Garten", "199942", &ObservationTemperature{v: 12.3}},
{"K-Stammheim", "H744", &ObservationTemperature{v: 11.9}},
{"All data fields", "all", &ObservationTemperature{v: 6.2}},
{"K-Botanischer Garten", "199942", &Temperature{v: 12.3}},
{"K-Stammheim", "H744", &Temperature{v: 11.9}},
{"All data fields", "all", &Temperature{v: 6.2}},
{"No data fields", "none", nil},
}
c := New(withMockAPI())
@ -682,11 +682,11 @@ func TestClient_ObservationLatestByStationID_TemperatureMax(t *testing.T) {
// Station ID
sid string
// Observation dewpoint
t *ObservationTemperature
t *Temperature
}{
{"K-Botanischer Garten", "199942", &ObservationTemperature{v: 20.5}},
{"K-Stammheim", "H744", &ObservationTemperature{v: 20.7}},
{"All data fields", "all", &ObservationTemperature{v: 12.4}},
{"K-Botanischer Garten", "199942", &Temperature{v: 20.5}},
{"K-Stammheim", "H744", &Temperature{v: 20.7}},
{"All data fields", "all", &Temperature{v: 12.4}},
{"No data fields", "none", nil},
}
c := New(withMockAPI())
@ -730,11 +730,11 @@ func TestClient_ObservationLatestByStationID_TemperatureAtGroundMin(t *testing.T
// Station ID
sid string
// Observation dewpoint
t *ObservationTemperature
t *Temperature
}{
{"K-Botanischer Garten", "199942", nil},
{"K-Stammheim", "H744", &ObservationTemperature{v: 12.8}},
{"All data fields", "all", &ObservationTemperature{v: 3.7}},
{"K-Stammheim", "H744", &Temperature{v: 12.8}},
{"All data fields", "all", &Temperature{v: 3.7}},
{"No data fields", "none", nil},
}
c := New(withMockAPI())
@ -778,11 +778,11 @@ func TestClient_ObservationLatestByStationID_TemperatureMean(t *testing.T) {
// Station ID
sid string
// Observation dewpoint
t *ObservationTemperature
t *Temperature
}{
{"K-Botanischer Garten", "199942", nil},
{"K-Stammheim", "H744", nil},
{"All data fields", "all", &ObservationTemperature{v: 16.3}},
{"All data fields", "all", &Temperature{v: 16.3}},
{"No data fields", "none", nil},
}
c := New(withMockAPI())
@ -826,14 +826,14 @@ func TestClient_ObservationLatestByStationID_PressureMSL(t *testing.T) {
// Station ID
sid string
// Observation dewpoint
p *ObservationPressure
p *Pressure
}{
{"K-Botanischer Garten", "199942", &ObservationPressure{
{"K-Botanischer Garten", "199942", &Pressure{
dt: time.Date(2023, 0o5, 15, 20, 10, 0, 0, time.UTC),
v: 1015.5,
}},
{"K-Stammheim", "H744", nil},
{"All data fields", "all", &ObservationPressure{
{"All data fields", "all", &Pressure{
dt: time.Date(2023, 0o5, 17, 7, 40, 0, 0, time.UTC),
v: 1026.3,
}},
@ -884,11 +884,11 @@ func TestClient_ObservationLatestByStationID_PressureQFE(t *testing.T) {
// Station ID
sid string
// Observation dewpoint
p *ObservationPressure
p *Pressure
}{
{"K-Botanischer Garten", "199942", &ObservationPressure{v: 1010.2}},
{"K-Botanischer Garten", "199942", &Pressure{v: 1010.2}},
{"K-Stammheim", "H744", nil},
{"All data fields", "all", &ObservationPressure{v: 1020.9}},
{"All data fields", "all", &Pressure{v: 1020.9}},
{"No data fields", "none", nil},
}
c := New(withMockAPI())
@ -954,14 +954,14 @@ func TestClient_ObservationLatestByStationID_GlobalRadiation10m(t *testing.T) {
// Station ID
sid string
// Observation radiation
p *ObservationRadiation
p *Radiation
}{
{"K-Botanischer Garten", "199942", &ObservationRadiation{
{"K-Botanischer Garten", "199942", &Radiation{
dt: time.Date(2023, 0o5, 15, 20, 10, 0, 0, time.UTC),
v: 0,
}},
{"K-Stammheim", "H744", nil},
{"All data fields", "all", &ObservationRadiation{
{"All data fields", "all", &Radiation{
dt: time.Date(2023, 0o5, 17, 7, 40, 0, 0, time.UTC),
v: 62,
}},
@ -1013,11 +1013,11 @@ func TestClient_ObservationLatestByStationID_GlobalRadiation1h(t *testing.T) {
// Station ID
sid string
// Observation radiation
p *ObservationRadiation
p *Radiation
}{
{"K-Botanischer Garten", "199942", &ObservationRadiation{v: 0}},
{"K-Botanischer Garten", "199942", &Radiation{v: 0}},
{"K-Stammheim", "H744", nil},
{"All data fields", "all", &ObservationRadiation{v: 200}},
{"All data fields", "all", &Radiation{v: 200}},
{"No data fields", "none", nil},
}
c := New(withMockAPI())
@ -1062,11 +1062,11 @@ func TestClient_ObservationLatestByStationID_GlobalRadiation24h(t *testing.T) {
// Station ID
sid string
// Observation radiation
p *ObservationRadiation
p *Radiation
}{
{"K-Botanischer Garten", "199942", &ObservationRadiation{v: 774}},
{"K-Botanischer Garten", "199942", &Radiation{v: 774}},
{"K-Stammheim", "H744", nil},
{"All data fields", "all", &ObservationRadiation{v: 756}},
{"All data fields", "all", &Radiation{v: 756}},
{"No data fields", "none", nil},
}
c := New(withMockAPI())
@ -1111,11 +1111,11 @@ func TestClient_ObservationLatestByStationID_Winddirection(t *testing.T) {
// Station ID
sid string
// Observation dewpoint
p *ObservationDirection
p *Direction
}{
{"K-Botanischer Garten", "199942", nil},
{"K-Stammheim", "H744", nil},
{"All data fields", "all", &ObservationDirection{
{"All data fields", "all", &Direction{
dt: time.Date(2023, 0o5, 21, 11, 30, 0, 0, time.UTC),
v: 90,
}},
@ -1166,11 +1166,11 @@ func TestClient_ObservationLatestByStationID_Windspeed(t *testing.T) {
// Station ID
sid string
// Observation dewpoint
p *ObservationSpeed
p *Speed
}{
{"K-Botanischer Garten", "199942", nil},
{"K-Stammheim", "H744", nil},
{"All data fields", "all", &ObservationSpeed{
{"All data fields", "all", &Speed{
dt: time.Date(2023, 0o5, 21, 11, 30, 0, 0, time.UTC),
v: 15,
}},
@ -1257,21 +1257,21 @@ func TestObservationTemperature_String(t *testing.T) {
ff := "%.1f°F"
for _, tc := range tt {
t.Run(fmt.Sprintf("%.2f°C", tc.c), func(t *testing.T) {
ot := ObservationTemperature{v: tc.c}
ot := Temperature{v: tc.c}
if ot.Celsius() != tc.c {
t.Errorf("ObservationTemperature.Celsius failed, expected: %f, got: %f", tc.c,
t.Errorf("Temperature.Celsius failed, expected: %f, got: %f", tc.c,
ot.Celsius())
}
if ot.CelsiusString() != fmt.Sprintf(cf, tc.c) {
t.Errorf("ObservationTemperature.CelsiusString failed, expected: %s, got: %s",
t.Errorf("Temperature.CelsiusString failed, expected: %s, got: %s",
fmt.Sprintf(cf, tc.c), ot.CelsiusString())
}
if ot.Fahrenheit() != tc.f {
t.Errorf("ObservationTemperature.Fahrenheit failed, expected: %f, got: %f", tc.f,
t.Errorf("Temperature.Fahrenheit failed, expected: %f, got: %f", tc.f,
ot.Fahrenheit())
}
if ot.FahrenheitString() != fmt.Sprintf(ff, tc.f) {
t.Errorf("ObservationTemperature.FahrenheitString failed, expected: %s, got: %s",
t.Errorf("Temperature.FahrenheitString failed, expected: %s, got: %s",
fmt.Sprintf(ff, tc.f), ot.FahrenheitString())
}
})
@ -1298,29 +1298,29 @@ func TestObservationSpeed_Conversion(t *testing.T) {
mphf := "%.1fmi/h"
for _, tc := range tt {
t.Run(fmt.Sprintf("%.0fkn", tc.kn), func(t *testing.T) {
os := ObservationSpeed{v: tc.kn}
os := Speed{v: tc.kn}
if os.Value() != tc.kn {
t.Errorf("ObservationSpeed.Value failed, expected: %f, got: %f", tc.kn,
t.Errorf("Speed.Value failed, expected: %f, got: %f", tc.kn,
os.Value())
}
if os.String() != fmt.Sprintf(knf, tc.kn) {
t.Errorf("ObservationSpeed.String failed, expected: %s, got: %s",
t.Errorf("Speed.String failed, expected: %s, got: %s",
fmt.Sprintf(knf, tc.kn), os.String())
}
if os.KMH() != tc.kmh {
t.Errorf("ObservationSpeed.KMH failed, expected: %f, got: %f", tc.kmh,
t.Errorf("Speed.KMH failed, expected: %f, got: %f", tc.kmh,
os.KMH())
}
if os.KMHString() != fmt.Sprintf(kmhf, tc.kmh) {
t.Errorf("ObservationSpeed.KMHString failed, expected: %s, got: %s",
t.Errorf("Speed.KMHString failed, expected: %s, got: %s",
fmt.Sprintf(kmhf, tc.kmh), os.KMHString())
}
if os.MPH() != tc.mph {
t.Errorf("ObservationSpeed.MPH failed, expected: %f, got: %f", tc.mph,
t.Errorf("Speed.MPH failed, expected: %f, got: %f", tc.mph,
os.MPH())
}
if os.MPHString() != fmt.Sprintf(mphf, tc.mph) {
t.Errorf("ObservationSpeed.MPHString failed, expected: %s, got: %s",
t.Errorf("Speed.MPHString failed, expected: %s, got: %s",
fmt.Sprintf(mphf, tc.mph), os.MPHString())
}
})
@ -1370,9 +1370,9 @@ func TestObservationDirection_Direction(t *testing.T) {
}
for _, tc := range tt {
t.Run(fmt.Sprintf("%.2f° => %s", tc.d, tc.ds), func(t *testing.T) {
d := ObservationDirection{v: tc.d}
d := Direction{v: tc.d}
if d.Direction() != tc.ds {
t.Errorf("ObservationDirection.Direction failed, expected: %s, got: %s",
t.Errorf("Direction.Direction failed, expected: %s, got: %s",
tc.ds, d.Direction())
}
})
@ -1422,9 +1422,9 @@ func TestObservationDirection_DirectionFull(t *testing.T) {
}
for _, tc := range tt {
t.Run(fmt.Sprintf("%.2f° => %s", tc.d, tc.ds), func(t *testing.T) {
d := ObservationDirection{v: tc.d}
d := Direction{v: tc.d}
if d.DirectionFull() != tc.ds {
t.Errorf("ObservationDirection.Direction failed, expected: %s, got: %s",
t.Errorf("Direction.Direction failed, expected: %s, got: %s",
tc.ds, d.DirectionFull())
}
})

42
precipitation.go Normal file
View file

@ -0,0 +1,42 @@
// SPDX-FileCopyrightText: 2023 Winni Neessen <wn@neessen.dev>
//
// SPDX-License-Identifier: MIT
package meteologix
import (
"fmt"
"math"
"time"
)
// Precipitation is a type wrapper of an WeatherData for holding precipitation
// values in WeatherData
type Precipitation WeatherData
// IsAvailable returns true if an Precipitation value was
// available at time of query
func (t Precipitation) IsAvailable() bool {
return !t.na
}
// DateTime returns true if an Precipitation value was
// available at time of query
func (t Precipitation) DateTime() time.Time {
return t.dt
}
// String satisfies the fmt.Stringer interface for the Precipitation type
func (t Precipitation) String() string {
return fmt.Sprintf("%.1fmm", t.v)
}
// Value returns the float64 value of an Precipitation
// If the Precipitation is not available in the Observation
// Vaule will return math.NaN instead.
func (t Precipitation) Value() float64 {
if t.na {
return math.NaN()
}
return t.v
}

42
pressure.go Normal file
View file

@ -0,0 +1,42 @@
// SPDX-FileCopyrightText: 2023 Winni Neessen <wn@neessen.dev>
//
// SPDX-License-Identifier: MIT
package meteologix
import (
"fmt"
"math"
"time"
)
// Pressure is a type wrapper of an WeatherData for holding pressure
// values in WeatherData
type Pressure WeatherData
// IsAvailable returns true if an Pressure value was
// available at time of query
func (t Pressure) IsAvailable() bool {
return !t.na
}
// DateTime returns true if an Pressure value was
// available at time of query
func (t Pressure) DateTime() time.Time {
return t.dt
}
// String satisfies the fmt.Stringer interface for the Pressure type
func (t Pressure) String() string {
return fmt.Sprintf("%.1fhPa", t.v)
}
// Value returns the float64 value of an Pressure
// If the Pressure is not available in the Observation
// Vaule will return math.NaN instead.
func (t Pressure) Value() float64 {
if t.na {
return math.NaN()
}
return t.v
}

42
radiation.go Normal file
View file

@ -0,0 +1,42 @@
// SPDX-FileCopyrightText: 2023 Winni Neessen <wn@neessen.dev>
//
// SPDX-License-Identifier: MIT
package meteologix
import (
"fmt"
"math"
"time"
)
// Radiation is a type wrapper of an WeatherData for holding radiation
// values in WeatherData
type Radiation WeatherData
// IsAvailable returns true if an Radiation value was
// available at time of query
func (t Radiation) IsAvailable() bool {
return !t.na
}
// DateTime returns true if an Radiation value was
// available at time of query
func (t Radiation) DateTime() time.Time {
return t.dt
}
// Value returns the float64 value of an Radiation
// If the Radiation is not available in the Observation
// Vaule will return math.NaN instead.
func (t Radiation) Value() float64 {
if t.na {
return math.NaN()
}
return t.v
}
// String satisfies the fmt.Stringer interface for the Radiation type
func (t Radiation) String() string {
return fmt.Sprintf("%.0fkJ/m²", t.v)
}

54
source.go Normal file
View file

@ -0,0 +1,54 @@
package meteologix
import "strings"
// Enum of different weather data sources
const (
// SourceObservation represent observations from weather stations (high precision)
SourceObservation = iota
// SourceAnalysis represents weather data based on analysis (medium precision)
SourceAnalysis
// SourceForecast represents weather data based on weather forcecasts
SourceForecast
// SourceMixed represents weather data based on mixed sources
SourceMixed
// SourceUnknown represents weather data based on unknown sources
SourceUnknown
)
// Source is a type wrapper for an int type to enum different weather sources
type Source int
// String satisfies the fmt.Stringer interface for the Source type
func (s Source) String() string {
switch s {
case SourceObservation:
return "Observation"
case SourceAnalysis:
return "Analysis"
case SourceForecast:
return "Forecast"
case SourceMixed:
return "Mixed"
case SourceUnknown:
return "Unknown"
default:
return "Unknown"
}
}
// StringToSource converts a given source string to a Source type
func StringToSource(s string) Source {
switch strings.ToLower(s) {
case "observation":
return SourceObservation
case "analysis":
return SourceAnalysis
case "forecast":
return SourceForecast
case "mixed":
return SourceMixed
default:
return SourceUnknown
}
}

62
speed.go Normal file
View file

@ -0,0 +1,62 @@
// SPDX-FileCopyrightText: 2023 Winni Neessen <wn@neessen.dev>
//
// SPDX-License-Identifier: MIT
package meteologix
import (
"fmt"
"math"
"time"
)
// Speed is a type wrapper of an WeatherData for holding speed
// values in WeatherData
type Speed WeatherData
// IsAvailable returns true if an Speed value was
// available at time of query
func (t Speed) IsAvailable() bool {
return !t.na
}
// DateTime returns true if an Speed value was
// available at time of query
func (t Speed) DateTime() time.Time {
return t.dt
}
// Value returns the float64 value of an Speed in knots
// If the Speed is not available in the Observation
// Vaule will return math.NaN instead.
func (t Speed) Value() float64 {
if t.na {
return math.NaN()
}
return t.v
}
// String satisfies the fmt.Stringer interface for the Speed type
func (t Speed) String() string {
return fmt.Sprintf("%.0fkn", t.v)
}
// KMH returns the Speed value in km/h
func (t Speed) KMH() float64 {
return t.v * 1.852
}
// KMHString returns the Speed value as formatted string in km/h
func (t Speed) KMHString() string {
return fmt.Sprintf("%.1fkm/h", t.KMH())
}
// MPH returns the Speed value in mi/h
func (t Speed) MPH() float64 {
return t.v * 1.151
}
// MPHString returns the Speed value as formatted string in mi/h
func (t Speed) MPHString() string {
return fmt.Sprintf("%.1fmi/h", t.MPH())
}

72
temperature.go Normal file
View file

@ -0,0 +1,72 @@
// SPDX-FileCopyrightText: 2023 Winni Neessen <wn@neessen.dev>
//
// SPDX-License-Identifier: MIT
package meteologix
import (
"fmt"
"math"
"time"
)
// Temperature is a type wrapper of an WeatherData for holding temperature
// values in WeatherData
type Temperature WeatherData
// IsAvailable returns true if an Temperature value was
// available at time of query
func (t Temperature) IsAvailable() bool {
return !t.na
}
// DateTime returns true if an Temperature value was
// available at time of query
func (t Temperature) DateTime() time.Time {
return t.dt
}
// Value returns the float64 value of an Temperature
// If the Temperature is not available in the Observation
// Vaule will return math.NaN instead.
func (t Temperature) Value() float64 {
if t.na {
return math.NaN()
}
return t.v
}
// Source returns the Source of an Temperature
// If the Source is not available it will return SourceUnknown
func (t Temperature) Source() Source {
return t.s
}
// String satisfies the fmt.Stringer interface for the Temperature type
func (t Temperature) String() string {
return fmt.Sprintf("%.1f°C", t.v)
}
// Celsius returns the Temperature value in Celsius
func (t Temperature) Celsius() float64 {
return t.v
}
// CelsiusString returns the Temperature value as Celsius
// formated string.
//
// This is an alias for the fmt.Stringer interface
func (t Temperature) CelsiusString() string {
return t.String()
}
// Fahrenheit returns the Temperature value in Fahrenheit
func (t Temperature) Fahrenheit() float64 {
return t.v*9/5 + 32
}
// FahrenheitString returns the Temperature value as Fahrenheit
// formated string.
func (t Temperature) FahrenheitString() string {
return fmt.Sprintf("%.1f°F", t.Fahrenheit())
}