go-meteologix/geolocation.go
Winni Neessen a4f19380ff
Handle error in GetGeoLocationByName
Return error when fetching GeoLocations fails or the result is empty. This ensures that an error is properly returned and handled when either an error occurs during the API call or no locations are found for the given city name.
2023-06-27 17:15:07 +02:00

98 lines
3 KiB
Go

// SPDX-FileCopyrightText: 2023 Winni Neessen <wn@neessen.dev>
//
// SPDX-License-Identifier: MIT
package meteologix
import (
"encoding/json"
"errors"
"fmt"
"net/url"
"sort"
"strconv"
)
// OSMNominatimURL is the API endpoint URL for the OpenStreetMaps Nominatim API
const OSMNominatimURL = "https://nominatim.openstreetmap.org/search"
// ErrCityNotFound is returned if a requested city was not found in the OSM API
var ErrCityNotFound = errors.New("requested city not found in OSM Nominatim API")
// GeoLocation represent the GPS GeoLocation coordinates of a City
type GeoLocation struct {
// Importance is the OSM computed importance rank
Importance float64 `json:"importance"`
// Latitude represents the GPS Latitude coordinates of the requested City as Float
Latitude float64
// LatitudeString represents the GPS Latitude coordinates of the requested City as string
LatitudeString string `json:"lat"`
// Longitude represents the GPS Longitude coordinates of the requested City as Float
Longitude float64
// LongitudeString represents the GPS Longitude coordinates of the requested City as String
LongitudeString string `json:"lon"`
// Name represents the requested City
Name string `json:"display_name"`
// PlaceID is the OSM Nominatim internal database ID
PlaceID int64 `json:"place_id"`
}
// GetGeoLocationByName returns the GeoLocation with the highest importance based on
// the given City name
//
// This method makes use of the OSM Nominatim API
func (c *Client) GetGeoLocationByName(ci string) (GeoLocation, error) {
ga, err := c.GetGeoLocationsByName(ci)
if err != nil || len(ga) < 1 {
return GeoLocation{}, err
}
return ga[0], nil
}
// GetGeoLocationsByName returns a slice of GeoLocation based on the requested City name
// The returned slice will be sorted by Importance of the results with the highest
// importance as first entry
//
// This method makes use of the OSM Nominatim API
func (c *Client) GetGeoLocationsByName(ci string) ([]GeoLocation, error) {
ga := make([]GeoLocation, 0)
u, err := url.Parse(OSMNominatimURL)
if err != nil {
return ga, fmt.Errorf("failed to parse OSM Nominatim URL: %w", err)
}
uq := u.Query()
uq.Add("format", "json")
uq.Add("q", ci)
u.RawQuery = uq.Encode()
r, err := c.httpClient.Get(u.String())
if err != nil {
return ga, fmt.Errorf("OSM Nominatim API request failed: %w", err)
}
var la []GeoLocation
if err := json.Unmarshal(r, &la); err != nil {
return ga, fmt.Errorf("failed to unmarshal API response JSON: %w", err)
}
if len(la) < 1 {
return ga, ErrCityNotFound
}
for _, l := range la {
lat, err := strconv.ParseFloat(l.LatitudeString, 64)
if err != nil {
return ga, fmt.Errorf("failed to convert latitude string to float value: %w", err)
}
lon, err := strconv.ParseFloat(l.LongitudeString, 64)
if err != nil {
return ga, fmt.Errorf("failed to convert longitude string to float value: %w", err)
}
l.Latitude = lat
l.Longitude = lon
ga = append(ga, l)
}
sort.SliceStable(ga, func(i, j int) bool { return ga[i].Importance > ga[j].Importance })
return ga, nil
}