2023-05-21 11:46:23 +02:00
|
|
|
// SPDX-FileCopyrightText: 2023 Winni Neessen <wn@neessen.dev>
|
2023-05-13 17:10:00 +02:00
|
|
|
//
|
|
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
|
|
|
|
package meteologix
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"net/url"
|
|
|
|
"sort"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
|
|
|
// DefaultRadius is the default radius value that is used in the
|
|
|
|
// station search
|
|
|
|
const DefaultRadius int = 10
|
|
|
|
|
|
|
|
const (
|
2023-06-27 19:21:24 +02:00
|
|
|
// PrecisionSuperHigh represents the precision level of data corresponding
|
|
|
|
// to a resolution of less than or approximately equal to 4 kilometers.
|
|
|
|
// This is the highest level of precision, usually associated with highly
|
|
|
|
// detailed measurements or observations.
|
2023-06-26 12:21:36 +02:00
|
|
|
PrecisionSuperHigh Precision = iota
|
2023-06-27 19:21:24 +02:00
|
|
|
// PrecisionHigh represents the precision level of data corresponding to a
|
|
|
|
// resolution between 4 kilometers and 10 kilometers. This is a high precision
|
|
|
|
// level, suitable for most operational needs that require a balance between
|
|
|
|
// detail and processing requirements.
|
2023-06-26 12:21:36 +02:00
|
|
|
PrecisionHigh
|
2023-06-27 19:21:24 +02:00
|
|
|
// PrecisionStandard represents the precision level of data corresponding to
|
|
|
|
// a resolution of 10 kilometers or more. This is the standard level of
|
|
|
|
// precision, generally used for large-scale analysis and modeling.
|
2023-06-26 12:21:36 +02:00
|
|
|
PrecisionStandard
|
2023-06-27 19:21:24 +02:00
|
|
|
// PrecisionUnknown is used when the precision level of a weather station
|
|
|
|
// is unknown. This constant can be used as a placeholder when the resolution
|
|
|
|
// data is not available.
|
2023-05-13 17:10:00 +02:00
|
|
|
PrecisionUnknown
|
|
|
|
)
|
|
|
|
|
2023-06-27 19:21:24 +02:00
|
|
|
// Precision levels defined as strings to allow for clear, consistent
|
|
|
|
// use throughout the application.
|
|
|
|
const (
|
|
|
|
// PrecisionStringSuperHigh represents the super high precision level string.
|
|
|
|
PrecisionStringSuperHigh = "SUPER_HIGH"
|
|
|
|
|
|
|
|
// PrecisionStringHigh represents the high precision level string.
|
|
|
|
PrecisionStringHigh = "HIGH"
|
|
|
|
|
|
|
|
// PrecisionStringStandard represents the standard precision level string.
|
|
|
|
PrecisionStringStandard = "STANDARD"
|
|
|
|
|
|
|
|
// PrecisionStringUnknown represents an unknown precision level string.
|
|
|
|
PrecisionStringUnknown = "UNKNOWN"
|
|
|
|
)
|
|
|
|
|
2023-05-13 17:10:00 +02:00
|
|
|
var (
|
|
|
|
// ErrRadiusTooSmall is returned if a given radius value is too small
|
|
|
|
ErrRadiusTooSmall = errors.New("given radius is too small")
|
|
|
|
// ErrNoStationFound is returned if a station search did not return any results
|
|
|
|
ErrNoStationFound = errors.New("no station found in requested location")
|
|
|
|
)
|
|
|
|
|
|
|
|
// Station is a weather station as returned by the Meteologix API
|
|
|
|
type Station struct {
|
|
|
|
// Altitude is the altitude of the station
|
|
|
|
Altitude int `json:"alt"`
|
|
|
|
// Distance is the distatnce of the station to the provided coordinates
|
|
|
|
Distance float64 `json:"distance"`
|
|
|
|
// ID is the station ID
|
|
|
|
ID string `json:"id"`
|
|
|
|
// Latitude is the latitude of the station
|
|
|
|
Latitude float64 `json:"lat"`
|
|
|
|
// Longitude is the latitude of the station
|
|
|
|
Longitude float64 `json:"lon"`
|
|
|
|
// Name is the name or location of the station
|
|
|
|
Name string `json:"name"`
|
|
|
|
// Precision is the precision string returned by the API
|
2023-05-13 18:44:29 +02:00
|
|
|
Precision *Precision `json:"precision,omitempty"`
|
2023-05-13 17:10:00 +02:00
|
|
|
// RecentlyActive represents if the station was recently active
|
|
|
|
RecentlyActive bool `json:"recentlyActive"`
|
|
|
|
// Type is the type of weather station
|
2023-05-13 18:44:29 +02:00
|
|
|
Type *string `json:"type,omitempty"`
|
2023-05-13 17:10:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Precision is a type wrapper for an int type
|
|
|
|
type Precision int
|
|
|
|
|
2023-05-13 18:44:29 +02:00
|
|
|
// StationSearchByCoordinates returns a list of available weather stations
|
|
|
|
// based on the given latitude, longitude coordinates within the default
|
|
|
|
// radius
|
2023-05-13 17:10:00 +02:00
|
|
|
//
|
2023-05-13 18:44:29 +02:00
|
|
|
// Results will be sorted by distance to the requested coordinates.
|
2023-05-13 17:10:00 +02:00
|
|
|
//
|
|
|
|
// Depending on your subscription you may have access to one, two or
|
|
|
|
// unlimited locations for station observations.
|
|
|
|
// Finding a station with his endpoint does not automatically mean
|
|
|
|
// that you are allowed to get all data from this station.
|
|
|
|
//
|
|
|
|
// See: https://api.kachelmannwetter.com/v02/_doc.html#/operations/get_station_search
|
2023-05-13 18:44:29 +02:00
|
|
|
func (c *Client) StationSearchByCoordinates(la, lo float64) ([]Station, error) {
|
|
|
|
return c.StationSearchByCoordinatesWithinRadius(la, lo, DefaultRadius)
|
2023-05-13 17:10:00 +02:00
|
|
|
}
|
|
|
|
|
2023-05-13 18:44:29 +02:00
|
|
|
// StationSearchByLocation returns a list of available weather stations
|
|
|
|
// based on the given location string within the default radius
|
2023-05-13 17:10:00 +02:00
|
|
|
//
|
2023-05-13 18:44:29 +02:00
|
|
|
// # Results will be sorted by distance to the requested location
|
2023-05-13 17:10:00 +02:00
|
|
|
//
|
|
|
|
// Depending on your subscription you may have access to one, two or
|
|
|
|
// unlimited locations for station observations.
|
|
|
|
// Finding a station with his endpoint does not automatically mean
|
|
|
|
// that you are allowed to get all data from this station.
|
|
|
|
//
|
|
|
|
// See: https://api.kachelmannwetter.com/v02/_doc.html#/operations/get_station_search
|
2023-05-13 18:44:29 +02:00
|
|
|
func (c *Client) StationSearchByLocation(lo string) ([]Station, error) {
|
|
|
|
return c.StationSearchByLocationWithinRadius(lo, DefaultRadius)
|
|
|
|
}
|
|
|
|
|
|
|
|
// StationSearchByLocationWithinRadius returns a list of available weather
|
|
|
|
// stations based on the given location string and radius.
|
|
|
|
//
|
|
|
|
// Results will be sorted by distance to the requested location.
|
|
|
|
//
|
|
|
|
// Depending on your subscription you may have access to one, two or
|
|
|
|
// unlimited locations for station observations.
|
|
|
|
// Finding a station with his endpoint does not automatically mean
|
|
|
|
// that you are allowed to get all data from this station.
|
|
|
|
//
|
|
|
|
// See: https://api.kachelmannwetter.com/v02/_doc.html#/operations/get_station_search
|
|
|
|
func (c *Client) StationSearchByLocationWithinRadius(lo string, ra int) ([]Station, error) {
|
2023-05-15 16:30:23 +02:00
|
|
|
l, err := c.GetGeoLocationByName(lo)
|
2023-05-13 17:10:00 +02:00
|
|
|
if err != nil {
|
2023-05-13 18:44:29 +02:00
|
|
|
return nil, fmt.Errorf("failed too look up location details: %w", err)
|
2023-05-13 17:10:00 +02:00
|
|
|
}
|
2023-05-13 18:44:29 +02:00
|
|
|
return c.StationSearchByCoordinatesWithinRadius(l.Latitude, l.Longitude, ra)
|
2023-05-13 17:10:00 +02:00
|
|
|
}
|
|
|
|
|
2023-05-13 18:44:29 +02:00
|
|
|
// StationSearchByCoordinatesWithinRadius returns a list of available weather stations
|
|
|
|
// based on the given latitude, longitude coordinates and radius.
|
2023-05-13 17:10:00 +02:00
|
|
|
//
|
2023-05-13 18:44:29 +02:00
|
|
|
// Results will be sorted by distance to the requested coordinates.
|
2023-05-13 17:10:00 +02:00
|
|
|
//
|
|
|
|
// Depending on your subscription you may have access to one, two or
|
|
|
|
// unlimited locations for station observations.
|
|
|
|
// Finding a station with his endpoint does not automatically mean
|
|
|
|
// that you are allowed to get all data from this station.
|
|
|
|
//
|
|
|
|
// See: https://api.kachelmannwetter.com/v02/_doc.html#/operations/get_station_search
|
2023-05-13 18:44:29 +02:00
|
|
|
func (c *Client) StationSearchByCoordinatesWithinRadius(la, lo float64, ra int) ([]Station, error) {
|
2023-05-13 17:10:00 +02:00
|
|
|
if ra < 1 {
|
|
|
|
return nil, ErrRadiusTooSmall
|
|
|
|
}
|
|
|
|
|
|
|
|
u, err := url.Parse(fmt.Sprintf("%s/station/search/%f/%f",
|
2023-05-15 16:02:04 +02:00
|
|
|
c.config.apiURL, la, lo))
|
2023-05-13 17:10:00 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to parse station search URL: %w", err)
|
|
|
|
}
|
|
|
|
uq := u.Query()
|
|
|
|
uq.Add("radius", fmt.Sprintf("%d", ra))
|
|
|
|
u.RawQuery = uq.Encode()
|
|
|
|
|
|
|
|
r, err := c.httpClient.Get(u.String())
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("API request failed: %w", err)
|
|
|
|
}
|
|
|
|
var sl []Station
|
|
|
|
if err := json.Unmarshal(r, &sl); err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to unmarshal API response JSON: %w", err)
|
|
|
|
}
|
|
|
|
if len(sl) < 1 {
|
|
|
|
return nil, ErrNoStationFound
|
|
|
|
}
|
|
|
|
sort.SliceStable(sl, func(i, j int) bool { return sl[i].Distance < sl[j].Distance })
|
|
|
|
|
|
|
|
return sl, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// UnmarshalJSON method for converting API precision responses into
|
|
|
|
// StationPrecision types
|
2023-05-13 18:44:29 +02:00
|
|
|
func (p *Precision) UnmarshalJSON(s []byte) error {
|
2023-05-13 17:10:00 +02:00
|
|
|
v := string(s)
|
|
|
|
v = strings.ReplaceAll(v, `"`, ``)
|
2023-06-27 19:21:24 +02:00
|
|
|
switch strings.ToUpper(v) {
|
|
|
|
case PrecisionStringSuperHigh:
|
2023-06-26 12:21:36 +02:00
|
|
|
*p = PrecisionSuperHigh
|
2023-06-27 19:21:24 +02:00
|
|
|
case PrecisionStringHigh:
|
2023-05-13 18:44:29 +02:00
|
|
|
*p = PrecisionHigh
|
2023-06-27 19:21:24 +02:00
|
|
|
case PrecisionStringStandard:
|
2023-06-26 12:21:36 +02:00
|
|
|
*p = PrecisionStandard
|
2023-05-13 17:10:00 +02:00
|
|
|
default:
|
2023-05-13 18:44:29 +02:00
|
|
|
*p = PrecisionUnknown
|
2023-05-13 17:10:00 +02:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// String satisfies the fmt.Stringer interface for the Precision type
|
2023-05-13 18:44:29 +02:00
|
|
|
func (p *Precision) String() string {
|
|
|
|
switch *p {
|
2023-06-26 12:21:36 +02:00
|
|
|
case PrecisionSuperHigh:
|
2023-06-27 19:21:24 +02:00
|
|
|
return PrecisionStringSuperHigh
|
2023-05-13 17:10:00 +02:00
|
|
|
case PrecisionHigh:
|
2023-06-27 19:21:24 +02:00
|
|
|
return PrecisionStringHigh
|
2023-06-26 12:21:36 +02:00
|
|
|
case PrecisionStandard:
|
2023-06-27 19:21:24 +02:00
|
|
|
return PrecisionStringStandard
|
2023-05-13 17:10:00 +02:00
|
|
|
case PrecisionUnknown:
|
2023-06-27 19:21:24 +02:00
|
|
|
return PrecisionStringUnknown
|
2023-05-13 17:10:00 +02:00
|
|
|
default:
|
2023-06-27 19:21:24 +02:00
|
|
|
return PrecisionStringUnknown
|
2023-05-13 17:10:00 +02:00
|
|
|
}
|
|
|
|
}
|