mirror of
https://github.com/chubin/wttr.in
synced 2025-01-11 19:48: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 {
|
||||
Name string
|
||||
|
||||
// Type describes the type of the location service.
|
||||
// Supported types: iq.
|
||||
Type string
|
||||
|
||||
URL string
|
||||
|
||||
Token string
|
||||
|
@ -112,9 +116,16 @@ func Default() *Config {
|
|||
Nominatim: []Nominatim{
|
||||
{
|
||||
Name: "locationiq",
|
||||
Type: "iq",
|
||||
URL: "https://eu1.locationiq.com/v1/search",
|
||||
Token: os.Getenv("NOMINATIM_LOCATIONIQ"),
|
||||
},
|
||||
{
|
||||
Name: "opencage",
|
||||
Type: "opencage",
|
||||
URL: "https://api.opencagedata.com/geocode/v1/json",
|
||||
Token: os.Getenv("NOMINATIM_OPENCAGE"),
|
||||
},
|
||||
},
|
||||
},
|
||||
Logging{
|
||||
|
|
|
@ -5,7 +5,6 @@ import (
|
|||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/chubin/wttr.in/internal/types"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
@ -15,69 +14,64 @@ type Nominatim struct {
|
|||
name string
|
||||
url string
|
||||
token string
|
||||
typ string
|
||||
}
|
||||
|
||||
type NominatimLocation struct {
|
||||
Name string `db:"name,key"`
|
||||
Lat string `db:"lat"`
|
||||
Lon string `db:"lon"`
|
||||
//nolint:tagliatelle
|
||||
Fullname string `db:"displayName" json:"display_name"`
|
||||
type locationQuerier interface {
|
||||
Query(*Nominatim, string) (*Location, error)
|
||||
}
|
||||
|
||||
func NewNominatim(name, url, token string) *Nominatim {
|
||||
func NewNominatim(name, typ, url, token string) *Nominatim {
|
||||
return &Nominatim{
|
||||
name: name,
|
||||
url: url,
|
||||
token: token,
|
||||
typ: typ,
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Nominatim) Query(location string) (*Location, error) {
|
||||
var (
|
||||
result []NominatimLocation
|
||||
var data locationQuerier
|
||||
|
||||
errResponse struct {
|
||||
Error string
|
||||
}
|
||||
)
|
||||
switch n.typ {
|
||||
case "iq":
|
||||
data = &locationIQ{}
|
||||
case "opencage":
|
||||
data = &locationOpenCage{}
|
||||
default:
|
||||
return nil, fmt.Errorf("%s: %w", n.name, types.ErrUnknownLocationService)
|
||||
}
|
||||
|
||||
urlws := fmt.Sprintf(
|
||||
"%s?q=%s&format=json&accept-language=native&limit=1&key=%s",
|
||||
n.url, url.QueryEscape(location), n.token)
|
||||
return data.Query(n, location)
|
||||
}
|
||||
|
||||
log.Debugln("nominatim:", urlws)
|
||||
resp, err := http.Get(urlws)
|
||||
func makeQuery(url string, result interface{}) error {
|
||||
var errResponse struct {
|
||||
Error string
|
||||
}
|
||||
|
||||
log.Debugln("nominatim:", url)
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", n.name, err)
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", n.name, err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(body, &errResponse)
|
||||
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))
|
||||
err = json.Unmarshal(body, &result)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", n.name, err)
|
||||
return err
|
||||
}
|
||||
|
||||
if len(result) != 1 {
|
||||
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
|
||||
return 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 {
|
||||
providers := []Provider{}
|
||||
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{
|
||||
|
|
|
@ -9,4 +9,6 @@ var (
|
|||
|
||||
// ErrNoServersConfigured means that there are no servers to run.
|
||||
ErrNoServersConfigured = errors.New("no servers configured")
|
||||
|
||||
ErrUnknownLocationService = errors.New("unknown location service")
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue