Add opencage support

chubin/logging
Igor Chubin 2 years ago
parent 2e67874e04
commit 302b00ee7d

@ -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)
}
return data.Query(n, location)
}
urlws := fmt.Sprintf(
"%s?q=%s&format=json&accept-language=native&limit=1&key=%s",
n.url, url.QueryEscape(location), n.token)
func makeQuery(url string, result interface{}) error {
var errResponse struct {
Error string
}
log.Debugln("nominatim:", urlws)
resp, err := http.Get(urlws)
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
}

@ -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
}

@ -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…
Cancel
Save