Merge pull request #9 from wneessen/more-curweather
v0.1.0: More data points for current weather
This commit is contained in:
commit
7bdf2de388
163
curweather.go
163
curweather.go
|
@ -34,23 +34,31 @@ type APICurrentWeatherData struct {
|
|||
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"`
|
||||
Precipitation *APIFloat `json:"prec,omitempty"`
|
||||
// Precipitation10m represents the amount of precipitation over the last 10 minutes
|
||||
Precipitation10m *APIFloat `json:"prec10m"`
|
||||
Precipitation10m *APIFloat `json:"prec10m,omitempty"`
|
||||
// Precipitation1h represents the amount of precipitation over the last hour
|
||||
Precipitation1h *APIFloat `json:"prec1h"`
|
||||
Precipitation1h *APIFloat `json:"prec1h,omitempty"`
|
||||
// Precipitation24h represents the amount of precipitation over the last 24 hours
|
||||
Precipitation24h *APIFloat `json:"prec24h"`
|
||||
Precipitation24h *APIFloat `json:"prec24h,omitempty"`
|
||||
// PressureMSL represents the pressure at mean sea level (MSL) in hPa
|
||||
PressureMSL *APIFloat `json:"pressureMsl"`
|
||||
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"`
|
||||
// Temperature represents the temperature in °C
|
||||
Temperature *APIFloat `json:"temp,omitempty"`
|
||||
// Windspeed represents the wind speed in knots
|
||||
Windspeed *APIFloat `json:"windSpeed,omitempty"`
|
||||
// Winddirection represents the direction from which the wind
|
||||
// WindDirection represents the direction from which the wind
|
||||
// originates in degree (0=N, 90=E, 180=S, 270=W)
|
||||
Winddirection *APIFloat `json:"windDirection,omitempty"`
|
||||
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"`
|
||||
|
@ -66,8 +74,6 @@ type APICurrentWeatherData struct {
|
|||
// GlobalRadiation24h represents the sum of global radiation over the last
|
||||
// 24 hour in kJ/m²
|
||||
GlobalRadiation24h *APIFloat `json:"globalRadiation24h,omitempty"`
|
||||
// PressureMSL represents the pressure at station level (QFE) in hPa
|
||||
PressureQFE *APIFloat `json:"pressure"`
|
||||
// TemperatureMax represents the maximum temperature in °C
|
||||
TemperatureMax *APIFloat `json:"tempMax,omitempty"`
|
||||
// TemperatureMean represents the mean temperature in °C
|
||||
|
@ -117,25 +123,6 @@ func (c *Client) CurrentWeatherByLocation(lo string) (CurrentWeather, error) {
|
|||
return c.CurrentWeatherByCoordinates(gl.Latitude, gl.Longitude)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
@ -174,6 +161,14 @@ func (cw CurrentWeather) HumidityRelative() Humidity {
|
|||
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.
|
||||
|
@ -235,6 +230,63 @@ func (cw CurrentWeather) PressureMSL() Pressure {
|
|||
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 temperature 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
|
||||
}
|
||||
|
||||
// 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
|
||||
|
@ -255,40 +307,59 @@ func (cw CurrentWeather) WeatherSymbol() Condition {
|
|||
return v
|
||||
}
|
||||
|
||||
// Winddirection returns the wind direction data point as Direction.
|
||||
// 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 {
|
||||
func (cw CurrentWeather) WindDirection() Direction {
|
||||
if cw.Data.WindDirection == nil {
|
||||
return Direction{na: true}
|
||||
}
|
||||
v := Direction{
|
||||
dt: cw.Data.Winddirection.DateTime,
|
||||
n: FieldWinddirection,
|
||||
dt: cw.Data.WindDirection.DateTime,
|
||||
n: FieldWindDirection,
|
||||
s: SourceUnknown,
|
||||
fv: cw.Data.Winddirection.Value,
|
||||
fv: cw.Data.WindDirection.Value,
|
||||
}
|
||||
if cw.Data.Winddirection.Source != nil {
|
||||
v.s = StringToSource(*cw.Data.Winddirection.Source)
|
||||
if cw.Data.WindDirection.Source != nil {
|
||||
v.s = StringToSource(*cw.Data.WindDirection.Source)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// Windspeed returns the average wind speed data point as Speed.
|
||||
// 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) Windspeed() Speed {
|
||||
if cw.Data.Windspeed == nil {
|
||||
func (cw CurrentWeather) WindGust() Speed {
|
||||
if cw.Data.WindGust == nil {
|
||||
return Speed{na: true}
|
||||
}
|
||||
v := Speed{
|
||||
dt: cw.Data.Windspeed.DateTime,
|
||||
n: FieldWindspeed,
|
||||
dt: cw.Data.WindGust.DateTime,
|
||||
n: FieldWindGust,
|
||||
s: SourceUnknown,
|
||||
fv: cw.Data.Windspeed.Value,
|
||||
fv: cw.Data.WindGust.Value,
|
||||
}
|
||||
if cw.Data.Windspeed.Source != nil {
|
||||
v.s = StringToSource(*cw.Data.Windspeed.Source)
|
||||
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
|
||||
}
|
||||
|
|
|
@ -107,63 +107,6 @@ func TestClient_CurrentWeatherByLocation_Fail(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestClient_CurrentWeatherByLocation_Temperature(t *testing.T) {
|
||||
tt := []struct {
|
||||
// Location name
|
||||
loc string
|
||||
// CurWeather temperature
|
||||
t *Temperature
|
||||
}{
|
||||
{"Ehrenfeld, Germany", &Temperature{
|
||||
dt: time.Date(2023, 5, 23, 7, 0, 0, 0, time.Local),
|
||||
s: SourceObservation,
|
||||
fv: 14.6,
|
||||
}},
|
||||
{"Berlin, Germany", &Temperature{
|
||||
dt: time.Date(2023, 5, 23, 7, 0, 0, 0, time.Local),
|
||||
s: SourceAnalysis,
|
||||
fv: 17.8,
|
||||
}},
|
||||
{"Neermoor, Germany", nil},
|
||||
}
|
||||
c := New(withMockAPI())
|
||||
if c == nil {
|
||||
t.Errorf("failed to create new Client, got nil")
|
||||
return
|
||||
}
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.loc, func(t *testing.T) {
|
||||
cw, err := c.CurrentWeatherByLocation(tc.loc)
|
||||
if err != nil {
|
||||
t.Errorf("CurrentWeatherByLocation failed: %s", err)
|
||||
return
|
||||
}
|
||||
if tc.t != nil && tc.t.String() != cw.Temperature().String() {
|
||||
t.Errorf("CurrentWeatherByLocation failed, expected temperature "+
|
||||
"string: %s, got: %s", tc.t.String(), cw.Temperature())
|
||||
}
|
||||
if tc.t != nil && tc.t.Value() != cw.Temperature().Value() {
|
||||
t.Errorf("CurrentWeatherByLocation failed, expected temperature "+
|
||||
"float: %f, got: %f", tc.t.Value(), cw.Temperature().Value())
|
||||
}
|
||||
if tc.t != nil && cw.Temperature().Source() != tc.t.s {
|
||||
t.Errorf("CurrentWeatherByLocation failed, expected source: %s, but got: %s",
|
||||
tc.t.s, cw.Temperature().Source())
|
||||
}
|
||||
if tc.t == nil {
|
||||
if cw.Temperature().IsAvailable() {
|
||||
t.Errorf("CurrentWeatherByLocation failed, expected temperature "+
|
||||
"to have no data, but got: %s", cw.Temperature())
|
||||
}
|
||||
if !math.IsNaN(cw.Temperature().Value()) {
|
||||
t.Errorf("CurrentWeatherByLocation failed, expected temperature "+
|
||||
"to return NaN, but got: %s", cw.Temperature().String())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_CurrentWeatherByLocation_Dewpoint(t *testing.T) {
|
||||
tt := []struct {
|
||||
// Location name
|
||||
|
@ -278,6 +221,36 @@ func TestClient_CurrentWeatherByLocation_HumidityRelative(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestClient_CurrentWeatherByLocation_IsDay(t *testing.T) {
|
||||
tt := []struct {
|
||||
// Location name
|
||||
loc string
|
||||
// CurWeather IsDay
|
||||
d bool
|
||||
}{
|
||||
{"Ehrenfeld, Germany", false},
|
||||
{"Berlin, Germany", true},
|
||||
{"Neermoor, Germany", false},
|
||||
}
|
||||
c := New(withMockAPI())
|
||||
if c == nil {
|
||||
t.Errorf("failed to create new Client, got nil")
|
||||
return
|
||||
}
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.loc, func(t *testing.T) {
|
||||
cw, err := c.CurrentWeatherByLocation(tc.loc)
|
||||
if err != nil {
|
||||
t.Errorf("CurrentWeatherByLocation failed: %s", err)
|
||||
return
|
||||
}
|
||||
if cw.IsDay() != tc.d {
|
||||
t.Errorf("CurrentWeather IsDay failed, expected: %t, got: %t", cw.IsDay(), tc.d)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_CurrentWeatherByLocation_PrecipitationCurrent(t *testing.T) {
|
||||
tt := []struct {
|
||||
// Location name
|
||||
|
@ -539,28 +512,20 @@ func TestClient_CurrentWeatherByLocation_PressureMSL(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestClient_CurrentWeatherByLocation_Winddirection(t *testing.T) {
|
||||
func TestClient_CurrentWeatherByLocation_PressureQFE(t *testing.T) {
|
||||
tt := []struct {
|
||||
// Location name
|
||||
loc string
|
||||
// CurWeather direction
|
||||
d *Direction
|
||||
// Direction abbr. string
|
||||
da string
|
||||
// Direction full string
|
||||
df string
|
||||
// CurWeather pressure
|
||||
p *Pressure
|
||||
}{
|
||||
{"Ehrenfeld, Germany", &Direction{
|
||||
{"Ehrenfeld, Germany", &Pressure{
|
||||
dt: time.Date(2023, 5, 23, 7, 0, 0, 0, time.Local),
|
||||
s: SourceAnalysis,
|
||||
fv: 302,
|
||||
}, "NWbW", "Northwest by West"},
|
||||
{"Berlin, Germany", &Direction{
|
||||
dt: time.Date(2023, 5, 23, 7, 0, 0, 0, time.Local),
|
||||
s: SourceAnalysis,
|
||||
fv: 286,
|
||||
}, "WbN", "West by North"},
|
||||
{"Neermoor, Germany", nil, "", ""},
|
||||
fv: 1011.7,
|
||||
}},
|
||||
{"Berlin, Germany", nil},
|
||||
{"Neermoor, Germany", nil},
|
||||
}
|
||||
c := New(withMockAPI())
|
||||
if c == nil {
|
||||
|
@ -574,56 +539,48 @@ func TestClient_CurrentWeatherByLocation_Winddirection(t *testing.T) {
|
|||
t.Errorf("CurrentWeatherByLocation failed: %s", err)
|
||||
return
|
||||
}
|
||||
if tc.d != nil && tc.d.String() != cw.Winddirection().String() {
|
||||
t.Errorf("CurrentWeatherByLocation failed, expected wind direction "+
|
||||
"string: %s, got: %s", tc.d.String(), cw.Winddirection())
|
||||
if tc.p != nil && tc.p.String() != cw.PressureQFE().String() {
|
||||
t.Errorf("CurrentWeatherByLocation failed, expected pressure "+
|
||||
"string: %s, got: %s", tc.p.String(), cw.PressureQFE())
|
||||
}
|
||||
if tc.d != nil && tc.d.Value() != cw.Winddirection().Value() {
|
||||
t.Errorf("CurrentWeatherByLocation failed, expected wind direction "+
|
||||
"float: %f, got: %f", tc.d.Value(), cw.Winddirection().Value())
|
||||
if tc.p != nil && tc.p.Value() != cw.PressureQFE().Value() {
|
||||
t.Errorf("CurrentWeatherByLocation failed, expected pressure "+
|
||||
"float: %f, got: %f", tc.p.Value(), cw.PressureQFE().Value())
|
||||
}
|
||||
if tc.d != nil && cw.Winddirection().Source() != tc.d.s {
|
||||
if tc.p != nil && cw.PressureQFE().Source() != tc.p.s {
|
||||
t.Errorf("CurrentWeatherByLocation failed, expected source: %s, but got: %s",
|
||||
tc.d.s, cw.Winddirection().Source())
|
||||
tc.p.s, cw.PressureQFE().Source())
|
||||
}
|
||||
if tc.d != nil && cw.Winddirection().Direction() != tc.da {
|
||||
t.Errorf("CurrentWeatherByLocation failed, expected direction abbr.: %s, but got: %s",
|
||||
tc.da, cw.Winddirection().Direction())
|
||||
}
|
||||
if tc.d != nil && cw.Winddirection().DirectionFull() != tc.df {
|
||||
t.Errorf("CurrentWeatherByLocation failed, expected direction full: %s, but got: %s",
|
||||
tc.df, cw.Winddirection().DirectionFull())
|
||||
}
|
||||
if tc.d == nil {
|
||||
if cw.Winddirection().IsAvailable() {
|
||||
t.Errorf("CurrentWeatherByLocation failed, expected wind direction "+
|
||||
"to have no data, but got: %s", cw.Winddirection())
|
||||
if tc.p == nil {
|
||||
if cw.PressureQFE().IsAvailable() {
|
||||
t.Errorf("CurrentWeatherByLocation failed, expected pressure "+
|
||||
"to have no data, but got: %s", cw.PressureQFE())
|
||||
}
|
||||
if !math.IsNaN(cw.Winddirection().Value()) {
|
||||
t.Errorf("CurrentWeatherByLocation failed, expected wind direction "+
|
||||
"to return NaN, but got: %s", cw.Windspeed().String())
|
||||
if !math.IsNaN(cw.PressureQFE().Value()) {
|
||||
t.Errorf("CurrentWeatherByLocation failed, expected pressure "+
|
||||
"to return NaN, but got: %s", cw.PressureQFE().String())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_CurrentWeatherByLocation_Windspeed(t *testing.T) {
|
||||
func TestClient_CurrentWeatherByLocation_SnowAmount(t *testing.T) {
|
||||
tt := []struct {
|
||||
// Location name
|
||||
loc string
|
||||
// CurWeather speed
|
||||
s *Speed
|
||||
// CurWeather pressure
|
||||
d *Density
|
||||
}{
|
||||
{"Ehrenfeld, Germany", &Speed{
|
||||
dt: time.Date(2023, 5, 23, 7, 0, 0, 0, time.Local),
|
||||
{"Ehrenfeld, Germany", &Density{
|
||||
dt: time.Date(2023, 5, 23, 6, 0, 0, 0, time.UTC),
|
||||
s: SourceAnalysis,
|
||||
fv: 3.94,
|
||||
fv: 0,
|
||||
}},
|
||||
{"Berlin, Germany", &Speed{
|
||||
dt: time.Date(2023, 5, 23, 7, 0, 0, 0, time.Local),
|
||||
{"Berlin, Germany", &Density{
|
||||
dt: time.Date(2023, 5, 23, 8, 0, 0, 0, time.UTC),
|
||||
s: SourceAnalysis,
|
||||
fv: 3.19,
|
||||
fv: 21.1,
|
||||
}},
|
||||
{"Neermoor, Germany", nil},
|
||||
}
|
||||
|
@ -639,26 +596,87 @@ func TestClient_CurrentWeatherByLocation_Windspeed(t *testing.T) {
|
|||
t.Errorf("CurrentWeatherByLocation failed: %s", err)
|
||||
return
|
||||
}
|
||||
if tc.s != nil && tc.s.String() != cw.Windspeed().String() {
|
||||
t.Errorf("CurrentWeatherByLocation failed, expected wind speed "+
|
||||
"string: %s, got: %s", tc.s.String(), cw.Windspeed())
|
||||
if tc.d != nil && tc.d.String() != cw.SnowAmount().String() {
|
||||
t.Errorf("CurrentWeatherByLocation failed, expected snow amount "+
|
||||
"string: %s, got: %s", tc.d.String(), cw.SnowAmount())
|
||||
}
|
||||
if tc.s != nil && tc.s.Value() != cw.Windspeed().Value() {
|
||||
t.Errorf("CurrentWeatherByLocation failed, expected wind speed "+
|
||||
"float: %f, got: %f", tc.s.Value(), cw.Windspeed().Value())
|
||||
if tc.d != nil && tc.d.Value() != cw.SnowAmount().Value() {
|
||||
t.Errorf("CurrentWeatherByLocation failed, expected snow amount "+
|
||||
"float: %f, got: %f", tc.d.Value(), cw.SnowAmount().Value())
|
||||
}
|
||||
if tc.s != nil && cw.Windspeed().Source() != tc.s.s {
|
||||
if tc.d != nil && cw.SnowAmount().Source() != tc.d.s {
|
||||
t.Errorf("CurrentWeatherByLocation failed, expected source: %s, but got: %s",
|
||||
tc.s.s, cw.Windspeed().Source())
|
||||
tc.d.s, cw.SnowAmount().Source())
|
||||
}
|
||||
if tc.s == nil {
|
||||
if cw.Windspeed().IsAvailable() {
|
||||
t.Errorf("CurrentWeatherByLocation failed, expected wind speed "+
|
||||
"to have no data, but got: %s", cw.Windspeed())
|
||||
if tc.d != nil && tc.d.dt.Unix() != cw.SnowAmount().DateTime().Unix() {
|
||||
t.Errorf("CurrentWeatherByLocation failed, expected datetime: %s, got: %s",
|
||||
tc.d.dt.Format(time.RFC3339), cw.SnowAmount().DateTime().Format(time.RFC3339))
|
||||
}
|
||||
if tc.d == nil {
|
||||
if cw.SnowAmount().IsAvailable() {
|
||||
t.Errorf("CurrentWeatherByLocation failed, expected snow amount "+
|
||||
"to have no data, but got: %s", cw.SnowAmount())
|
||||
}
|
||||
if !math.IsNaN(cw.Windspeed().Value()) {
|
||||
t.Errorf("CurrentWeatherByLocation failed, expected wind speed "+
|
||||
"to return NaN, but got: %s", cw.Windspeed().String())
|
||||
if !math.IsNaN(cw.SnowAmount().Value()) {
|
||||
t.Errorf("CurrentWeatherByLocation failed, expected snow amount "+
|
||||
"to return NaN, but got: %s", cw.SnowAmount().String())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_CurrentWeatherByLocation_Temperature(t *testing.T) {
|
||||
tt := []struct {
|
||||
// Location name
|
||||
loc string
|
||||
// CurWeather temperature
|
||||
t *Temperature
|
||||
}{
|
||||
{"Ehrenfeld, Germany", &Temperature{
|
||||
dt: time.Date(2023, 5, 23, 7, 0, 0, 0, time.Local),
|
||||
s: SourceObservation,
|
||||
fv: 14.6,
|
||||
}},
|
||||
{"Berlin, Germany", &Temperature{
|
||||
dt: time.Date(2023, 5, 23, 7, 0, 0, 0, time.Local),
|
||||
s: SourceAnalysis,
|
||||
fv: 17.8,
|
||||
}},
|
||||
{"Neermoor, Germany", nil},
|
||||
}
|
||||
c := New(withMockAPI())
|
||||
if c == nil {
|
||||
t.Errorf("failed to create new Client, got nil")
|
||||
return
|
||||
}
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.loc, func(t *testing.T) {
|
||||
cw, err := c.CurrentWeatherByLocation(tc.loc)
|
||||
if err != nil {
|
||||
t.Errorf("CurrentWeatherByLocation failed: %s", err)
|
||||
return
|
||||
}
|
||||
if tc.t != nil && tc.t.String() != cw.Temperature().String() {
|
||||
t.Errorf("CurrentWeatherByLocation failed, expected temperature "+
|
||||
"string: %s, got: %s", tc.t.String(), cw.Temperature())
|
||||
}
|
||||
if tc.t != nil && tc.t.Value() != cw.Temperature().Value() {
|
||||
t.Errorf("CurrentWeatherByLocation failed, expected temperature "+
|
||||
"float: %f, got: %f", tc.t.Value(), cw.Temperature().Value())
|
||||
}
|
||||
if tc.t != nil && cw.Temperature().Source() != tc.t.s {
|
||||
t.Errorf("CurrentWeatherByLocation failed, expected source: %s, but got: %s",
|
||||
tc.t.s, cw.Temperature().Source())
|
||||
}
|
||||
if tc.t == nil {
|
||||
if cw.Temperature().IsAvailable() {
|
||||
t.Errorf("CurrentWeatherByLocation failed, expected temperature "+
|
||||
"to have no data, but got: %s", cw.Temperature())
|
||||
}
|
||||
if !math.IsNaN(cw.Temperature().Value()) {
|
||||
t.Errorf("CurrentWeatherByLocation failed, expected temperature "+
|
||||
"to return NaN, but got: %s", cw.Temperature().String())
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -729,3 +747,186 @@ func TestClient_CurrentWeatherByLocation_WeatherSymbol(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_CurrentWeatherByLocation_WindDirection(t *testing.T) {
|
||||
tt := []struct {
|
||||
// Location name
|
||||
loc string
|
||||
// CurWeather direction
|
||||
d *Direction
|
||||
// Direction abbr. string
|
||||
da string
|
||||
// Direction full string
|
||||
df string
|
||||
}{
|
||||
{"Ehrenfeld, Germany", &Direction{
|
||||
dt: time.Date(2023, 5, 23, 7, 0, 0, 0, time.Local),
|
||||
s: SourceAnalysis,
|
||||
fv: 302,
|
||||
}, "NWbW", "Northwest by West"},
|
||||
{"Berlin, Germany", &Direction{
|
||||
dt: time.Date(2023, 5, 23, 7, 0, 0, 0, time.Local),
|
||||
s: SourceAnalysis,
|
||||
fv: 286,
|
||||
}, "WbN", "West by North"},
|
||||
{"Neermoor, Germany", nil, "", ""},
|
||||
}
|
||||
c := New(withMockAPI())
|
||||
if c == nil {
|
||||
t.Errorf("failed to create new Client, got nil")
|
||||
return
|
||||
}
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.loc, func(t *testing.T) {
|
||||
cw, err := c.CurrentWeatherByLocation(tc.loc)
|
||||
if err != nil {
|
||||
t.Errorf("CurrentWeatherByLocation failed: %s", err)
|
||||
return
|
||||
}
|
||||
if tc.d != nil && tc.d.String() != cw.WindDirection().String() {
|
||||
t.Errorf("CurrentWeatherByLocation failed, expected wind direction "+
|
||||
"string: %s, got: %s", tc.d.String(), cw.WindDirection())
|
||||
}
|
||||
if tc.d != nil && tc.d.Value() != cw.WindDirection().Value() {
|
||||
t.Errorf("CurrentWeatherByLocation failed, expected wind direction "+
|
||||
"float: %f, got: %f", tc.d.Value(), cw.WindDirection().Value())
|
||||
}
|
||||
if tc.d != nil && cw.WindDirection().Source() != tc.d.s {
|
||||
t.Errorf("CurrentWeatherByLocation failed, expected source: %s, but got: %s",
|
||||
tc.d.s, cw.WindDirection().Source())
|
||||
}
|
||||
if tc.d != nil && cw.WindDirection().Direction() != tc.da {
|
||||
t.Errorf("CurrentWeatherByLocation failed, expected direction abbr.: %s, but got: %s",
|
||||
tc.da, cw.WindDirection().Direction())
|
||||
}
|
||||
if tc.d != nil && cw.WindDirection().DirectionFull() != tc.df {
|
||||
t.Errorf("CurrentWeatherByLocation failed, expected direction full: %s, but got: %s",
|
||||
tc.df, cw.WindDirection().DirectionFull())
|
||||
}
|
||||
if tc.d == nil {
|
||||
if cw.WindDirection().IsAvailable() {
|
||||
t.Errorf("CurrentWeatherByLocation failed, expected wind direction "+
|
||||
"to have no data, but got: %s", cw.WindDirection())
|
||||
}
|
||||
if !math.IsNaN(cw.WindDirection().Value()) {
|
||||
t.Errorf("CurrentWeatherByLocation failed, expected wind direction "+
|
||||
"to return NaN, but got: %s", cw.WindSpeed().String())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_CurrentWeatherByLocation_WindGust(t *testing.T) {
|
||||
tt := []struct {
|
||||
// Location name
|
||||
loc string
|
||||
// CurWeather speed
|
||||
s *Speed
|
||||
}{
|
||||
{"Ehrenfeld, Germany", &Speed{
|
||||
dt: time.Date(2023, 5, 23, 7, 0, 0, 0, time.Local),
|
||||
s: SourceAnalysis,
|
||||
fv: 7.770000,
|
||||
}},
|
||||
{"Berlin, Germany", &Speed{
|
||||
dt: time.Date(2023, 5, 23, 7, 0, 0, 0, time.Local),
|
||||
s: SourceAnalysis,
|
||||
fv: 5.570000,
|
||||
}},
|
||||
{"Neermoor, Germany", nil},
|
||||
}
|
||||
c := New(withMockAPI())
|
||||
if c == nil {
|
||||
t.Errorf("failed to create new Client, got nil")
|
||||
return
|
||||
}
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.loc, func(t *testing.T) {
|
||||
cw, err := c.CurrentWeatherByLocation(tc.loc)
|
||||
if err != nil {
|
||||
t.Errorf("CurrentWeatherByLocation failed: %s", err)
|
||||
return
|
||||
}
|
||||
if tc.s != nil && tc.s.String() != cw.WindGust().String() {
|
||||
t.Errorf("CurrentWeatherByLocation failed, expected wind gust "+
|
||||
"string: %s, got: %s", tc.s.String(), cw.WindGust())
|
||||
}
|
||||
if tc.s != nil && tc.s.Value() != cw.WindGust().Value() {
|
||||
t.Errorf("CurrentWeatherByLocation failed, expected wind gust "+
|
||||
"float: %f, got: %f", tc.s.Value(), cw.WindGust().Value())
|
||||
}
|
||||
if tc.s != nil && cw.WindGust().Source() != tc.s.s {
|
||||
t.Errorf("CurrentWeatherByLocation failed, expected source: %s, but got: %s",
|
||||
tc.s.s, cw.WindGust().Source())
|
||||
}
|
||||
if tc.s == nil {
|
||||
if cw.WindGust().IsAvailable() {
|
||||
t.Errorf("CurrentWeatherByLocation failed, expected wind gust "+
|
||||
"to have no data, but got: %s", cw.WindGust())
|
||||
}
|
||||
if !math.IsNaN(cw.WindGust().Value()) {
|
||||
t.Errorf("CurrentWeatherByLocation failed, expected wind gust "+
|
||||
"to return NaN, but got: %s", cw.WindGust().String())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_CurrentWeatherByLocation_WindSpeed(t *testing.T) {
|
||||
tt := []struct {
|
||||
// Location name
|
||||
loc string
|
||||
// CurWeather speed
|
||||
s *Speed
|
||||
}{
|
||||
{"Ehrenfeld, Germany", &Speed{
|
||||
dt: time.Date(2023, 5, 23, 7, 0, 0, 0, time.Local),
|
||||
s: SourceAnalysis,
|
||||
fv: 3.94,
|
||||
}},
|
||||
{"Berlin, Germany", &Speed{
|
||||
dt: time.Date(2023, 5, 23, 7, 0, 0, 0, time.Local),
|
||||
s: SourceAnalysis,
|
||||
fv: 3.19,
|
||||
}},
|
||||
{"Neermoor, Germany", nil},
|
||||
}
|
||||
c := New(withMockAPI())
|
||||
if c == nil {
|
||||
t.Errorf("failed to create new Client, got nil")
|
||||
return
|
||||
}
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.loc, func(t *testing.T) {
|
||||
cw, err := c.CurrentWeatherByLocation(tc.loc)
|
||||
if err != nil {
|
||||
t.Errorf("CurrentWeatherByLocation failed: %s", err)
|
||||
return
|
||||
}
|
||||
if tc.s != nil && tc.s.String() != cw.WindSpeed().String() {
|
||||
t.Errorf("CurrentWeatherByLocation failed, expected wind speed "+
|
||||
"string: %s, got: %s", tc.s.String(), cw.WindSpeed())
|
||||
}
|
||||
if tc.s != nil && tc.s.Value() != cw.WindSpeed().Value() {
|
||||
t.Errorf("CurrentWeatherByLocation failed, expected wind speed "+
|
||||
"float: %f, got: %f", tc.s.Value(), cw.WindSpeed().Value())
|
||||
}
|
||||
if tc.s != nil && cw.WindSpeed().Source() != tc.s.s {
|
||||
t.Errorf("CurrentWeatherByLocation failed, expected source: %s, but got: %s",
|
||||
tc.s.s, cw.WindSpeed().Source())
|
||||
}
|
||||
if tc.s == nil {
|
||||
if cw.WindSpeed().IsAvailable() {
|
||||
t.Errorf("CurrentWeatherByLocation failed, expected wind speed "+
|
||||
"to have no data, but got: %s", cw.WindSpeed())
|
||||
}
|
||||
if !math.IsNaN(cw.WindSpeed().Value()) {
|
||||
t.Errorf("CurrentWeatherByLocation failed, expected wind speed "+
|
||||
"to return NaN, but got: %s", cw.WindSpeed().String())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
21
datatype.go
21
datatype.go
|
@ -44,6 +44,8 @@ const (
|
|||
FieldPressureMSL
|
||||
// FieldPressureQFE represents the PressureQFE data point
|
||||
FieldPressureQFE
|
||||
// FieldSnowAmount represents the SnowAmount data point
|
||||
FieldSnowAmount
|
||||
// FieldSunrise represents the Sunrise data point
|
||||
FieldSunrise
|
||||
// FieldSunset represents the Sunset data point
|
||||
|
@ -62,10 +64,12 @@ const (
|
|||
FieldTemperatureMin
|
||||
// FieldWeatherSymbol represents the weather symbol data point
|
||||
FieldWeatherSymbol
|
||||
// FieldWinddirection represents the Winddirection data point
|
||||
FieldWinddirection
|
||||
// FieldWindspeed represents the Windspeed data point
|
||||
FieldWindspeed
|
||||
// FieldWindDirection represents the WindDirection data point
|
||||
FieldWindDirection
|
||||
// FieldWindGust represents the WindGust data point
|
||||
FieldWindGust
|
||||
// FieldWindSpeed represents the WindSpeed data point
|
||||
FieldWindSpeed
|
||||
)
|
||||
|
||||
// Enum for different Timespan values
|
||||
|
@ -86,6 +90,14 @@ type APIDate struct {
|
|||
time.Time
|
||||
}
|
||||
|
||||
// APIBool is the JSON structure of the weather data that is
|
||||
// returned by the API endpoints in which the value is a boolean
|
||||
type APIBool struct {
|
||||
DateTime time.Time `json:"dateTime"`
|
||||
Source *string `json:"source,omitempty"`
|
||||
Value bool `json:"value"`
|
||||
}
|
||||
|
||||
// APIFloat is the JSON structure of the weather data that is
|
||||
// returned by the API endpoints in which the value is a float
|
||||
type APIFloat struct {
|
||||
|
@ -109,6 +121,7 @@ type Timespan int
|
|||
// Weather) data and can be wrapped into other types to provide type
|
||||
// specific receiver methods
|
||||
type WeatherData struct {
|
||||
// bv bool
|
||||
dt time.Time
|
||||
dv time.Time
|
||||
fv float64
|
||||
|
|
48
density.go
Normal file
48
density.go
Normal file
|
@ -0,0 +1,48 @@
|
|||
// SPDX-FileCopyrightText: 2023 Winni Neessen <wn@neessen.dev>
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package meteologix
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Density is a type wrapper of an WeatherData for holding density
|
||||
// values in WeatherData
|
||||
type Density WeatherData
|
||||
|
||||
// IsAvailable returns true if an Density value was
|
||||
// available at time of query
|
||||
func (d Density) IsAvailable() bool {
|
||||
return !d.na
|
||||
}
|
||||
|
||||
// DateTime returns true if an Density value was
|
||||
// available at time of query
|
||||
func (d Density) DateTime() time.Time {
|
||||
return d.dt
|
||||
}
|
||||
|
||||
// String satisfies the fmt.Stringer interface for the Density type
|
||||
func (d Density) String() string {
|
||||
return fmt.Sprintf("%.1fkg/m³", d.fv)
|
||||
}
|
||||
|
||||
// Source returns the Source of Density
|
||||
// If the Source is not available it will return SourceUnknown
|
||||
func (d Density) Source() Source {
|
||||
return d.s
|
||||
}
|
||||
|
||||
// Value returns the float64 value of an Density
|
||||
// If the Density is not available in the Observation
|
||||
// Vaule will return math.NaN instead.
|
||||
func (d Density) Value() float64 {
|
||||
if d.na {
|
||||
return math.NaN()
|
||||
}
|
||||
return d.fv
|
||||
}
|
2
doc.go
2
doc.go
|
@ -6,4 +6,4 @@
|
|||
package meteologix
|
||||
|
||||
// VERSION represents the current version of the package
|
||||
const VERSION = "0.0.9"
|
||||
const VERSION = "0.1.0"
|
||||
|
|
|
@ -52,17 +52,17 @@ type APIObservationData struct {
|
|||
// HumidityRelative represents the relative humidity in percent
|
||||
HumidityRelative *APIFloat `json:"humidityRelative,omitempty"`
|
||||
// Precipitation represents the current amount of precipitation
|
||||
Precipitation *APIFloat `json:"prec"`
|
||||
Precipitation *APIFloat `json:"prec,omitempty"`
|
||||
// Precipitation10m represents the amount of precipitation over the last 10 minutes
|
||||
Precipitation10m *APIFloat `json:"prec10m"`
|
||||
Precipitation10m *APIFloat `json:"prec10m,omitempty"`
|
||||
// Precipitation1h represents the amount of precipitation over the last hour
|
||||
Precipitation1h *APIFloat `json:"prec1h"`
|
||||
Precipitation1h *APIFloat `json:"prec1h,omitempty"`
|
||||
// Precipitation24h represents the amount of precipitation over the last 24 hours
|
||||
Precipitation24h *APIFloat `json:"prec24h"`
|
||||
// PressureMSL represents the pressure at mean sea level (MSL) in hPa
|
||||
PressureMSL *APIFloat `json:"pressureMsl"`
|
||||
// PressureMSL represents the pressure at station level (QFE) in hPa
|
||||
PressureQFE *APIFloat `json:"pressure"`
|
||||
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
|
||||
|
@ -76,11 +76,11 @@ type APIObservationData struct {
|
|||
// Temperature5cm represents the minimum temperature 5cm above
|
||||
// ground in °C
|
||||
Temperature5cmMin *APIFloat `json:"temp5cmMin,omitempty"`
|
||||
// Winddirection represents the direction from which the wind
|
||||
// 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
|
||||
Windspeed *APIFloat `json:"windSpeed,omitempty"`
|
||||
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
|
||||
|
@ -365,33 +365,33 @@ func (o Observation) GlobalRadiation(ts Timespan) Radiation {
|
|||
}
|
||||
}
|
||||
|
||||
// Winddirection returns the current direction from which the wind
|
||||
// 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 {
|
||||
func (o Observation) WindDirection() Direction {
|
||||
if o.Data.WindDirection == nil {
|
||||
return Direction{na: true}
|
||||
}
|
||||
return Direction{
|
||||
dt: o.Data.Winddirection.DateTime,
|
||||
n: FieldWinddirection,
|
||||
dt: o.Data.WindDirection.DateTime,
|
||||
n: FieldWindDirection,
|
||||
s: SourceObservation,
|
||||
fv: o.Data.Winddirection.Value,
|
||||
fv: o.Data.WindDirection.Value,
|
||||
}
|
||||
}
|
||||
|
||||
// Windspeed returns the current windspeed data point as Speed.
|
||||
// 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 {
|
||||
func (o Observation) WindSpeed() Speed {
|
||||
if o.Data.WindSpeed == nil {
|
||||
return Speed{na: true}
|
||||
}
|
||||
return Speed{
|
||||
dt: o.Data.Windspeed.DateTime,
|
||||
n: FieldWindspeed,
|
||||
dt: o.Data.WindSpeed.DateTime,
|
||||
n: FieldWindSpeed,
|
||||
s: SourceObservation,
|
||||
fv: o.Data.Windspeed.Value,
|
||||
fv: o.Data.WindSpeed.Value * 0.5144444444,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1124,7 +1124,7 @@ func TestClient_ObservationLatestByStationID_GlobalRadiation24h(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestClient_ObservationLatestByStationID_Winddirection(t *testing.T) {
|
||||
func TestClient_ObservationLatestByStationID_WindDirection(t *testing.T) {
|
||||
tt := []struct {
|
||||
// Test name
|
||||
n string
|
||||
|
@ -1153,37 +1153,37 @@ func TestClient_ObservationLatestByStationID_Winddirection(t *testing.T) {
|
|||
t.Errorf("ObservationLatestByStationID with station %s failed: %s", tc.sid, err)
|
||||
return
|
||||
}
|
||||
if tc.p != nil && tc.p.String() != o.Winddirection().String() {
|
||||
if tc.p != nil && tc.p.String() != o.WindDirection().String() {
|
||||
t.Errorf("ObservationLatestByStationID failed, expected wind direction "+
|
||||
"string: %s, got: %s", tc.p.String(), o.Winddirection())
|
||||
"string: %s, got: %s", tc.p.String(), o.WindDirection())
|
||||
}
|
||||
if tc.p != nil && tc.p.Value() != o.Winddirection().Value() {
|
||||
if tc.p != nil && tc.p.Value() != o.WindDirection().Value() {
|
||||
t.Errorf("ObservationLatestByStationID failed, expected wind direction "+
|
||||
"float: %f, got: %f", tc.p.Value(), o.Winddirection().Value())
|
||||
"float: %f, got: %f", tc.p.Value(), o.WindDirection().Value())
|
||||
}
|
||||
if tc.p != nil && tc.p.dt.Unix() != o.Winddirection().DateTime().Unix() {
|
||||
if tc.p != nil && tc.p.dt.Unix() != o.WindDirection().DateTime().Unix() {
|
||||
t.Errorf("ObservationLatestByStationID failed, expected datetime: %s, got: %s",
|
||||
tc.p.dt.Format(time.RFC3339), o.Winddirection().DateTime().Format(time.RFC3339))
|
||||
tc.p.dt.Format(time.RFC3339), o.WindDirection().DateTime().Format(time.RFC3339))
|
||||
}
|
||||
if o.Winddirection().Source() != SourceObservation {
|
||||
if o.WindDirection().Source() != SourceObservation {
|
||||
t.Errorf("ObservationLatestByStationID failed, expected observation source, but got: %s",
|
||||
o.Winddirection().Source())
|
||||
o.WindDirection().Source())
|
||||
}
|
||||
if tc.p == nil {
|
||||
if o.Winddirection().IsAvailable() {
|
||||
if o.WindDirection().IsAvailable() {
|
||||
t.Errorf("ObservationLatestByStationID failed, expected wind direction "+
|
||||
"to have no data, but got: %s", o.Winddirection())
|
||||
"to have no data, but got: %s", o.WindDirection())
|
||||
}
|
||||
if !math.IsNaN(o.Winddirection().Value()) {
|
||||
if !math.IsNaN(o.WindDirection().Value()) {
|
||||
t.Errorf("ObservationLatestByStationID failed, expected wind direction "+
|
||||
"to return NaN, but got: %s", o.Winddirection().String())
|
||||
"to return NaN, but got: %s", o.WindDirection().String())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_ObservationLatestByStationID_Windspeed(t *testing.T) {
|
||||
func TestClient_ObservationLatestByStationID_WindSpeed(t *testing.T) {
|
||||
tt := []struct {
|
||||
// Test name
|
||||
n string
|
||||
|
@ -1196,7 +1196,7 @@ func TestClient_ObservationLatestByStationID_Windspeed(t *testing.T) {
|
|||
{"K-Stammheim", "H744", nil},
|
||||
{"All data fields", "all", &Speed{
|
||||
dt: time.Date(2023, 0o5, 21, 11, 30, 0, 0, time.UTC),
|
||||
fv: 15,
|
||||
fv: 7.716666666,
|
||||
}},
|
||||
{"No data fields", "none", nil},
|
||||
}
|
||||
|
@ -1212,30 +1212,30 @@ func TestClient_ObservationLatestByStationID_Windspeed(t *testing.T) {
|
|||
t.Errorf("ObservationLatestByStationID with station %s failed: %s", tc.sid, err)
|
||||
return
|
||||
}
|
||||
if tc.p != nil && tc.p.String() != o.Windspeed().String() {
|
||||
if tc.p != nil && tc.p.String() != o.WindSpeed().String() {
|
||||
t.Errorf("ObservationLatestByStationID failed, expected windspeed "+
|
||||
"string: %s, got: %s", tc.p.String(), o.Windspeed())
|
||||
"string: %s, got: %s", tc.p.String(), o.WindSpeed())
|
||||
}
|
||||
if tc.p != nil && tc.p.Value() != o.Windspeed().Value() {
|
||||
if tc.p != nil && tc.p.Value() != o.WindSpeed().Value() {
|
||||
t.Errorf("ObservationLatestByStationID failed, expected windspeed "+
|
||||
"float: %f, got: %f", tc.p.Value(), o.Windspeed().Value())
|
||||
"float: %f, got: %f, %+v", tc.p.Value(), o.WindSpeed().Value(), o.Data.WindSpeed)
|
||||
}
|
||||
if tc.p != nil && tc.p.dt.Unix() != o.Windspeed().DateTime().Unix() {
|
||||
if tc.p != nil && tc.p.dt.Unix() != o.WindSpeed().DateTime().Unix() {
|
||||
t.Errorf("ObservationLatestByStationID failed, expected datetime: %s, got: %s",
|
||||
tc.p.dt.Format(time.RFC3339), o.Windspeed().DateTime().Format(time.RFC3339))
|
||||
tc.p.dt.Format(time.RFC3339), o.WindSpeed().DateTime().Format(time.RFC3339))
|
||||
}
|
||||
if o.Windspeed().Source() != SourceObservation {
|
||||
if o.WindSpeed().Source() != SourceObservation {
|
||||
t.Errorf("ObservationLatestByStationID failed, expected observation source, but got: %s",
|
||||
o.Windspeed().Source())
|
||||
o.WindSpeed().Source())
|
||||
}
|
||||
if tc.p == nil {
|
||||
if o.Windspeed().IsAvailable() {
|
||||
if o.WindSpeed().IsAvailable() {
|
||||
t.Errorf("ObservationLatestByStationID failed, expected windspeed "+
|
||||
"to have no data, but got: %s", o.Windspeed())
|
||||
"to have no data, but got: %s", o.WindSpeed())
|
||||
}
|
||||
if !math.IsNaN(o.Windspeed().Value()) {
|
||||
if !math.IsNaN(o.WindSpeed().Value()) {
|
||||
t.Errorf("ObservationLatestByStationID failed, expected windspeed "+
|
||||
"to return NaN, but got: %s", o.Windspeed().String())
|
||||
"to return NaN, but got: %s", o.WindSpeed().String())
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -1308,32 +1308,35 @@ func TestObservationTemperature_String(t *testing.T) {
|
|||
|
||||
func TestObservationSpeed_Conversion(t *testing.T) {
|
||||
tt := []struct {
|
||||
// Original knots value
|
||||
kn float64
|
||||
// Original m/s value
|
||||
ms float64
|
||||
// km/h value
|
||||
kmh float64
|
||||
// mi/h value
|
||||
mph float64
|
||||
// knots value
|
||||
kn float64
|
||||
}{
|
||||
{0, 0, 0},
|
||||
{1, 1.852, 1.151},
|
||||
{10, 18.52, 11.51},
|
||||
{15, 27.78, 17.265},
|
||||
{30, 55.56, 34.53},
|
||||
{0, 0, 0, 0},
|
||||
{1, 3.6, 2.236936, 1.9438444924},
|
||||
{10, 36, 22.369360, 19.438444924},
|
||||
{15, 54, 33.554040, 29.157667386},
|
||||
{30, 108, 67.108080, 58.315334772},
|
||||
}
|
||||
msf := "%.1fm/s"
|
||||
knf := "%.0fkn"
|
||||
kmhf := "%.1fkm/h"
|
||||
mphf := "%.1fmi/h"
|
||||
for _, tc := range tt {
|
||||
t.Run(fmt.Sprintf("%.0fkn", tc.kn), func(t *testing.T) {
|
||||
os := Speed{fv: tc.kn}
|
||||
if os.Value() != tc.kn {
|
||||
t.Errorf("Speed.Value failed, expected: %f, got: %f", tc.kn,
|
||||
t.Run(fmt.Sprintf("%.0fm/s", tc.ms), func(t *testing.T) {
|
||||
os := Speed{fv: tc.ms}
|
||||
if os.Value() != tc.ms {
|
||||
t.Errorf("Speed.Value failed, expected: %f, got: %f", tc.ms,
|
||||
os.Value())
|
||||
}
|
||||
if os.String() != fmt.Sprintf(knf, tc.kn) {
|
||||
if os.String() != fmt.Sprintf(msf, tc.ms) {
|
||||
t.Errorf("Speed.String failed, expected: %s, got: %s",
|
||||
fmt.Sprintf(knf, tc.kn), os.String())
|
||||
fmt.Sprintf(msf, tc.ms), os.String())
|
||||
}
|
||||
if os.KMH() != tc.kmh {
|
||||
t.Errorf("Speed.KMH failed, expected: %f, got: %f", tc.kmh,
|
||||
|
@ -1351,6 +1354,14 @@ func TestObservationSpeed_Conversion(t *testing.T) {
|
|||
t.Errorf("Speed.MPHString failed, expected: %s, got: %s",
|
||||
fmt.Sprintf(mphf, tc.mph), os.MPHString())
|
||||
}
|
||||
if os.Knots() != tc.kn {
|
||||
t.Errorf("Speed.Knots failed, expected: %f, got: %f", tc.kn,
|
||||
os.Knots())
|
||||
}
|
||||
if os.KnotsString() != fmt.Sprintf(knf, tc.kn) {
|
||||
t.Errorf("Speed.KnotsString failed, expected: %s, got: %s",
|
||||
fmt.Sprintf(knf, tc.kn), os.KnotsString())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
28
speed.go
28
speed.go
|
@ -10,6 +10,15 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// MultiplierKnots is the multiplier for converting the base unit to knots
|
||||
MultiplierKnots = 1.9438444924
|
||||
// MultiplierKPH is the multiplier for converting the base unit to kilometers per hour
|
||||
MultiplierKPH = 3.6
|
||||
// MultiplierMPH is the multiplier for converting the base unit to miles per hour
|
||||
MultiplierMPH = 2.236936
|
||||
)
|
||||
|
||||
// Speed is a type wrapper of an WeatherData for holding speed
|
||||
// values in WeatherData
|
||||
type Speed WeatherData
|
||||
|
@ -26,7 +35,8 @@ func (s Speed) DateTime() time.Time {
|
|||
return s.dt
|
||||
}
|
||||
|
||||
// Value returns the float64 value of an Speed in knots
|
||||
// Value returns the float64 value of an Speed in meters
|
||||
// per second.
|
||||
// If the Speed is not available in the Observation
|
||||
// Vaule will return math.NaN instead.
|
||||
func (s Speed) Value() float64 {
|
||||
|
@ -38,7 +48,7 @@ func (s Speed) Value() float64 {
|
|||
|
||||
// String satisfies the fmt.Stringer interface for the Speed type
|
||||
func (s Speed) String() string {
|
||||
return fmt.Sprintf("%.0fkn", s.fv)
|
||||
return fmt.Sprintf("%.1fm/s", s.fv)
|
||||
}
|
||||
|
||||
// Source returns the Source of Speed
|
||||
|
@ -49,7 +59,7 @@ func (s Speed) Source() Source {
|
|||
|
||||
// KMH returns the Speed value in km/h
|
||||
func (s Speed) KMH() float64 {
|
||||
return s.fv * 1.852
|
||||
return s.fv * MultiplierKPH
|
||||
}
|
||||
|
||||
// KMHString returns the Speed value as formatted string in km/h
|
||||
|
@ -57,9 +67,19 @@ func (s Speed) KMHString() string {
|
|||
return fmt.Sprintf("%.1fkm/h", s.KMH())
|
||||
}
|
||||
|
||||
// Knots returns the Speed value in kn
|
||||
func (s Speed) Knots() float64 {
|
||||
return s.fv * MultiplierKnots
|
||||
}
|
||||
|
||||
// KnotsString returns the Speed value as formatted string in kn
|
||||
func (s Speed) KnotsString() string {
|
||||
return fmt.Sprintf("%.0fkn", s.Knots())
|
||||
}
|
||||
|
||||
// MPH returns the Speed value in mi/h
|
||||
func (s Speed) MPH() float64 {
|
||||
return s.fv * 1.151
|
||||
return s.fv * MultiplierMPH
|
||||
}
|
||||
|
||||
// MPHString returns the Speed value as formatted string in mi/h
|
||||
|
|
Loading…
Reference in a new issue