mirror of
https://github.com/chubin/wttr.in
synced 2025-01-12 03:58:45 +00:00
Add opencage support
This commit is contained in:
parent
2e67874e04
commit
302b00ee7d
6 changed files with 123 additions and 35 deletions
|
@ -91,6 +91,10 @@ type Geo struct {
|
||||||
type Nominatim struct {
|
type Nominatim struct {
|
||||||
Name string
|
Name string
|
||||||
|
|
||||||
|
// Type describes the type of the location service.
|
||||||
|
// Supported types: iq.
|
||||||
|
Type string
|
||||||
|
|
||||||
URL string
|
URL string
|
||||||
|
|
||||||
Token string
|
Token string
|
||||||
|
@ -112,9 +116,16 @@ func Default() *Config {
|
||||||
Nominatim: []Nominatim{
|
Nominatim: []Nominatim{
|
||||||
{
|
{
|
||||||
Name: "locationiq",
|
Name: "locationiq",
|
||||||
|
Type: "iq",
|
||||||
URL: "https://eu1.locationiq.com/v1/search",
|
URL: "https://eu1.locationiq.com/v1/search",
|
||||||
Token: os.Getenv("NOMINATIM_LOCATIONIQ"),
|
Token: os.Getenv("NOMINATIM_LOCATIONIQ"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "opencage",
|
||||||
|
Type: "opencage",
|
||||||
|
URL: "https://api.opencagedata.com/geocode/v1/json",
|
||||||
|
Token: os.Getenv("NOMINATIM_OPENCAGE"),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Logging{
|
Logging{
|
||||||
|
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
|
||||||
|
|
||||||
"github.com/chubin/wttr.in/internal/types"
|
"github.com/chubin/wttr.in/internal/types"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
@ -15,69 +14,64 @@ type Nominatim struct {
|
||||||
name string
|
name string
|
||||||
url string
|
url string
|
||||||
token string
|
token string
|
||||||
|
typ string
|
||||||
}
|
}
|
||||||
|
|
||||||
type NominatimLocation struct {
|
type locationQuerier interface {
|
||||||
Name string `db:"name,key"`
|
Query(*Nominatim, string) (*Location, error)
|
||||||
Lat string `db:"lat"`
|
|
||||||
Lon string `db:"lon"`
|
|
||||||
//nolint:tagliatelle
|
|
||||||
Fullname string `db:"displayName" json:"display_name"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewNominatim(name, url, token string) *Nominatim {
|
func NewNominatim(name, typ, url, token string) *Nominatim {
|
||||||
return &Nominatim{
|
return &Nominatim{
|
||||||
name: name,
|
name: name,
|
||||||
url: url,
|
url: url,
|
||||||
token: token,
|
token: token,
|
||||||
|
typ: typ,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Nominatim) Query(location string) (*Location, error) {
|
func (n *Nominatim) Query(location string) (*Location, error) {
|
||||||
var (
|
var data locationQuerier
|
||||||
result []NominatimLocation
|
|
||||||
|
|
||||||
errResponse struct {
|
switch n.typ {
|
||||||
|
case "iq":
|
||||||
|
data = &locationIQ{}
|
||||||
|
case "opencage":
|
||||||
|
data = &locationOpenCage{}
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("%s: %w", n.name, types.ErrUnknownLocationService)
|
||||||
|
}
|
||||||
|
|
||||||
|
return data.Query(n, location)
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeQuery(url string, result interface{}) error {
|
||||||
|
var errResponse struct {
|
||||||
Error string
|
Error string
|
||||||
}
|
}
|
||||||
)
|
|
||||||
|
|
||||||
urlws := fmt.Sprintf(
|
log.Debugln("nominatim:", url)
|
||||||
"%s?q=%s&format=json&accept-language=native&limit=1&key=%s",
|
resp, err := http.Get(url)
|
||||||
n.url, url.QueryEscape(location), n.token)
|
|
||||||
|
|
||||||
log.Debugln("nominatim:", urlws)
|
|
||||||
resp, err := http.Get(urlws)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s: %w", n.name, err)
|
return err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s: %w", n.name, err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = json.Unmarshal(body, &errResponse)
|
err = json.Unmarshal(body, &errResponse)
|
||||||
if err == nil && errResponse.Error != "" {
|
if err == nil && errResponse.Error != "" {
|
||||||
return nil, fmt.Errorf("%w: %s: %s", types.ErrUpstream, n.name, errResponse.Error)
|
return fmt.Errorf("%w: %s", types.ErrUpstream, errResponse.Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugln("nominatim: response: ", string(body))
|
log.Debugln("nominatim: response: ", string(body))
|
||||||
err = json.Unmarshal(body, &result)
|
err = json.Unmarshal(body, &result)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s: %w", n.name, err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(result) != 1 {
|
return nil
|
||||||
return nil, fmt.Errorf("%w: %s: invalid response", types.ErrUpstream, n.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
nl := &result[0]
|
|
||||||
|
|
||||||
return &Location{
|
|
||||||
Lat: nl.Lat,
|
|
||||||
Lon: nl.Lon,
|
|
||||||
Fullname: nl.Fullname,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
39
internal/geo/location/nominatim_locationiq.go
Normal file
39
internal/geo/location/nominatim_locationiq.go
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
package location
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/chubin/wttr.in/internal/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type locationIQ []struct {
|
||||||
|
Name string `db:"name,key"`
|
||||||
|
Lat string `db:"lat"`
|
||||||
|
Lon string `db:"lon"`
|
||||||
|
//nolint:tagliatelle
|
||||||
|
Fullname string `db:"displayName" json:"display_name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (data *locationIQ) Query(n *Nominatim, location string) (*Location, error) {
|
||||||
|
url := fmt.Sprintf(
|
||||||
|
"%s?q=%s&format=json&language=native&limit=1&key=%s",
|
||||||
|
n.url, url.QueryEscape(location), n.token)
|
||||||
|
|
||||||
|
err := makeQuery(url, data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s: %w", n.name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(*data) != 1 {
|
||||||
|
return nil, fmt.Errorf("%w: %s: invalid response", types.ErrUpstream, n.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
nl := &(*data)[0]
|
||||||
|
|
||||||
|
return &Location{
|
||||||
|
Lat: nl.Lat,
|
||||||
|
Lon: nl.Lon,
|
||||||
|
Fullname: nl.Fullname,
|
||||||
|
}, nil
|
||||||
|
}
|
42
internal/geo/location/nominatim_opencage.go
Normal file
42
internal/geo/location/nominatim_opencage.go
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
package location
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/chubin/wttr.in/internal/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type locationOpenCage struct {
|
||||||
|
Results []struct {
|
||||||
|
Name string `db:"name,key"`
|
||||||
|
Geometry struct {
|
||||||
|
Lat float64 `db:"lat"`
|
||||||
|
Lng float64 `db:"lng"`
|
||||||
|
}
|
||||||
|
Fullname string `json:"formatted"`
|
||||||
|
} `json:"results"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (data *locationOpenCage) Query(n *Nominatim, location string) (*Location, error) {
|
||||||
|
url := fmt.Sprintf(
|
||||||
|
"%s?q=%s&language=native&limit=1&key=%s",
|
||||||
|
n.url, url.QueryEscape(location), n.token)
|
||||||
|
|
||||||
|
err := makeQuery(url, data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s: %w", n.name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(data.Results) != 1 {
|
||||||
|
return nil, fmt.Errorf("%w: %s: invalid response", types.ErrUpstream, n.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
nl := data.Results[0]
|
||||||
|
|
||||||
|
return &Location{
|
||||||
|
Lat: fmt.Sprint(nl.Geometry.Lat),
|
||||||
|
Lon: fmt.Sprint(nl.Geometry.Lng),
|
||||||
|
Fullname: nl.Fullname,
|
||||||
|
}, nil
|
||||||
|
}
|
|
@ -14,7 +14,7 @@ type Searcher struct {
|
||||||
func NewSearcher(config *config.Config) *Searcher {
|
func NewSearcher(config *config.Config) *Searcher {
|
||||||
providers := []Provider{}
|
providers := []Provider{}
|
||||||
for _, p := range config.Geo.Nominatim {
|
for _, p := range config.Geo.Nominatim {
|
||||||
providers = append(providers, NewNominatim(p.Name, p.URL, p.Token))
|
providers = append(providers, NewNominatim(p.Name, p.Type, p.URL, p.Token))
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Searcher{
|
return &Searcher{
|
||||||
|
|
|
@ -9,4 +9,6 @@ var (
|
||||||
|
|
||||||
// ErrNoServersConfigured means that there are no servers to run.
|
// ErrNoServersConfigured means that there are no servers to run.
|
||||||
ErrNoServersConfigured = errors.New("no servers configured")
|
ErrNoServersConfigured = errors.New("no servers configured")
|
||||||
|
|
||||||
|
ErrUnknownLocationService = errors.New("unknown location service")
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in a new issue