From 2ce4c28c34065e057cf33db7b3c4b84252eb98d3 Mon Sep 17 00:00:00 2001 From: Igor Chubin Date: Fri, 9 Dec 2022 21:02:10 +0100 Subject: [PATCH] Add Nominatim queries resolution initial support --- internal/geo/location/location.go | 49 ++++++++++++++++++++++ internal/geo/location/nominatim.go | 66 ++++++++++++++++++++++++++++++ 2 files changed, 115 insertions(+) create mode 100644 internal/geo/location/location.go create mode 100644 internal/geo/location/nominatim.go diff --git a/internal/geo/location/location.go b/internal/geo/location/location.go new file mode 100644 index 0000000..86fde7f --- /dev/null +++ b/internal/geo/location/location.go @@ -0,0 +1,49 @@ +package location + +import "github.com/chubin/wttr.in/internal/config" + +type Location struct { + Name string + Fullname string `json:"display_name"` + Lat string + Lon string +} + +type Provider interface { + Query(location string) (*Location, error) +} + +type Searcher struct { + providers []Provider +} + +// NewSearcher returns a new Searcher for the specified config. +func NewSearcher(config *config.Config) *Searcher { + providers := []Provider{} + for _, p := range config.Geo.Nominatim { + providers = append(providers, NewNominatim(p.Name, p.URL, p.Token)) + } + + return &Searcher{ + providers: providers, + } +} + +// Search makes queries through all known providers, +// and returns response, as soon as it is not nil. +// If all responses were nil, the last response is returned. +func (s *Searcher) Search(location string) (*Location, error) { + var ( + err error + result *Location + ) + + for _, p := range s.providers { + result, err = p.Query(location) + if result != nil && err == nil { + return result, nil + } + } + + return result, err +} diff --git a/internal/geo/location/nominatim.go b/internal/geo/location/nominatim.go new file mode 100644 index 0000000..fbb8a7f --- /dev/null +++ b/internal/geo/location/nominatim.go @@ -0,0 +1,66 @@ +package location + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "log" + "net/http" + "net/url" +) + +type Nominatim struct { + name string + url string + token string +} + +func NewNominatim(name, url, token string) *Nominatim { + return &Nominatim{ + name: name, + url: url, + token: token, + } +} + +func (n *Nominatim) Query(location string) (*Location, error) { + var ( + result []Location + + errResponse struct { + Error string + } + ) + + urlws := fmt.Sprintf( + "%s?q=%s&format=json&accept-language=native&limit=1&key=%s", + n.url, url.QueryEscape(location), n.token) + + log.Println(urlws) + resp, err := http.Get(urlws) + if err != nil { + return nil, fmt.Errorf("%s: %w", n.name, err) + } + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("%s: %w", n.name, err) + } + + err = json.Unmarshal(body, &errResponse) + if err == nil && errResponse.Error != "" { + return nil, fmt.Errorf("%s: %s", n.name, errResponse.Error) + } + + err = json.Unmarshal(body, &result) + if err != nil { + return nil, fmt.Errorf("%s: %w", n.name, err) + } + + if len(result) != 1 { + return nil, fmt.Errorf("%s: invalid response", n.name) + } + + return &result[0], nil + +}