go-meteologix/curweather.go
Winni Neessen d7567d4b2b
Remove unused and redundant weather fields
Removed several unused and redundant weather fields like DewPointMean, GlobalRadiation, TemperatureMean, Temperature5cm, etc. from curweather.go to reduce complexity and improve readability.
2023-06-27 16:12:38 +02:00

362 lines
11 KiB
Go

// SPDX-FileCopyrightText: 2023 Winni Neessen <wn@neessen.dev>
//
// SPDX-License-Identifier: MIT
package meteologix
import (
"encoding/json"
"fmt"
"net/url"
"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"`
// 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 pressure at mean sea level (MSL) in hPa
PressureMSL *APIFloat `json:"pressureMsl,omitempty"`
// PressureQFE represents the pressure at station level (QFE) in hPa
PressureQFE *APIFloat `json:"pressure,omitempty"`
// 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"`
// Temperature represents the temperature in °C
Temperature *APIFloat `json:"temp,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"`
// 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"`
}
// CurrentWeatherByCoordinates returns the CurrentWeather values for the given coordinates
func (c *Client) CurrentWeatherByCoordinates(la, lo float64) (CurrentWeather, error) {
var cw CurrentWeather
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
}
// 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
}
// 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
}
// 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
// at this point.
func (cw CurrentWeather) Precipitation(ts Timespan) Precipitation {
var df *APIFloat
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,
}
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
}
// 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.
// 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
// as Condition.
// If the data point is not available in the CurrentWeather it will return
// Condition in which the "not available" field will be true.
func (cw CurrentWeather) WeatherSymbol() Condition {
if cw.Data.WeatherSymbol == nil {
return Condition{na: true}
}
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 {
if cw.Data.WindDirection == nil {
return Direction{na: true}
}
v := Direction{
dt: cw.Data.WindDirection.DateTime,
n: FieldWindDirection,
s: SourceUnknown,
fv: cw.Data.WindDirection.Value,
}
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 {
if cw.Data.WindSpeed == nil {
return Speed{na: true}
}
v := Speed{
dt: cw.Data.WindSpeed.DateTime,
n: FieldWindSpeed,
s: SourceUnknown,
fv: cw.Data.WindSpeed.Value,
}
if cw.Data.WindSpeed.Source != nil {
v.s = StringToSource(*cw.Data.WindSpeed.Source)
}
return v
}