├── .gitignore
├── go.mod
├── go.sum
├── LICENSE
├── chained
├── geocoder.go
└── geocoder_test.go
├── data
├── geocoder.go
└── geocoder_test.go
├── cached
├── geocoder.go
└── geocoder_test.go
├── geocoder.go
├── osm
└── osm.go
├── geocod
├── geocoder.go
└── geocoder_test.go
├── mapquest
├── nominatim
│ ├── geocoder.go
│ └── geocoder_test.go
└── open
│ ├── geocoder.go
│ └── geocoder_test.go
├── openstreetmap
├── geocoder.go
└── geocoder_test.go
├── pickpoint
├── geocoder.go
└── geocoder_test.go
├── bing
├── geocoder.go
└── geocoder_test.go
├── opencage
├── geocoder.go
└── geocoder_test.go
├── arcgis
├── geocoder.go
└── geocoder_test.go
├── mapzen
├── geocoder.go
└── geocoder_test.go
├── locationiq
├── geocoder.go
└── geocoder_test.go
├── here
├── search
│ ├── geocoder.go
│ └── geocoder_test.go
└── geocoder.go
├── baidu
├── baidu_test.go
└── baidu.go
├── tomtom
├── geocoder.go
└── geocoder_test.go
├── mapbox
└── geocoder.go
├── frenchapigouv
├── geocoder.go
└── geocoder_test.go
├── amap
├── amap_test.go
└── amap.go
├── google
├── geocoder.go
└── geocoder_test.go
├── http_geocoder.go
├── yandex
└── geocoder.go
├── examples
└── geocoder_example.go
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/codingsince1985/geo-golang
2 |
3 | go 1.24.1
4 |
5 | require (
6 | github.com/patrickmn/go-cache v2.1.0+incompatible
7 | github.com/stretchr/testify v1.10.0
8 | )
9 |
10 | require (
11 | github.com/davecgh/go-spew v1.1.1 // indirect
12 | github.com/pmezard/go-difflib v1.0.0 // indirect
13 | gopkg.in/yaml.v3 v3.0.1 // indirect
14 | )
15 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3 | github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
4 | github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
5 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
6 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
7 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
8 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
9 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
10 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
11 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
12 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
13 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014-2016 codingsince1985 / Jerry Zhao
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/chained/geocoder.go:
--------------------------------------------------------------------------------
1 | package chained
2 |
3 | import (
4 | "github.com/codingsince1985/geo-golang"
5 | )
6 |
7 | type chainedGeocoder struct{ Geocoders []geo.Geocoder }
8 |
9 | // Geocoder creates a chain of Geocoders to lookup address and fallback on
10 | func Geocoder(geocoders ...geo.Geocoder) geo.Geocoder { return chainedGeocoder{Geocoders: geocoders} }
11 |
12 | // Geocode returns location for address
13 | func (c chainedGeocoder) Geocode(address string) (*geo.Location, error) {
14 | // Geocode address by each geocoder until we get a real location response
15 | for i := range c.Geocoders {
16 | if l, err := c.Geocoders[i].Geocode(address); err == nil && l != nil {
17 | return l, nil
18 | }
19 | // skip error and try the next geocoder
20 | continue
21 | }
22 | // No geocoders found a result
23 | return nil, nil
24 | }
25 |
26 | // ReverseGeocode returns address for location
27 | func (c chainedGeocoder) ReverseGeocode(lat, lng float64) (*geo.Address, error) {
28 | // Geocode address by each geocoder until we get a real location response
29 | for i := range c.Geocoders {
30 | if addr, err := c.Geocoders[i].ReverseGeocode(lat, lng); err == nil && addr != nil {
31 | return addr, nil
32 | }
33 | // skip error and try the next geocoder
34 | continue
35 | }
36 | // No geocoders found a result
37 | return nil, nil
38 | }
39 |
--------------------------------------------------------------------------------
/data/geocoder.go:
--------------------------------------------------------------------------------
1 | package data
2 |
3 | import (
4 | "github.com/codingsince1985/geo-golang"
5 | )
6 |
7 | // AddressToLocation maps address string to location (lat, long)
8 | type AddressToLocation map[geo.Address]geo.Location
9 |
10 | // LocationToAddress maps location(lat,lng) to address
11 | type LocationToAddress map[geo.Location]geo.Address
12 |
13 | // dataGeocoder represents geo data in memory
14 | type dataGeocoder struct {
15 | AddressToLocation
16 | LocationToAddress
17 | }
18 |
19 | // Geocoder constructs data geocoder
20 | func Geocoder(addressToLocation AddressToLocation, LocationToAddress LocationToAddress) geo.Geocoder {
21 | return dataGeocoder{
22 | AddressToLocation: addressToLocation,
23 | LocationToAddress: LocationToAddress,
24 | }
25 | }
26 |
27 | // Geocode returns location for address
28 | func (d dataGeocoder) Geocode(address string) (*geo.Location, error) {
29 | addr := geo.Address{
30 | FormattedAddress: address,
31 | }
32 | if l, ok := d.AddressToLocation[addr]; ok {
33 | return &l, nil
34 | }
35 |
36 | return nil, nil
37 | }
38 |
39 | // ReverseGeocode returns address for location
40 | func (d dataGeocoder) ReverseGeocode(lat, lng float64) (*geo.Address, error) {
41 | if address, ok := d.LocationToAddress[geo.Location{Lat: lat, Lng: lng}]; ok {
42 | return &address, nil
43 | }
44 | return nil, nil
45 | }
46 |
--------------------------------------------------------------------------------
/data/geocoder_test.go:
--------------------------------------------------------------------------------
1 | package data_test
2 |
3 | import (
4 | "strings"
5 | "testing"
6 |
7 | "github.com/codingsince1985/geo-golang"
8 | "github.com/codingsince1985/geo-golang/data"
9 | "github.com/stretchr/testify/assert"
10 | )
11 |
12 | var (
13 | addressFixture = geo.Address{
14 | FormattedAddress: "64 Elizabeth Street, Melbourne, Victoria 3000, Australia",
15 | }
16 | locationFixture = geo.Location{
17 | Lat: -37.814107,
18 | Lng: 144.96328,
19 | }
20 | geocoder = data.Geocoder(
21 | data.AddressToLocation{
22 | addressFixture: locationFixture,
23 | },
24 | data.LocationToAddress{
25 | locationFixture: addressFixture,
26 | },
27 | )
28 | )
29 |
30 | func TestGeocode(t *testing.T) {
31 | location, err := geocoder.Geocode(addressFixture.FormattedAddress)
32 | assert.NoError(t, err)
33 | assert.Equal(t, geo.Location{Lat: -37.814107, Lng: 144.96328}, *location)
34 | }
35 |
36 | func TestReverseGeocode(t *testing.T) {
37 | address, err := geocoder.ReverseGeocode(locationFixture.Lat, locationFixture.Lng)
38 | assert.Nil(t, err)
39 | assert.NotNil(t, address)
40 | assert.True(t, strings.Contains(address.FormattedAddress, "Melbourne, Victoria 3000, Australia"))
41 | }
42 |
43 | func TestReverseGeocodeWithNoResult(t *testing.T) {
44 | addr, err := geocoder.ReverseGeocode(1, 2)
45 | assert.Nil(t, err)
46 | assert.Nil(t, addr)
47 | }
48 |
--------------------------------------------------------------------------------
/cached/geocoder.go:
--------------------------------------------------------------------------------
1 | package cached
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/codingsince1985/geo-golang"
7 | "github.com/patrickmn/go-cache"
8 | )
9 |
10 | type cachedGeocoder struct {
11 | Geocoder geo.Geocoder
12 | Cache *cache.Cache
13 | }
14 |
15 | // Geocoder creates a chain of Geocoders to lookup address and fallback on
16 | func Geocoder(geocoder geo.Geocoder, cache *cache.Cache) geo.Geocoder {
17 | return cachedGeocoder{Geocoder: geocoder, Cache: cache}
18 | }
19 |
20 | // Geocode returns location for address
21 | func (c cachedGeocoder) Geocode(address string) (*geo.Location, error) {
22 | // Check if we've cached this response
23 | if cachedLoc, found := c.Cache.Get(address); found {
24 | return cachedLoc.(*geo.Location), nil
25 | }
26 |
27 | if loc, err := c.Geocoder.Geocode(address); err != nil {
28 | return loc, err
29 | } else {
30 | c.Cache.Set(address, loc, 0)
31 | return loc, nil
32 | }
33 | }
34 |
35 | // ReverseGeocode returns address for location
36 | func (c cachedGeocoder) ReverseGeocode(lat, lng float64) (*geo.Address, error) {
37 | // Check if we've cached this response
38 | locKey := fmt.Sprintf("geo.Location{%f,%f}", lat, lng)
39 | if cachedAddr, found := c.Cache.Get(locKey); found {
40 | return cachedAddr.(*geo.Address), nil
41 | }
42 |
43 | if addr, err := c.Geocoder.ReverseGeocode(lat, lng); err != nil {
44 | return nil, err
45 | } else {
46 | c.Cache.Set(locKey, addr, 0)
47 | return addr, nil
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/geocoder.go:
--------------------------------------------------------------------------------
1 | // Package geo is a generic framework to develop geocode/reverse geocode clients
2 | package geo
3 |
4 | import (
5 | "io"
6 | "log"
7 | )
8 |
9 | // Geocoder can look up (lat, long) by address and address by (lat, long)
10 | type Geocoder interface {
11 | Geocode(address string) (*Location, error)
12 | ReverseGeocode(lat, lng float64) (*Address, error)
13 | }
14 |
15 | // Location is the output of Geocode
16 | type Location struct {
17 | Lat, Lng float64
18 | }
19 |
20 | // Address is returned by ReverseGeocode.
21 | // This is a structured representation of an address, including its flat representation
22 | type Address struct {
23 | FormattedAddress string
24 | Street string
25 | HouseNumber string
26 | Suburb string
27 | Postcode string
28 | State string
29 | StateCode string
30 | StateDistrict string
31 | County string
32 | Country string
33 | CountryCode string
34 | City string
35 | }
36 |
37 | // ErrLogger is an implementation of StdLogger that geo uses to log its error messages.
38 | var ErrLogger StdLogger = log.New(io.Discard, "[Geo][Err]", log.LstdFlags)
39 |
40 | // DebugLogger is an implementation of StdLogger that geo uses to log its debug messages.
41 | var DebugLogger StdLogger = log.New(io.Discard, "[Geo][Debug]", log.LstdFlags)
42 |
43 | // StdLogger is a interface for logging libraries.
44 | type StdLogger interface {
45 | Printf(string, ...interface{})
46 | }
47 |
--------------------------------------------------------------------------------
/osm/osm.go:
--------------------------------------------------------------------------------
1 | // Package osm provides common types for OpenStreetMap used by various providers
2 | // and some helper functions to reduce code repetition across specific client implementations.
3 | package osm
4 |
5 | // Address contains address fields specific to OpenStreetMap
6 | type Address struct {
7 | HouseNumber string `json:"house_number"`
8 | Road string `json:"road"`
9 | Pedestrian string `json:"pedestrian"`
10 | Footway string `json:"footway"`
11 | Cycleway string `json:"cycleway"`
12 | Highway string `json:"highway"`
13 | Path string `json:"path"`
14 | Suburb string `json:"suburb"`
15 | City string `json:"city"`
16 | Town string `json:"town"`
17 | Village string `json:"village"`
18 | Hamlet string `json:"hamlet"`
19 | County string `json:"county"`
20 | Country string `json:"country"`
21 | CountryCode string `json:"country_code"`
22 | State string `json:"state"`
23 | StateDistrict string `json:"state_district"`
24 | Postcode string `json:"postcode"`
25 | }
26 |
27 | // Locality checks different fields for the locality name
28 | func (a Address) Locality() string {
29 | var locality string
30 |
31 | if a.City != "" {
32 | locality = a.City
33 | } else if a.Town != "" {
34 | locality = a.Town
35 | } else if a.Village != "" {
36 | locality = a.Village
37 | } else if a.Hamlet != "" {
38 | locality = a.Hamlet
39 | }
40 |
41 | return locality
42 | }
43 |
44 | // Street checks different fields for the street name
45 | func (a Address) Street() string {
46 | var street string
47 |
48 | if a.Road != "" {
49 | street = a.Road
50 | } else if a.Pedestrian != "" {
51 | street = a.Pedestrian
52 | } else if a.Path != "" {
53 | street = a.Path
54 | } else if a.Cycleway != "" {
55 | street = a.Cycleway
56 | } else if a.Footway != "" {
57 | street = a.Footway
58 | } else if a.Highway != "" {
59 | street = a.Highway
60 | }
61 |
62 | return street
63 | }
64 |
--------------------------------------------------------------------------------
/geocod/geocoder.go:
--------------------------------------------------------------------------------
1 | package geocod
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 |
7 | geo "github.com/codingsince1985/geo-golang"
8 | )
9 |
10 | type (
11 | baseURL string
12 | geocodeResponse struct {
13 | Results []struct {
14 | Components struct {
15 | Number string
16 | Street string
17 | City string
18 | County string
19 | State string
20 | Zip string
21 | Country string
22 | } `json:"address_components"`
23 | Address string `json:"formatted_address"`
24 | Location struct {
25 | Lat float64
26 | Lng float64
27 | }
28 | }
29 | }
30 | )
31 |
32 | // Geocoder constructs Geocodio geocoder
33 | func Geocoder(key string, baseURLs ...string) geo.Geocoder {
34 | return geo.HTTPGeocoder{
35 | EndpointBuilder: baseURL(getUrl(key, baseURLs...)),
36 | ResponseParserFactory: func() geo.ResponseParser { return &geocodeResponse{} },
37 | }
38 | }
39 |
40 | func getUrl(key string, baseURLs ...string) string {
41 | if len(baseURLs) > 0 {
42 | return baseURLs[0]
43 | }
44 | return "https://api.geocod.io/v1/*&api_key=" + key
45 | }
46 |
47 | func (b baseURL) GeocodeURL(address string) string {
48 | params := fmt.Sprintf("geocode?q=%s", address)
49 | url := strings.Replace(string(b), "*", params, 1)
50 | return url
51 | }
52 |
53 | func (b baseURL) ReverseGeocodeURL(l geo.Location) string {
54 | params := fmt.Sprintf("reverse?q=%f,%f", l.Lat, l.Lng)
55 | url := strings.Replace(string(b), "*", params, 1)
56 | return url
57 | }
58 |
59 | func (r *geocodeResponse) Location() (*geo.Location, error) {
60 | if len(r.Results) == 0 {
61 | return nil, nil
62 | }
63 |
64 | loc := r.Results[0].Location
65 | return &geo.Location{Lat: loc.Lat, Lng: loc.Lng}, nil
66 | }
67 |
68 | func (r *geocodeResponse) Address() (*geo.Address, error) {
69 | if len(r.Results) == 0 {
70 | return nil, nil
71 | }
72 |
73 | r0 := r.Results[0]
74 | c := r0.Components
75 | addr := &geo.Address{
76 | FormattedAddress: r0.Address,
77 | Street: c.Street,
78 | HouseNumber: c.Number,
79 | Postcode: c.Zip,
80 | State: c.State,
81 | StateCode: c.State,
82 | CountryCode: c.Country,
83 | }
84 |
85 | return addr, nil
86 | }
87 |
--------------------------------------------------------------------------------
/mapquest/nominatim/geocoder.go:
--------------------------------------------------------------------------------
1 | // Package nominatim is a geo-golang based MapRequest Nominatim geocode/reverse geocode client
2 | package nominatim
3 |
4 | import (
5 | "fmt"
6 | "strings"
7 |
8 | "github.com/codingsince1985/geo-golang"
9 | "github.com/codingsince1985/geo-golang/osm"
10 | )
11 |
12 | type (
13 | baseURL string
14 |
15 | geocodeResponse struct {
16 | DisplayName string `json:"display_name"`
17 | Lat, Lon, Error string
18 | Addr osm.Address `json:"address"`
19 | }
20 | )
21 |
22 | var key string
23 |
24 | // Geocoder constructs MapRequest Nominatim geocoder
25 | func Geocoder(k string, baseURLs ...string) geo.Geocoder {
26 | key = k
27 | return geo.HTTPGeocoder{
28 | EndpointBuilder: baseURL(getURL(baseURLs...)),
29 | ResponseParserFactory: func() geo.ResponseParser { return &geocodeResponse{} },
30 | }
31 | }
32 |
33 | func getURL(baseURLs ...string) string {
34 | if len(baseURLs) > 0 {
35 | return baseURLs[0]
36 | }
37 | return "http://open.mapquestapi.com/nominatim/v1/"
38 | }
39 |
40 | func (b baseURL) GeocodeURL(address string) string {
41 | return string(b) + "search.php?key=" + key + "&format=json&limit=1&q=" + address
42 | }
43 |
44 | func (b baseURL) ReverseGeocodeURL(l geo.Location) string {
45 | return string(b) + "reverse.php?key=" + key + fmt.Sprintf("&format=json&lat=%f&lon=%f", l.Lat, l.Lng)
46 | }
47 |
48 | func (r *geocodeResponse) Location() (*geo.Location, error) {
49 | if r.Error != "" {
50 | return nil, fmt.Errorf("geocode error: %s", r.Error)
51 | }
52 |
53 | return &geo.Location{
54 | Lat: geo.ParseFloat(r.Lat),
55 | Lng: geo.ParseFloat(r.Lon),
56 | }, nil
57 | }
58 |
59 | func (r *geocodeResponse) Address() (*geo.Address, error) {
60 | if r.Error != "" {
61 | return nil, fmt.Errorf("reverse geocode error: %s", r.Error)
62 | }
63 |
64 | return &geo.Address{
65 | FormattedAddress: r.DisplayName,
66 | HouseNumber: r.Addr.HouseNumber,
67 | Street: r.Addr.Street(),
68 | Suburb: r.Addr.Suburb,
69 | City: r.Addr.Locality(),
70 | State: r.Addr.State,
71 | County: r.Addr.County,
72 | Postcode: r.Addr.Postcode,
73 | Country: r.Addr.Country,
74 | CountryCode: strings.ToUpper(r.Addr.CountryCode),
75 | }, nil
76 | }
77 |
--------------------------------------------------------------------------------
/openstreetmap/geocoder.go:
--------------------------------------------------------------------------------
1 | // Package openstreetmap is a geo-golang based OpenStreetMap geocode/reverse geocode client
2 | package openstreetmap
3 |
4 | import (
5 | "fmt"
6 | "strings"
7 |
8 | "github.com/codingsince1985/geo-golang"
9 | "github.com/codingsince1985/geo-golang/osm"
10 | )
11 |
12 | type (
13 | baseURL string
14 | geocodeResponse struct {
15 | DisplayName string `json:"display_name"`
16 | Lat string
17 | Lon string
18 | Error string
19 | Addr osm.Address `json:"address"`
20 | }
21 | )
22 |
23 | // Geocoder constructs OpenStreetMap geocoder
24 | func Geocoder() geo.Geocoder { return GeocoderWithURL("https://nominatim.openstreetmap.org/") }
25 |
26 | // GeocoderWithURL constructs OpenStreetMap geocoder using a custom installation of Nominatim
27 | func GeocoderWithURL(nominatimURL string) geo.Geocoder {
28 | return geo.HTTPGeocoder{
29 | EndpointBuilder: baseURL(nominatimURL),
30 | ResponseParserFactory: func() geo.ResponseParser { return &geocodeResponse{} },
31 | }
32 | }
33 |
34 | func (b baseURL) GeocodeURL(address string) string {
35 | return string(b) + "search?format=json&limit=1&q=" + address
36 | }
37 |
38 | func (b baseURL) ReverseGeocodeURL(l geo.Location) string {
39 | return string(b) + "reverse?" + fmt.Sprintf("format=json&lat=%f&lon=%f", l.Lat, l.Lng)
40 | }
41 |
42 | func (r *geocodeResponse) Location() (*geo.Location, error) {
43 | if r.Error != "" {
44 | return nil, fmt.Errorf("geocoding error: %s", r.Error)
45 | }
46 | if r.Lat == "" && r.Lon == "" {
47 | return nil, nil
48 | }
49 |
50 | return &geo.Location{
51 | Lat: geo.ParseFloat(r.Lat),
52 | Lng: geo.ParseFloat(r.Lon),
53 | }, nil
54 | }
55 |
56 | func (r *geocodeResponse) Address() (*geo.Address, error) {
57 | if r.Error != "" {
58 | return nil, fmt.Errorf("reverse geocoding error: %s", r.Error)
59 | }
60 |
61 | return &geo.Address{
62 | FormattedAddress: r.DisplayName,
63 | HouseNumber: r.Addr.HouseNumber,
64 | Street: r.Addr.Street(),
65 | Postcode: r.Addr.Postcode,
66 | City: r.Addr.Locality(),
67 | Suburb: r.Addr.Suburb,
68 | State: r.Addr.State,
69 | Country: r.Addr.Country,
70 | CountryCode: strings.ToUpper(r.Addr.CountryCode),
71 | }, nil
72 | }
73 |
--------------------------------------------------------------------------------
/pickpoint/geocoder.go:
--------------------------------------------------------------------------------
1 | // Package pickpoint is a geo-golang based PickPoint geocode/reverse geocode client
2 | package pickpoint
3 |
4 | import (
5 | "fmt"
6 | "strings"
7 |
8 | "github.com/codingsince1985/geo-golang"
9 | "github.com/codingsince1985/geo-golang/osm"
10 | )
11 |
12 | type (
13 | baseURL string
14 | geocodeResponse struct {
15 | DisplayName string `json:"display_name"`
16 | Lat string
17 | Lon string
18 | Error string
19 | Addr osm.Address `json:"address"`
20 | }
21 | )
22 |
23 | var key string
24 |
25 | // Geocoder constructs PickPoint geocoder
26 | func Geocoder(apiKey string, baseURLs ...string) geo.Geocoder {
27 | key = apiKey
28 | return geo.HTTPGeocoder{
29 | EndpointBuilder: baseURL(getURL(baseURLs...)),
30 | ResponseParserFactory: func() geo.ResponseParser { return &geocodeResponse{} },
31 | }
32 | }
33 |
34 | func getURL(baseURLs ...string) string {
35 | if len(baseURLs) > 0 {
36 | return baseURLs[0]
37 | }
38 | return "https://api.pickpoint.io/v1"
39 | }
40 |
41 | func (b baseURL) GeocodeURL(address string) string {
42 | return string(b) + fmt.Sprintf("/forward?key=%s&limit=1&q=%s", key, address)
43 | }
44 |
45 | func (b baseURL) ReverseGeocodeURL(l geo.Location) string {
46 | return string(b) + fmt.Sprintf("/reverse?key=%s&lat=%f&lon=%f", key, l.Lat, l.Lng)
47 | }
48 |
49 | func (r *geocodeResponse) Location() (*geo.Location, error) {
50 | if r.Error != "" {
51 | return nil, fmt.Errorf("geocoding error: %s", r.Error)
52 | }
53 | if r.Lat == "" && r.Lon == "" {
54 | return nil, nil
55 | }
56 |
57 | return &geo.Location{
58 | Lat: geo.ParseFloat(r.Lat),
59 | Lng: geo.ParseFloat(r.Lon),
60 | }, nil
61 | }
62 |
63 | func (r *geocodeResponse) Address() (*geo.Address, error) {
64 | if r.Error != "" {
65 | return nil, fmt.Errorf("reverse geocoding error: %s", r.Error)
66 | }
67 |
68 | return &geo.Address{
69 | FormattedAddress: r.DisplayName,
70 | HouseNumber: r.Addr.HouseNumber,
71 | Street: r.Addr.Street(),
72 | Postcode: r.Addr.Postcode,
73 | City: r.Addr.Locality(),
74 | Suburb: r.Addr.Suburb,
75 | State: r.Addr.State,
76 | Country: r.Addr.Country,
77 | CountryCode: strings.ToUpper(r.Addr.CountryCode),
78 | }, nil
79 | }
80 |
--------------------------------------------------------------------------------
/bing/geocoder.go:
--------------------------------------------------------------------------------
1 | // Package bing is a geo-golang based Microsoft Bing geocode/reverse geocode client
2 | package bing
3 |
4 | import (
5 | "errors"
6 | "fmt"
7 | "strings"
8 |
9 | "github.com/codingsince1985/geo-golang"
10 | )
11 |
12 | type (
13 | baseURL string
14 | geocodeResponse struct {
15 | ResourceSets []struct {
16 | Resources []struct {
17 | Point struct {
18 | Coordinates []float64
19 | }
20 | Address struct {
21 | FormattedAddress string
22 | AddressLine string
23 | AdminDistrict string
24 | AdminDistrict2 string
25 | CountryRegion string
26 | Locality string
27 | PostalCode string
28 | }
29 | }
30 | }
31 | ErrorDetails []string
32 | }
33 | )
34 |
35 | // Geocoder constructs Bing geocoder
36 | func Geocoder(key string, baseURLs ...string) geo.Geocoder {
37 | return geo.HTTPGeocoder{
38 | EndpointBuilder: baseURL(getURL(key, baseURLs...)),
39 | ResponseParserFactory: func() geo.ResponseParser { return &geocodeResponse{} },
40 | }
41 | }
42 |
43 | func getURL(key string, baseURLs ...string) string {
44 | if len(baseURLs) > 0 {
45 | return baseURLs[0]
46 | }
47 | return "http://dev.virtualearth.net/REST/v1/Locations*key=" + key
48 | }
49 |
50 | func (b baseURL) GeocodeURL(address string) string {
51 | return strings.Replace(string(b), "*", "?q="+address+"&", 1)
52 | }
53 |
54 | func (b baseURL) ReverseGeocodeURL(l geo.Location) string {
55 | return strings.Replace(string(b), "*", fmt.Sprintf("/%f,%f?", l.Lat, l.Lng), 1)
56 | }
57 |
58 | func (r *geocodeResponse) Location() (*geo.Location, error) {
59 | if len(r.ResourceSets) <= 0 || len(r.ResourceSets[0].Resources) <= 0 {
60 | return nil, nil
61 | }
62 | c := r.ResourceSets[0].Resources[0].Point.Coordinates
63 | return &geo.Location{
64 | Lat: c[0],
65 | Lng: c[1],
66 | }, nil
67 | }
68 |
69 | func (r *geocodeResponse) Address() (*geo.Address, error) {
70 | if len(r.ErrorDetails) > 0 {
71 | return nil, errors.New(strings.Join(r.ErrorDetails, " "))
72 | }
73 | if len(r.ResourceSets) <= 0 || len(r.ResourceSets[0].Resources) <= 0 {
74 | return nil, nil
75 | }
76 |
77 | a := r.ResourceSets[0].Resources[0].Address
78 | return &geo.Address{
79 | FormattedAddress: a.FormattedAddress,
80 | Street: a.AddressLine,
81 | City: a.Locality,
82 | Postcode: a.PostalCode,
83 | Country: a.CountryRegion,
84 | }, nil
85 | }
86 |
--------------------------------------------------------------------------------
/opencage/geocoder.go:
--------------------------------------------------------------------------------
1 | // Package opencage is a geo-golang based OpenCage geocode/reverse geocode client
2 | package opencage
3 |
4 | import (
5 | "fmt"
6 | "strings"
7 |
8 | "github.com/codingsince1985/geo-golang"
9 | "github.com/codingsince1985/geo-golang/osm"
10 | )
11 |
12 | type (
13 | baseURL string
14 |
15 | geocodeResponse struct {
16 | Results []struct {
17 | Formatted string
18 | Geometry geo.Location
19 | Components osm.Address
20 | }
21 | Status struct {
22 | Code int
23 | Message string
24 | }
25 | }
26 | )
27 |
28 | // Geocoder constructs OpenCage geocoder
29 | func Geocoder(key string, baseURLs ...string) geo.Geocoder {
30 | return geo.HTTPGeocoder{
31 | EndpointBuilder: baseURL(getURL(key, baseURLs...)),
32 | ResponseParserFactory: func() geo.ResponseParser { return &geocodeResponse{} },
33 | }
34 | }
35 |
36 | func getURL(key string, baseURLs ...string) string {
37 | if len(baseURLs) > 0 {
38 | return baseURLs[0]
39 | }
40 | return "http://api.opencagedata.com/geocode/v1/json?key=" + key + "&q="
41 | }
42 |
43 | func (b baseURL) GeocodeURL(address string) string { return string(b) + address }
44 |
45 | func (b baseURL) ReverseGeocodeURL(l geo.Location) string {
46 | return string(b) + fmt.Sprintf("%+f,%+f", l.Lat, l.Lng)
47 | }
48 |
49 | func (r *geocodeResponse) Location() (*geo.Location, error) {
50 | if r.Status.Code >= 400 {
51 | return nil, fmt.Errorf("geocoding error: %s", r.Status.Message)
52 | }
53 | if len(r.Results) == 0 {
54 | return nil, nil
55 | }
56 |
57 | return &geo.Location{
58 | Lat: r.Results[0].Geometry.Lat,
59 | Lng: r.Results[0].Geometry.Lng,
60 | }, nil
61 | }
62 |
63 | func (r *geocodeResponse) Address() (*geo.Address, error) {
64 | if r.Status.Code >= 400 {
65 | return nil, fmt.Errorf("geocoding error: %s", r.Status.Message)
66 | }
67 | if len(r.Results) == 0 {
68 | return nil, nil
69 | }
70 |
71 | addr := r.Results[0].Components
72 |
73 | locality := addr.Locality()
74 | if locality == "" {
75 | locality = addr.Suburb
76 | }
77 | return &geo.Address{
78 | FormattedAddress: r.Results[0].Formatted,
79 | HouseNumber: addr.HouseNumber,
80 | Street: addr.Street(),
81 | Suburb: addr.Suburb,
82 | Postcode: addr.Postcode,
83 | City: locality,
84 | CountryCode: strings.ToUpper(addr.CountryCode),
85 | Country: addr.Country,
86 | County: addr.County,
87 | State: addr.State,
88 | StateDistrict: addr.StateDistrict,
89 | }, nil
90 | }
91 |
--------------------------------------------------------------------------------
/arcgis/geocoder.go:
--------------------------------------------------------------------------------
1 | // Package arcgis is a geo-golang based ArcGIS geocode/reverse client
2 | package arcgis
3 |
4 | import (
5 | "fmt"
6 | "strings"
7 |
8 | geo "github.com/codingsince1985/geo-golang"
9 | )
10 |
11 | type (
12 | baseURL string
13 | geocodeResponse struct {
14 | Candidates []struct {
15 | Address string
16 | Location struct {
17 | X float64
18 | Y float64
19 | }
20 | }
21 |
22 | ReverseAddress struct {
23 | MatchAddr string `json:"Match_addr"`
24 | LongLabel string
25 | ShortLabel string
26 | AddNum string
27 | Address string
28 | Neighborhood string
29 | City string
30 | Subregion string
31 | Region string
32 | Postal string
33 | CountryCode string
34 | } `json:"address"`
35 | }
36 | )
37 |
38 | // Geocoder constructs ArcGIS geocoder
39 | func Geocoder(token string, baseURLs ...string) geo.Geocoder {
40 | return geo.HTTPGeocoder{
41 | EndpointBuilder: baseURL(getUrl(token, baseURLs...)),
42 | ResponseParserFactory: func() geo.ResponseParser { return &geocodeResponse{} },
43 | }
44 | }
45 |
46 | func getUrl(token string, baseURLs ...string) string {
47 | if len(baseURLs) > 0 {
48 | return baseURLs[0]
49 | }
50 |
51 | url := "http://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/*"
52 | if len(token) > 0 {
53 | url += "&token=" + token
54 | }
55 | return url
56 | }
57 |
58 | func (b baseURL) GeocodeURL(address string) string {
59 | params := fmt.Sprintf("findAddressCandidates?f=json&maxLocations=%d&SingleLine=%s", 1, address)
60 | return strings.Replace(string(b), "*", params, 1)
61 | }
62 |
63 | func (b baseURL) ReverseGeocodeURL(l geo.Location) string {
64 | params := fmt.Sprintf("reverseGeocode?f=json&location=%f,%f", l.Lng, l.Lat)
65 | return strings.Replace(string(b), "*", params, 1)
66 | }
67 |
68 | func (r *geocodeResponse) Location() (*geo.Location, error) {
69 | if len(r.Candidates) == 0 {
70 | return nil, nil
71 | }
72 |
73 | g := r.Candidates[0].Location
74 | return &geo.Location{
75 | Lat: g.Y,
76 | Lng: g.X,
77 | }, nil
78 | }
79 |
80 | func (r *geocodeResponse) Address() (*geo.Address, error) {
81 | addr := &geo.Address{
82 | FormattedAddress: r.ReverseAddress.MatchAddr,
83 | Street: r.ReverseAddress.Address,
84 | HouseNumber: r.ReverseAddress.AddNum,
85 | Postcode: r.ReverseAddress.Postal,
86 | State: r.ReverseAddress.Region,
87 | CountryCode: r.ReverseAddress.CountryCode,
88 | }
89 |
90 | return addr, nil
91 | }
92 |
--------------------------------------------------------------------------------
/chained/geocoder_test.go:
--------------------------------------------------------------------------------
1 | package chained_test
2 |
3 | import (
4 | "strings"
5 | "testing"
6 |
7 | "github.com/codingsince1985/geo-golang"
8 | "github.com/codingsince1985/geo-golang/chained"
9 | "github.com/codingsince1985/geo-golang/data"
10 | "github.com/stretchr/testify/assert"
11 | )
12 |
13 | // geocoder is chained with one data geocoder with address -> location data
14 | // the other has location -> address data
15 | // this will exercise the chained fallback handling
16 | var (
17 | addressFixture = geo.Address{
18 | FormattedAddress: "64 Elizabeth Street, Melbourne, Victoria 3000, Australia",
19 | }
20 | locationFixture = geo.Location{
21 | Lat: -37.814107,
22 | Lng: 144.96328,
23 | }
24 | geocoder = chained.Geocoder(
25 | data.Geocoder(
26 | data.AddressToLocation{
27 | addressFixture: locationFixture,
28 | },
29 | data.LocationToAddress{},
30 | ),
31 |
32 | data.Geocoder(
33 | data.AddressToLocation{},
34 | data.LocationToAddress{
35 | locationFixture: addressFixture,
36 | },
37 | ),
38 | )
39 | )
40 |
41 | func TestGeocode(t *testing.T) {
42 | location, err := geocoder.Geocode(addressFixture.FormattedAddress)
43 | assert.NoError(t, err)
44 | assert.Equal(t, geo.Location{locationFixture.Lat, locationFixture.Lng}, *location)
45 | }
46 |
47 | func TestReverseGeocode(t *testing.T) {
48 | address, err := geocoder.ReverseGeocode(locationFixture.Lat, locationFixture.Lng)
49 | assert.NoError(t, err)
50 | assert.True(t, strings.HasSuffix(address.FormattedAddress, "Melbourne, Victoria 3000, Australia"))
51 | }
52 |
53 | func TestReverseGeocodeWithNoResult(t *testing.T) {
54 | addr, err := geocoder.ReverseGeocode(0, 0)
55 | assert.Nil(t, err)
56 | assert.Nil(t, addr)
57 | }
58 |
59 | func TestChainedGeocode(t *testing.T) {
60 | mock1 := data.Geocoder(
61 | data.AddressToLocation{
62 | geo.Address{FormattedAddress: "Austin,TX"}: geo.Location{Lat: 1, Lng: 2},
63 | },
64 | data.LocationToAddress{},
65 | )
66 |
67 | mock2 := data.Geocoder(
68 | data.AddressToLocation{
69 | geo.Address{FormattedAddress: "Dallas,TX"}: geo.Location{Lat: 3, Lng: 4},
70 | },
71 | data.LocationToAddress{},
72 | )
73 |
74 | c := chained.Geocoder(mock1, mock2)
75 |
76 | l, err := c.Geocode("Austin,TX")
77 | assert.NoError(t, err)
78 | assert.Equal(t, geo.Location{Lat: 1, Lng: 2}, *l)
79 |
80 | l, err = c.Geocode("Dallas,TX")
81 | assert.NoError(t, err)
82 | assert.Equal(t, geo.Location{Lat: 3, Lng: 4}, *l)
83 |
84 | addr, err := c.Geocode("NOWHERE,TX")
85 | assert.Nil(t, err)
86 | assert.Nil(t, addr)
87 | }
88 |
--------------------------------------------------------------------------------
/cached/geocoder_test.go:
--------------------------------------------------------------------------------
1 | package cached_test
2 |
3 | import (
4 | "strings"
5 | "testing"
6 | "time"
7 |
8 | "github.com/codingsince1985/geo-golang"
9 | "github.com/codingsince1985/geo-golang/cached"
10 | "github.com/codingsince1985/geo-golang/data"
11 | "github.com/patrickmn/go-cache"
12 | "github.com/stretchr/testify/assert"
13 | )
14 |
15 | var geoCache = cache.New(5*time.Minute, 30*time.Second)
16 |
17 | // geocoder is chained with one data geocoder with address -> location data
18 | // the other has location -> address data
19 | // this will exercise the chained fallback handling
20 | var (
21 | addressFixture = geo.Address{
22 | FormattedAddress: "64 Elizabeth Street, Melbourne, Victoria 3000, Australia",
23 | }
24 | locationFixture = geo.Location{
25 | Lat: -37.814107,
26 | Lng: 144.96328,
27 | }
28 | geocoder = cached.Geocoder(
29 | data.Geocoder(
30 | data.AddressToLocation{
31 | addressFixture: locationFixture,
32 | },
33 | data.LocationToAddress{
34 | locationFixture: addressFixture,
35 | },
36 | ),
37 | geoCache,
38 | )
39 | )
40 |
41 | func TestGeocode(t *testing.T) {
42 | location, err := geocoder.Geocode("64 Elizabeth Street, Melbourne, Victoria 3000, Australia")
43 | assert.NoError(t, err)
44 | assert.Equal(t, locationFixture, *location)
45 | }
46 |
47 | func TestReverseGeocode(t *testing.T) {
48 | address, err := geocoder.ReverseGeocode(locationFixture.Lat, locationFixture.Lng)
49 | assert.NoError(t, err)
50 | assert.True(t, strings.HasSuffix(address.FormattedAddress, "Melbourne, Victoria 3000, Australia"))
51 | }
52 |
53 | func TestReverseGeocodeWithNoResult(t *testing.T) {
54 | addr, err := geocoder.ReverseGeocode(1, 2)
55 | assert.Nil(t, err)
56 | assert.Nil(t, addr)
57 | }
58 |
59 | func TestCachedGeocode(t *testing.T) {
60 | mockAddr := geo.Address{
61 | FormattedAddress: "42, Some Street, Austin, Texas",
62 | }
63 | mock1 := data.Geocoder(
64 | data.AddressToLocation{
65 | mockAddr: geo.Location{Lat: 1, Lng: 2},
66 | },
67 | data.LocationToAddress{},
68 | )
69 |
70 | c := cached.Geocoder(mock1, geoCache)
71 |
72 | l, err := c.Geocode("42, Some Street, Austin, Texas")
73 | assert.NoError(t, err)
74 | assert.Equal(t, geo.Location{Lat: 1, Lng: 2}, *l)
75 |
76 | // Should be cached
77 | // TODO: write a mock Cache impl to test cache is being used
78 | l, err = c.Geocode("42, Some Street, Austin, Texas")
79 | assert.NoError(t, err)
80 | assert.Equal(t, geo.Location{Lat: 1, Lng: 2}, *l)
81 |
82 | addr, err := c.Geocode("NOWHERE,TX")
83 | assert.Nil(t, err)
84 | assert.Nil(t, addr)
85 | }
86 |
--------------------------------------------------------------------------------
/mapzen/geocoder.go:
--------------------------------------------------------------------------------
1 | // Package mapzen is a geo-golang based Mapzen geocode/reverse client
2 | package mapzen
3 |
4 | import (
5 | "fmt"
6 | "strings"
7 |
8 | geo "github.com/codingsince1985/geo-golang"
9 | )
10 |
11 | type (
12 | baseURL string
13 | geocodeResponse struct {
14 | Geocoding struct {
15 | Query struct {
16 | Text string
17 | }
18 | }
19 |
20 | Features []struct {
21 | Geometry struct {
22 | Coordinates []float64
23 | }
24 | Properties struct {
25 | Name string
26 | HouseNumber string
27 | Street string
28 | PostalCode string
29 | Country string
30 | CountryCode string `json:"country_a"`
31 | Region string
32 | RegionCode string `json:"region_a"`
33 | County string
34 | Label string
35 | }
36 | }
37 | }
38 | )
39 |
40 | // Geocoder constructs Mapzen geocoder
41 | func Geocoder(key string, baseURLs ...string) geo.Geocoder {
42 | return geo.HTTPGeocoder{
43 | EndpointBuilder: baseURL(getUrl(key, baseURLs...)),
44 | ResponseParserFactory: func() geo.ResponseParser { return &geocodeResponse{} },
45 | }
46 | }
47 |
48 | func getUrl(key string, baseURLs ...string) string {
49 | if len(baseURLs) > 0 {
50 | return baseURLs[0]
51 | }
52 | return "https://search.mapzen.com/v1/*" + "&api_key=" + key
53 | }
54 |
55 | func (b baseURL) GeocodeURL(address string) string {
56 | params := fmt.Sprintf("search?size=%d&text=%s", 1, address)
57 | return strings.Replace(string(b), "*", params, 1)
58 | }
59 |
60 | func (b baseURL) ReverseGeocodeURL(l geo.Location) string {
61 | params := fmt.Sprintf("reverse?size=%d&point.lat=%f&point.lon=%f", 1, l.Lat, l.Lng)
62 | return strings.Replace(string(b), "*", params, 1)
63 | }
64 |
65 | func (r *geocodeResponse) Location() (*geo.Location, error) {
66 | if len(r.Features) == 0 {
67 | return nil, nil
68 | }
69 |
70 | pt := r.Features[0].Geometry.Coordinates
71 | if len(pt) == 0 {
72 | return nil, nil
73 | }
74 |
75 | return &geo.Location{Lat: pt[1], Lng: pt[0]}, nil
76 | }
77 |
78 | func (r *geocodeResponse) Address() (*geo.Address, error) {
79 | if len(r.Features) == 0 {
80 | return nil, nil
81 | }
82 |
83 | props := r.Features[0].Properties
84 | addr := &geo.Address{
85 | FormattedAddress: props.Label,
86 | Street: props.Street,
87 | HouseNumber: props.HouseNumber,
88 | Postcode: props.PostalCode,
89 | Country: props.Country,
90 | CountryCode: props.CountryCode,
91 | State: props.Region,
92 | StateCode: props.RegionCode,
93 | }
94 | return addr, nil
95 | }
96 |
--------------------------------------------------------------------------------
/mapquest/open/geocoder.go:
--------------------------------------------------------------------------------
1 | // Package open is a geo-golang based MapRequest Open geocode/reverse geocode client
2 | package open
3 |
4 | import (
5 | "fmt"
6 | "strings"
7 |
8 | "github.com/codingsince1985/geo-golang"
9 | )
10 |
11 | type (
12 | baseURL string
13 | geocodeResponse struct {
14 | Results []struct {
15 | Locations []struct {
16 | LatLng struct {
17 | Lat float64
18 | Lng float64
19 | }
20 | PostalCode string
21 | Street string
22 | AdminArea6 string // neighbourhood
23 | AdminArea5 string // city
24 | AdminArea4 string // county
25 | AdminArea3 string // state
26 | AdminArea1 string // country (ISO 3166-1 alpha-2 code)
27 | }
28 | }
29 | }
30 | )
31 |
32 | // Geocoder constructs MapRequest Open geocoder
33 | func Geocoder(key string, baseURLs ...string) geo.Geocoder {
34 |
35 | return geo.HTTPGeocoder{
36 | EndpointBuilder: baseURL(getURL(key, baseURLs...)),
37 | ResponseParserFactory: func() geo.ResponseParser { return &geocodeResponse{} },
38 | }
39 | }
40 |
41 | func getURL(key string, baseURLs ...string) string {
42 | if len(baseURLs) > 0 {
43 | return baseURLs[0]
44 | }
45 | return "http://open.mapquestapi.com/geocoding/v1/*?key=" + key + "&location="
46 | }
47 |
48 | func (b baseURL) GeocodeURL(address string) string {
49 | return strings.Replace(string(b), "*", "address", 1) + address
50 | }
51 |
52 | func (b baseURL) ReverseGeocodeURL(l geo.Location) string {
53 | return strings.Replace(string(b), "*", "reverse", 1) + fmt.Sprintf("%f,%f", l.Lat, l.Lng)
54 | }
55 |
56 | func (r *geocodeResponse) Location() (*geo.Location, error) {
57 | if len(r.Results) == 0 || len(r.Results[0].Locations) == 0 {
58 | return nil, nil
59 | }
60 |
61 | loc := r.Results[0].Locations[0].LatLng
62 | return &geo.Location{
63 | Lat: loc.Lat,
64 | Lng: loc.Lng,
65 | }, nil
66 | }
67 |
68 | func (r *geocodeResponse) Address() (*geo.Address, error) {
69 | if len(r.Results) == 0 || len(r.Results[0].Locations) == 0 {
70 | return nil, nil
71 | }
72 |
73 | p := r.Results[0].Locations[0]
74 | if p.Street == "" || p.AdminArea5 == "" {
75 | return nil, nil
76 | }
77 |
78 | formattedAddress := p.Street + ", " + p.PostalCode + ", " + p.AdminArea5 + ", " + p.AdminArea3 + ", " + p.AdminArea1
79 | return &geo.Address{
80 | FormattedAddress: formattedAddress,
81 | Street: p.Street,
82 | Suburb: p.AdminArea6,
83 | Postcode: p.PostalCode,
84 | City: p.AdminArea5,
85 | County: p.AdminArea4,
86 | State: p.AdminArea3,
87 | CountryCode: p.AdminArea1,
88 | }, nil
89 | }
90 |
--------------------------------------------------------------------------------
/locationiq/geocoder.go:
--------------------------------------------------------------------------------
1 | // Package locationiq is a geo-golang based LocationIQ geocode/reverse geocode client
2 | package locationiq
3 |
4 | import (
5 | "fmt"
6 | "strings"
7 |
8 | "github.com/codingsince1985/geo-golang"
9 | "github.com/codingsince1985/geo-golang/osm"
10 | )
11 |
12 | type baseURL string
13 |
14 | type geocodeResponse struct {
15 | DisplayName string `json:"display_name"`
16 | Lat, Lon, Error string
17 | Addr osm.Address `json:"address"`
18 | }
19 |
20 | const (
21 | defaultURL = "http://locationiq.org/v1/"
22 | minZoom = 0 // Min zoom level for locationiq - country level
23 | maxZoom = 18 // Max zoom level for locationiq - house level
24 | defaultZoom = 18
25 | )
26 |
27 | var (
28 | key string
29 | zoom int
30 | )
31 |
32 | // Geocoder constructs LocationIQ geocoder
33 | func Geocoder(k string, z int, baseURLs ...string) geo.Geocoder {
34 | key = k
35 |
36 | var url string
37 | if len(baseURLs) > 0 {
38 | url = baseURLs[0]
39 | } else {
40 | url = defaultURL
41 | }
42 |
43 | if z > minZoom && z <= maxZoom {
44 | zoom = z
45 | } else {
46 | zoom = defaultZoom
47 | }
48 |
49 | return geo.HTTPGeocoder{
50 | EndpointBuilder: baseURL(url),
51 | ResponseParserFactory: func() geo.ResponseParser { return &geocodeResponse{} },
52 | }
53 | }
54 |
55 | func (b baseURL) GeocodeURL(address string) string {
56 | return string(b) + "search.php?key=" + key + "&format=json&limit=1&q=" + address
57 | }
58 |
59 | func (b baseURL) ReverseGeocodeURL(l geo.Location) string {
60 | return string(b) + "reverse.php?key=" + key + fmt.Sprintf("&format=json&lat=%f&lon=%f&zoom=%d", l.Lat, l.Lng, zoom)
61 | }
62 |
63 | func (r *geocodeResponse) Location() (*geo.Location, error) {
64 | if r.Error != "" {
65 | return nil, fmt.Errorf("geocoding error: %s", r.Error)
66 | }
67 |
68 | // no result
69 | if r.Lat == "" || r.Lon == "" {
70 | return nil, nil
71 | }
72 |
73 | return &geo.Location{
74 | Lat: geo.ParseFloat(r.Lat),
75 | Lng: geo.ParseFloat(r.Lon),
76 | }, nil
77 | }
78 |
79 | func (r *geocodeResponse) Address() (*geo.Address, error) {
80 | if r.Error != "" {
81 | return nil, fmt.Errorf("reverse geocoding error: %s", r.Error)
82 | }
83 |
84 | return &geo.Address{
85 | FormattedAddress: r.DisplayName,
86 | Street: r.Addr.Street(),
87 | HouseNumber: r.Addr.HouseNumber,
88 | City: r.Addr.Locality(),
89 | Postcode: r.Addr.Postcode,
90 | Suburb: r.Addr.Suburb,
91 | State: r.Addr.State,
92 | Country: r.Addr.Country,
93 | CountryCode: strings.ToUpper(r.Addr.CountryCode),
94 | }, nil
95 | }
96 |
--------------------------------------------------------------------------------
/here/search/geocoder.go:
--------------------------------------------------------------------------------
1 | // Package search is a geo-golang based HERE geocode/reverse geocode client for the Geocoding and Search API
2 | package search
3 |
4 | import (
5 | "fmt"
6 | "net/url"
7 |
8 | "github.com/codingsince1985/geo-golang"
9 | )
10 |
11 | type (
12 | baseURL struct{ forGeocode, forReverseGeocode string }
13 | geocodeResponse struct {
14 | Items []struct {
15 | Address struct {
16 | Label string
17 | CountryCode string
18 | CountryName string
19 | StateCode string
20 | State string
21 | County string
22 | District string
23 | City string
24 | Street string
25 | PostalCode string
26 | HouseNumber string
27 | }
28 | Position struct {
29 | Lat float64
30 | Lng float64
31 | }
32 | }
33 | }
34 | )
35 |
36 | // Geocoder constructs HERE geocoder
37 | func Geocoder(apiKey string, baseURLs ...string) geo.Geocoder {
38 | p := "apiKey=" + url.QueryEscape(apiKey)
39 | return geo.HTTPGeocoder{
40 | EndpointBuilder: baseURL{
41 | getGeocodeURL(p, baseURLs...),
42 | getReverseGeocodeURL(p, baseURLs...)},
43 | ResponseParserFactory: func() geo.ResponseParser { return &geocodeResponse{} },
44 | }
45 | }
46 |
47 | func getGeocodeURL(p string, baseURLs ...string) string {
48 | if len(baseURLs) > 0 {
49 | return baseURLs[0]
50 | }
51 | return "https://geocode.search.hereapi.com/v1/geocode?" + p
52 | }
53 |
54 | func getReverseGeocodeURL(p string, baseURLs ...string) string {
55 | if len(baseURLs) > 0 {
56 | return baseURLs[0]
57 | }
58 | return "https://revgeocode.search.hereapi.com/v1/revgeocode?" + p
59 | }
60 |
61 | func (b baseURL) GeocodeURL(address string) string { return b.forGeocode + "&limit=1&q=" + address }
62 |
63 | func (b baseURL) ReverseGeocodeURL(l geo.Location) string {
64 | return b.forReverseGeocode + fmt.Sprintf("&limit=1&at=%f,%f", l.Lat, l.Lng)
65 | }
66 |
67 | func (r *geocodeResponse) Location() (*geo.Location, error) {
68 | if len(r.Items) == 0 {
69 | return nil, nil
70 | }
71 | p := r.Items[0].Position
72 | return &geo.Location{
73 | Lat: p.Lat,
74 | Lng: p.Lng,
75 | }, nil
76 | }
77 |
78 | func (r *geocodeResponse) Address() (*geo.Address, error) {
79 | if len(r.Items) == 0 {
80 | return nil, nil
81 | }
82 |
83 | res := r.Items[0].Address
84 |
85 | addr := &geo.Address{
86 | FormattedAddress: res.Label,
87 | City: res.City,
88 | Street: res.Street,
89 | HouseNumber: res.HouseNumber,
90 | Postcode: res.PostalCode,
91 | State: res.State,
92 | County: res.County,
93 | Country: res.CountryName,
94 | CountryCode: res.CountryCode,
95 | Suburb: res.District,
96 | }
97 | return addr, nil
98 | }
99 |
--------------------------------------------------------------------------------
/baidu/baidu_test.go:
--------------------------------------------------------------------------------
1 | package baidu_test
2 |
3 | import (
4 | "net/http"
5 | "net/http/httptest"
6 | "os"
7 | "strings"
8 | "testing"
9 |
10 | "github.com/codingsince1985/geo-golang"
11 | "github.com/codingsince1985/geo-golang/baidu"
12 | "github.com/stretchr/testify/assert"
13 | )
14 |
15 | var key = os.Getenv("BAIDU_APP_KEY")
16 |
17 | func TestGeocode(t *testing.T) {
18 | ts := testServer(response1)
19 | defer ts.Close()
20 |
21 | geocoder := baidu.Geocoder(key, "en", "bd09ll", ts.URL+"/")
22 | location, err := geocoder.Geocode("60 Collins St, Melbourne VIC")
23 |
24 | assert.NoError(t, err)
25 | assert.Equal(t, geo.Location{Lat: 40.05703033345938, Lng: 116.3084202915042}, *location)
26 | }
27 |
28 | func TestReverseGeocode(t *testing.T) {
29 | ts := testServer(response2)
30 | defer ts.Close()
31 |
32 | geocoder := baidu.Geocoder(key, "en", "bd09ll", ts.URL+"/")
33 | address, err := geocoder.ReverseGeocode(40.03333340036988, 116.29999999999993)
34 |
35 | assert.NoError(t, err)
36 | assert.True(t, strings.HasPrefix(address.FormattedAddress, "43号 农大南路, Haidian, Beijing, China"))
37 | assert.True(t, address.City == "Beijing")
38 | }
39 |
40 | func TestReverseGeocodeWithNoResult(t *testing.T) {
41 | ts := testServer(response3)
42 | defer ts.Close()
43 |
44 | geocoder := baidu.Geocoder(key, "en", "bd09ll", ts.URL+"/")
45 | addr, err := geocoder.ReverseGeocode(-37.81375, 164.97176)
46 |
47 | assert.NoError(t, err)
48 | assert.Nil(t, addr)
49 | }
50 |
51 | func testServer(response string) *httptest.Server {
52 | return httptest.NewServer(http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
53 | resp.Write([]byte(response))
54 | }))
55 | }
56 |
57 | const (
58 | response1 = `{"status":0,"result":{"location":{"lng":116.3084202915042,"lat":40.05703033345938},"precise":1,"confidence":80,"comprehension":100,"level":"门址"}}`
59 | response2 = `{"status":0,"result":{"location":{"lng":116.29999999999993,"lat":40.03333340036988},"formatted_address":"43号 农大南路, Haidian, Beijing, China","business":"马连洼,上地","addressComponent":{"country":"China","country_code":0,"country_code_iso":"CHN","country_code_iso2":"CN","province":"Beijing","city":"Beijing","city_level":2,"district":"Haidian","town":"","town_code":"","adcode":"110108","street":"农大南路","street_number":"43号","direction":"附近","distance":"26"},"pois":[],"roads":[],"poiRegions":[],"sematic_description":"","cityCode":131}}`
60 | response3 = `{"status":0,"result":{"location":{"lng":164.97175999999986,"lat":-37.81375002268602},"formatted_address":"","business":"","addressComponent":{"country":"","country_code":-1,"country_code_iso":"","country_code_iso2":"","province":"","city":"","city_level":2,"district":"","town":"","town_code":"","adcode":"0","street":"","street_number":"","direction":"","distance":""},"pois":[],"roads":[],"poiRegions":[],"sematic_description":"","cityCode":0}}`
61 | )
62 |
--------------------------------------------------------------------------------
/tomtom/geocoder.go:
--------------------------------------------------------------------------------
1 | // Package tomtom is a geo-golang based TomTom geocode/reverse geocode client
2 | package tomtom
3 |
4 | import (
5 | "fmt"
6 | "strings"
7 |
8 | geo "github.com/codingsince1985/geo-golang"
9 | )
10 |
11 | type (
12 | baseURL string
13 | geocodeResponse struct {
14 | Summary struct {
15 | Query string
16 | }
17 |
18 | Results []struct {
19 | Position struct {
20 | Lat float64
21 | Lon float64
22 | }
23 | }
24 |
25 | // Reverse Geocoding response
26 | Addresses []struct {
27 | Address struct {
28 | BuildingNumber string
29 | StreetNumber string
30 | Street string
31 | StreetName string
32 | StreetNameAndNumber string
33 | CountryCode string
34 | CountrySubdivision string // state code
35 | CountrySecondarySubdivision string
36 | CountryTertiarySubdivision string
37 | Municipality string // city
38 | PostalCode string
39 | Country string
40 | CountryCodeISO3 string
41 | FreeformAddress string
42 | CountrySubdivisionName string
43 | }
44 | }
45 | }
46 | )
47 |
48 | // Geocoder constructs TomTom geocoder
49 | func Geocoder(key string, baseURLs ...string) geo.Geocoder {
50 | return geo.HTTPGeocoder{
51 | EndpointBuilder: baseURL(getUrl(key, baseURLs...)),
52 | ResponseParserFactory: func() geo.ResponseParser { return &geocodeResponse{} },
53 | }
54 | }
55 |
56 | func getUrl(key string, baseURLs ...string) string {
57 | if len(baseURLs) > 0 {
58 | return baseURLs[0]
59 | }
60 | return "https://api.tomtom.com/search/2/*?key=" + key
61 | }
62 |
63 | func (b baseURL) GeocodeURL(address string) string {
64 | params := fmt.Sprintf("geocode/%s.json", address)
65 | return strings.Replace(string(b), "*", params, 1)
66 | }
67 |
68 | func (b baseURL) ReverseGeocodeURL(l geo.Location) string {
69 | params := fmt.Sprintf("reverseGeocode/%f,%f", l.Lat, l.Lng)
70 | return strings.Replace(string(b), "*", params, 1)
71 | }
72 |
73 | func (r *geocodeResponse) Location() (*geo.Location, error) {
74 | if len(r.Results) > 0 {
75 | p := r.Results[0].Position
76 |
77 | return &geo.Location{
78 | Lat: p.Lat,
79 | Lng: p.Lon,
80 | }, nil
81 | }
82 | return nil, nil
83 | }
84 |
85 | func (r *geocodeResponse) Address() (*geo.Address, error) {
86 | if len(r.Addresses) > 0 {
87 | a := r.Addresses[0].Address
88 | return &geo.Address{
89 | FormattedAddress: a.FreeformAddress,
90 | Street: a.StreetName,
91 | HouseNumber: a.StreetNumber,
92 | City: a.Municipality,
93 | Postcode: a.PostalCode,
94 | State: a.CountrySubdivision,
95 | Country: a.Country,
96 | CountryCode: a.CountryCode,
97 | }, nil
98 | }
99 | return nil, nil
100 | }
101 |
--------------------------------------------------------------------------------
/pickpoint/geocoder_test.go:
--------------------------------------------------------------------------------
1 | package pickpoint_test
2 |
3 | import (
4 | "github.com/codingsince1985/geo-golang"
5 | "github.com/codingsince1985/geo-golang/pickpoint"
6 | "github.com/stretchr/testify/assert"
7 | "net/http"
8 | "net/http/httptest"
9 | "os"
10 | "strings"
11 | "testing"
12 | )
13 |
14 | var key = os.Getenv("PICKPOINT_API_KEY")
15 |
16 | func TestGeocode(t *testing.T) {
17 | ts := testServer(response1)
18 | defer ts.Close()
19 |
20 | geocoder := pickpoint.Geocoder(key, ts.URL+"/")
21 | location, err := geocoder.Geocode("60 Collins St, Melbourne VIC 3000")
22 | assert.Nil(t, err)
23 | assert.Equal(t, geo.Location{Lat: -37.8157915, Lng: 144.9656171}, *location)
24 | }
25 |
26 | func TestReverseGeocode(t *testing.T) {
27 | ts := testServer(response2)
28 | defer ts.Close()
29 |
30 | geocoder := pickpoint.Geocoder(key, ts.URL+"/")
31 | address, err := geocoder.ReverseGeocode(-37.8157915, 144.9656171)
32 | assert.Nil(t, err)
33 | assert.True(t, strings.Index(address.FormattedAddress, "Collins St") > 0)
34 | }
35 |
36 | func TestReverseGeocodeWithNoResult(t *testing.T) {
37 | ts := testServer(response3)
38 | defer ts.Close()
39 |
40 | geocoder := pickpoint.Geocoder(key, ts.URL+"/")
41 | addr, err := geocoder.ReverseGeocode(-37.8157915, 164.9656171)
42 | assert.Nil(t, addr)
43 | assert.NotNil(t, err)
44 | }
45 |
46 | func testServer(response string) *httptest.Server {
47 | return httptest.NewServer(http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
48 | resp.Write([]byte(response))
49 | }))
50 | }
51 |
52 | const (
53 | response1 = `[
54 | {
55 | "place_id":"133372311",
56 | "licence":"Data © OpenStreetMap contributors, ODbL 1.0. http:\/\/www.openstreetmap.org\/copyright",
57 | "osm_type":"way",
58 | "osm_id":"316166613",
59 | "boundingbox":[
60 | "-37.8162553",
61 | "-37.815533",
62 | "144.9640149",
63 | "144.9665099"
64 | ],
65 | "lat":"-37.8157915",
66 | "lon":"144.9656171",
67 | "display_name":"Collins Street, Melbourne, City of Melbourne, Greater Melbourne, Victoria, 3000, Australia",
68 | "class":"highway",
69 | "type":"tertiary",
70 | "importance":0.51
71 | }
72 | ]`
73 | response2 = `{
74 | "place_id":"5122082",
75 | "licence":"Data © OpenStreetMap contributors, ODbL 1.0. http:\/\/www.openstreetmap.org\/copyright",
76 | "osm_type":"node",
77 | "osm_id":"594206614",
78 | "lat":"-37.8158091",
79 | "lon":"144.9656492",
80 | "display_name":"Telstra, Collins Street, Melbourne, City of Melbourne, Greater Melbourne, Victoria, 3000, Australia",
81 | "address":{
82 | "telephone":"Telstra",
83 | "road":"Collins Street",
84 | "suburb":"Melbourne",
85 | "city":"Melbourne",
86 | "county":"City of Melbourne",
87 | "region":"Greater Melbourne",
88 | "state":"Victoria",
89 | "postcode":"3000",
90 | "country":"Australia",
91 | "country_code":"au"
92 | },
93 | "boundingbox":[
94 | "-37.8159091",
95 | "-37.8157091",
96 | "144.9655492",
97 | "144.9657492"
98 | ]
99 | }`
100 | response3 = `{
101 | "error":"Unable to geocode"
102 | }`
103 | )
104 |
--------------------------------------------------------------------------------
/arcgis/geocoder_test.go:
--------------------------------------------------------------------------------
1 | package arcgis
2 |
3 | import (
4 | "math"
5 | "net/http"
6 | "net/http/httptest"
7 | "os"
8 | "testing"
9 |
10 | geo "github.com/codingsince1985/geo-golang"
11 | )
12 |
13 | var token = os.Getenv("ARCGIS_TOKEN")
14 |
15 | func TestGeocode(t *testing.T) {
16 | ts := testServer(geocodeResp)
17 | defer ts.Close()
18 |
19 | address := "380 New York, Redlands, CA 92373, USA"
20 | geocoder := Geocoder(token, ts.URL)
21 | loc, err := geocoder.Geocode(address)
22 | if err != nil {
23 | t.Fatal(err)
24 | }
25 |
26 | if loc == nil {
27 | t.Fatalf("Address: %s - Not Found\n", address)
28 | }
29 | expected := geo.Location{Lng: -117.1956703176181, Lat: 34.056488119308924}
30 | if math.Abs(loc.Lng-expected.Lng) > eps {
31 | t.Fatalf("Got: %v\tExpected: %v\n", loc, expected)
32 | }
33 | if math.Abs(loc.Lat-expected.Lat) > eps {
34 | t.Fatalf("Got: %v\tExpected: %v\n", loc, expected)
35 | }
36 | }
37 |
38 | func TestReverseGeocode(t *testing.T) {
39 | ts := testServer(reverseResp)
40 | defer ts.Close()
41 |
42 | code := "USA"
43 | state := "California"
44 | lat := 34.056488119308924
45 | lng := -117.1956703176181
46 | geocoder := Geocoder(token, ts.URL)
47 | addr, err := geocoder.ReverseGeocode(lat, lng)
48 | if err != nil {
49 | t.Error(err)
50 | }
51 |
52 | if addr == nil {
53 | t.Fatalf("Location: lat:%f, lng:%f - Not Found\n", lat, lng)
54 | }
55 |
56 | if addr.CountryCode != code {
57 | t.Fatalf("Got: %v\tExpected: %v\n", addr.CountryCode, code)
58 | }
59 | if addr.State != state {
60 | t.Fatalf("Got: %v\tExpected: %v\n", addr.State, state)
61 | }
62 | }
63 |
64 | func testServer(response string) *httptest.Server {
65 | return httptest.NewServer(http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
66 | resp.Write([]byte(response))
67 | }))
68 | }
69 |
70 | const (
71 | eps = 1.0e-5
72 |
73 | geocodeResp = `{
74 | "spatialReference": {
75 | "wkid": 4326,
76 | "latestWkid": 4326
77 | },
78 | "candidates": [
79 | {
80 | "address": "380 New York St, Redlands, California, 92373",
81 | "location": {
82 | "x": -117.1956703176181,
83 | "y": 34.056488119308924
84 | },
85 | "score": 100,
86 | "attributes": {
87 |
88 | },
89 | "extent": {
90 | "xmin": -117.1963135,
91 | "ymin": 34.055108000000011,
92 | "xmax": -117.19431349999999,
93 | "ymax": 34.057108000000007
94 | }
95 | }
96 | ]
97 | }`
98 |
99 | reverseResp = `{
100 | "address": {
101 | "Match_addr": "117 Norwood St, Redlands, California, 92373",
102 | "LongLabel": "117 Norwood St, Redlands, CA, 92373, USA",
103 | "ShortLabel": "117 Norwood St",
104 | "Addr_type": "PointAddress",
105 | "Type": "",
106 | "PlaceName": "",
107 | "AddNum": "117",
108 | "Address": "117 Norwood St",
109 | "Block": "",
110 | "Sector": "",
111 | "Neighborhood": "South Redlands",
112 | "District": "",
113 | "City": "Redlands",
114 | "MetroArea": "Inland Empire",
115 | "Subregion": "San Bernardino",
116 | "Region": "California",
117 | "Territory": "",
118 | "Postal": "92373",
119 | "PostalExt": "",
120 | "CountryCode": "USA"
121 | },
122 | "location": {
123 | "x": -117.19052869813629,
124 | "y": 34.049723195135194,
125 | "spatialReference": {
126 | "wkid": 4326,
127 | "latestWkid": 4326
128 | }
129 | }
130 | }`
131 | )
132 |
--------------------------------------------------------------------------------
/mapbox/geocoder.go:
--------------------------------------------------------------------------------
1 | // Package mapbox is a geo-golang based Mapbox geocode/reverse geocode client
2 | package mapbox
3 |
4 | import (
5 | "encoding/json"
6 | "fmt"
7 | "strings"
8 |
9 | "github.com/codingsince1985/geo-golang"
10 | )
11 |
12 | type (
13 | baseURL string
14 | geocodeResponse struct {
15 | Features []struct {
16 | PlaceName string `json:"place_name"`
17 | Center [2]float64
18 | Text string `json:"text"` // usually street name
19 | Address json.RawMessage `json:"address"` // potentially house number
20 | Context []struct {
21 | Text string `json:"text"`
22 | Id string `json:"id"`
23 | ShortCode string `json:"short_code"`
24 | Wikidata string `json:"wikidata"`
25 | }
26 | }
27 | Message string `json:"message"`
28 | }
29 | )
30 |
31 | const (
32 | mapboxPrefixLocality = "place"
33 | mapboxPrefixPostcode = "postcode"
34 | mapboxPrefixState = "region"
35 | mapboxPrefixCountry = "country"
36 | )
37 |
38 | // Geocoder constructs Mapbox geocoder
39 | func Geocoder(token string, baseURLs ...string) geo.Geocoder {
40 | return geo.HTTPGeocoder{
41 | EndpointBuilder: baseURL(getURL(token, baseURLs...)),
42 | ResponseParserFactory: func() geo.ResponseParser { return &geocodeResponse{} },
43 | }
44 | }
45 |
46 | func getURL(token string, baseURLs ...string) string {
47 | if len(baseURLs) > 0 {
48 | return baseURLs[0]
49 | }
50 | return "https://api.mapbox.com/geocoding/v5/mapbox.places/*.json?limit=1&access_token=" + token
51 | }
52 |
53 | func (b baseURL) GeocodeURL(address string) string {
54 | return strings.Replace(string(b), "*", address, 1)
55 | }
56 |
57 | func (b baseURL) ReverseGeocodeURL(l geo.Location) string {
58 | return strings.Replace(string(b), "*", fmt.Sprintf("%+f,%+f", l.Lng, l.Lat), 1)
59 | }
60 |
61 | func (r *geocodeResponse) Location() (*geo.Location, error) {
62 | if len(r.Features) == 0 {
63 | // error in response
64 | if r.Message != "" {
65 | return nil, fmt.Errorf("reverse geocoding error: %s", r.Message)
66 | }
67 | // no results
68 | return nil, nil
69 | }
70 |
71 | g := r.Features[0]
72 | return &geo.Location{
73 | Lat: g.Center[1],
74 | Lng: g.Center[0],
75 | }, nil
76 | }
77 |
78 | func (r *geocodeResponse) Address() (*geo.Address, error) {
79 | if len(r.Features) == 0 {
80 | // error in response
81 | if r.Message != "" {
82 | return nil, fmt.Errorf("reverse geocoding error: %s", r.Message)
83 | }
84 | // no results
85 | return nil, nil
86 | }
87 |
88 | return parseMapboxResponse(r), nil
89 | }
90 |
91 | func parseMapboxResponse(r *geocodeResponse) *geo.Address {
92 | addr := &geo.Address{}
93 | f := r.Features[0]
94 | addr.FormattedAddress = f.PlaceName
95 | addr.Street = f.Text
96 | addr.HouseNumber = string(f.Address)
97 | for _, c := range f.Context {
98 | if strings.HasPrefix(c.Id, mapboxPrefixLocality) {
99 | addr.City = c.Text
100 | } else if strings.HasPrefix(c.Id, mapboxPrefixPostcode) {
101 | addr.Postcode = c.Text
102 | } else if strings.HasPrefix(c.Id, mapboxPrefixState) {
103 | addr.State = c.Text
104 | addr.StateCode = c.ShortCode
105 | } else if strings.HasPrefix(c.Id, mapboxPrefixCountry) {
106 | addr.Country = c.Text
107 | addr.CountryCode = strings.ToUpper(c.ShortCode)
108 | }
109 | }
110 |
111 | return addr
112 | }
113 |
--------------------------------------------------------------------------------
/here/geocoder.go:
--------------------------------------------------------------------------------
1 | // Package here is a geo-golang based HERE geocode/reverse geocode client for the legacy geocoder API
2 | package here
3 |
4 | import (
5 | "fmt"
6 |
7 | "github.com/codingsince1985/geo-golang"
8 | )
9 |
10 | type (
11 | baseURL struct{ forGeocode, forReverseGeocode string }
12 | geocodeResponse struct {
13 | Response struct {
14 | View []struct {
15 | Result []struct {
16 | Location struct {
17 | DisplayPosition struct {
18 | Latitude, Longitude float64
19 | }
20 | Address struct {
21 | Label string
22 | Country string
23 | State string
24 | County string
25 | City string
26 | District string
27 | Street string
28 | HouseNumber string
29 | PostalCode string
30 | AdditionalData []struct {
31 | Key string
32 | Value string
33 | }
34 | }
35 | }
36 | }
37 | }
38 | }
39 | }
40 | )
41 |
42 | // Key*Name represents constants for geocoding more address detail
43 | const (
44 | KeyCountryName = "CountryName"
45 | KeyStateName = "StateName"
46 | KeyCountyName = "CountyName"
47 | )
48 |
49 | var r = 100
50 |
51 | // Geocoder constructs HERE geocoder
52 | func Geocoder(id, code string, radius int, baseURLs ...string) geo.Geocoder {
53 | if radius > 0 {
54 | r = radius
55 | }
56 | p := "gen=9&app_id=" + id + "&app_code=" + code
57 | return geo.HTTPGeocoder{
58 | EndpointBuilder: baseURL{
59 | getGeocodeURL(p, baseURLs...),
60 | getReverseGeocodeURL(p, baseURLs...)},
61 | ResponseParserFactory: func() geo.ResponseParser { return &geocodeResponse{} },
62 | }
63 | }
64 |
65 | func getGeocodeURL(p string, baseURLs ...string) string {
66 | if len(baseURLs) > 0 {
67 | return baseURLs[0]
68 | }
69 | return "http://geocoder.api.here.com/6.2/geocode.json?" + p
70 | }
71 |
72 | func getReverseGeocodeURL(p string, baseURLs ...string) string {
73 | if len(baseURLs) > 0 {
74 | return baseURLs[0]
75 | }
76 | return "http://reverse.geocoder.api.here.com/6.2/reversegeocode.json?mode=retrieveAddresses&" + p
77 | }
78 |
79 | func (b baseURL) GeocodeURL(address string) string { return b.forGeocode + "&searchtext=" + address }
80 |
81 | func (b baseURL) ReverseGeocodeURL(l geo.Location) string {
82 | return b.forReverseGeocode + fmt.Sprintf("&prox=%f,%f,%d", l.Lat, l.Lng, r)
83 | }
84 |
85 | func (r *geocodeResponse) Location() (*geo.Location, error) {
86 | if len(r.Response.View) == 0 {
87 | return nil, nil
88 | }
89 | p := r.Response.View[0].Result[0].Location.DisplayPosition
90 | return &geo.Location{
91 | Lat: p.Latitude,
92 | Lng: p.Longitude,
93 | }, nil
94 | }
95 |
96 | func (r *geocodeResponse) Address() (*geo.Address, error) {
97 | if len(r.Response.View) == 0 || len(r.Response.View[0].Result) == 0 {
98 | return nil, nil
99 | }
100 |
101 | res := r.Response.View[0].Result[0].Location.Address
102 |
103 | addr := &geo.Address{
104 | FormattedAddress: res.Label,
105 | Street: res.Street,
106 | HouseNumber: res.HouseNumber,
107 | City: res.City,
108 | Postcode: res.PostalCode,
109 | CountryCode: res.Country,
110 | }
111 | for _, v := range res.AdditionalData {
112 | switch v.Key {
113 | case KeyCountryName:
114 | addr.Country = v.Value
115 | case KeyStateName:
116 | addr.State = v.Value
117 | }
118 | }
119 | return addr, nil
120 | }
121 |
--------------------------------------------------------------------------------
/frenchapigouv/geocoder.go:
--------------------------------------------------------------------------------
1 | // Package frenchapigouv is a geo-golang based French API Gouv geocode/reverse geocode client
2 | package frenchapigouv
3 |
4 | import (
5 | "fmt"
6 | "strings"
7 |
8 | "github.com/codingsince1985/geo-golang"
9 | )
10 |
11 | type (
12 | baseURL string
13 | geocodeResponse struct {
14 | Type string
15 | Version string
16 | Features []struct {
17 | Type string
18 | Geometry struct {
19 | Type string
20 | Coordinates []float64
21 | }
22 | Properties struct {
23 | Label string
24 | Score float64
25 | Housenumber string
26 | Citycode string
27 | Context string
28 | Postcode string
29 | Name string
30 | ID string
31 | Y float64
32 | Importance float64
33 | Type string
34 | City string
35 | X float64
36 | Street string
37 | }
38 | }
39 | Attribution string
40 | Licence string
41 | Query string
42 | Limit int
43 | }
44 | context struct {
45 | state string
46 | county string
47 | countyCode string
48 | }
49 | )
50 |
51 | // Geocoder constructs FrenchApiGouv geocoder
52 | func Geocoder() geo.Geocoder { return GeocoderWithURL("https://api-adresse.data.gouv.fr/") }
53 |
54 | // GeocoderWithURL constructs French API Gouv geocoder using a custom installation of Nominatim
55 | func GeocoderWithURL(url string) geo.Geocoder {
56 | return geo.HTTPGeocoder{
57 | EndpointBuilder: baseURL(url),
58 | ResponseParserFactory: func() geo.ResponseParser { return &geocodeResponse{} },
59 | }
60 | }
61 |
62 | func (b baseURL) GeocodeURL(address string) string {
63 | return string(b) + "search?limit=1&q=" + address
64 | }
65 |
66 | func (b baseURL) ReverseGeocodeURL(l geo.Location) string {
67 | return string(b) + "reverse?" + fmt.Sprintf("lat=%f&lon=%f", l.Lat, l.Lng)
68 | }
69 |
70 | func (r *geocodeResponse) Location() (*geo.Location, error) {
71 | if len(r.Features) == 0 || len(r.Features[0].Geometry.Coordinates) < 2 {
72 | return nil, nil
73 | }
74 | p := r.Features[0].Geometry.Coordinates
75 | return &geo.Location{
76 | Lat: p[1],
77 | Lng: p[0],
78 | }, nil
79 | }
80 |
81 | func (r *geocodeResponse) Address() (*geo.Address, error) {
82 | if len(r.Features) == 0 || r.Features[0].Properties.Label == "baninfo" {
83 | return nil, nil
84 | }
85 | p := r.Features[0].Properties
86 | c := r.parseContext()
87 |
88 | if p.Type == "street" || p.Type == "locality" {
89 | p.Street = p.Name
90 | }
91 | return &geo.Address{
92 | FormattedAddress: strings.Join(strings.Fields(strings.Trim(fmt.Sprintf("%s, %s, %s, %s, %s, %s, %s", p.Housenumber, p.Street, p.Postcode, p.City, c.county, c.state, "France"), " ,")), " "),
93 | HouseNumber: p.Housenumber,
94 | Street: p.Street,
95 | Postcode: p.Postcode,
96 | City: p.City,
97 | State: c.state,
98 | County: c.county,
99 | Country: "France",
100 | CountryCode: "FRA",
101 | }, nil
102 | }
103 |
104 | func (r *geocodeResponse) parseContext() *context {
105 | var c context
106 | if len(r.Features) > 0 {
107 | p := r.Features[0].Properties
108 | f := strings.Split(p.Context, ",")
109 | for i := range f {
110 | switch i {
111 | case 0:
112 | c.countyCode = f[i]
113 | case 1:
114 | c.county = strings.TrimSpace(f[i])
115 | case 2:
116 | c.state = strings.TrimSpace(f[i])
117 | }
118 | }
119 | }
120 | return &c
121 | }
122 |
--------------------------------------------------------------------------------
/openstreetmap/geocoder_test.go:
--------------------------------------------------------------------------------
1 | package openstreetmap_test
2 |
3 | import (
4 | "net/http"
5 | "net/http/httptest"
6 | "strings"
7 | "testing"
8 |
9 | "github.com/codingsince1985/geo-golang"
10 | "github.com/codingsince1985/geo-golang/openstreetmap"
11 | "github.com/stretchr/testify/assert"
12 | )
13 |
14 | func TestGeocode(t *testing.T) {
15 | ts := testServer(response1)
16 | defer ts.Close()
17 |
18 | geocoder := openstreetmap.GeocoderWithURL(ts.URL + "/")
19 | location, err := geocoder.Geocode("60 Collins St, Melbourne VIC 3000")
20 | assert.Nil(t, err)
21 | assert.Equal(t, geo.Location{Lat: -37.8157915, Lng: 144.9656171}, *location)
22 | }
23 |
24 | func TestReverseGeocode(t *testing.T) {
25 | ts := testServer(response2)
26 | defer ts.Close()
27 |
28 | geocoder := openstreetmap.GeocoderWithURL(ts.URL + "/")
29 | address, err := geocoder.ReverseGeocode(-37.8157915, 144.9656171)
30 | assert.Nil(t, err)
31 | assert.True(t, strings.Index(address.FormattedAddress, "Collins St") > 0)
32 | }
33 |
34 | func TestReverseGeocodeWithNoResult(t *testing.T) {
35 | ts := testServer(response3)
36 | defer ts.Close()
37 |
38 | geocoder := openstreetmap.GeocoderWithURL(ts.URL + "/")
39 | //geocoder := openstreetmap.Geocoder()
40 | addr, err := geocoder.ReverseGeocode(-37.8157915, 164.9656171)
41 | assert.Nil(t, addr)
42 | assert.NotNil(t, err)
43 | }
44 |
45 | func TestReverseGeocodeWithBrokenResponse(t *testing.T) {
46 | ts := testServer(response4)
47 | defer ts.Close()
48 |
49 | geocoder := openstreetmap.GeocoderWithURL(ts.URL + "/")
50 | addr, err := geocoder.ReverseGeocode(-37.8157915, 164.9656171)
51 | assert.Nil(t, addr)
52 | assert.NotNil(t, err)
53 | }
54 |
55 | func testServer(response string) *httptest.Server {
56 | return httptest.NewServer(http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
57 | resp.Write([]byte(response))
58 | }))
59 | }
60 |
61 | const (
62 | response1 = `[
63 | {
64 | "place_id":"133372311",
65 | "licence":"Data © OpenStreetMap contributors, ODbL 1.0. http:\/\/www.openstreetmap.org\/copyright",
66 | "osm_type":"way",
67 | "osm_id":"316166613",
68 | "boundingbox":[
69 | "-37.8162553",
70 | "-37.815533",
71 | "144.9640149",
72 | "144.9665099"
73 | ],
74 | "lat":"-37.8157915",
75 | "lon":"144.9656171",
76 | "display_name":"Collins Street, Melbourne, City of Melbourne, Greater Melbourne, Victoria, 3000, Australia",
77 | "class":"highway",
78 | "type":"tertiary",
79 | "importance":0.51
80 | }
81 | ]`
82 | response2 = `{
83 | "place_id":"5122082",
84 | "licence":"Data © OpenStreetMap contributors, ODbL 1.0. http:\/\/www.openstreetmap.org\/copyright",
85 | "osm_type":"node",
86 | "osm_id":"594206614",
87 | "lat":"-37.8158091",
88 | "lon":"144.9656492",
89 | "display_name":"Telstra, Collins Street, Melbourne, City of Melbourne, Greater Melbourne, Victoria, 3000, Australia",
90 | "address":{
91 | "telephone":"Telstra",
92 | "road":"Collins Street",
93 | "suburb":"Melbourne",
94 | "city":"Melbourne",
95 | "county":"City of Melbourne",
96 | "region":"Greater Melbourne",
97 | "state":"Victoria",
98 | "postcode":"3000",
99 | "country":"Australia",
100 | "country_code":"au"
101 | },
102 | "boundingbox":[
103 | "-37.8159091",
104 | "-37.8157091",
105 | "144.9655492",
106 | "144.9657492"
107 | ]
108 | }`
109 | response3 = `{
110 | "error":"Unable to geocode"
111 | }`
112 | response4 = `{
113 | broken response
114 | }`
115 | )
116 |
--------------------------------------------------------------------------------
/amap/amap_test.go:
--------------------------------------------------------------------------------
1 | package amap_test
2 |
3 | import (
4 | "net/http"
5 | "net/http/httptest"
6 | "os"
7 | "testing"
8 |
9 | "github.com/codingsince1985/geo-golang"
10 | "github.com/codingsince1985/geo-golang/amap"
11 | "github.com/stretchr/testify/assert"
12 | )
13 |
14 | var key = os.Getenv("AMAP_APP_KEY")
15 |
16 | func TestGeocode(t *testing.T) {
17 | ts := testServer(response1)
18 | defer ts.Close()
19 |
20 | geocoder := amap.Geocoder(key, 1000, ts.URL+"/")
21 | location, err := geocoder.Geocode("北京市海淀区清河街道西二旗西路领秀新硅谷")
22 |
23 | assert.NoError(t, err)
24 | assert.NotNil(t, location)
25 | if location != nil {
26 | assert.Equal(t, geo.Location{Lat: 40.055106, Lng: 116.309866}, *location)
27 | }
28 | }
29 |
30 | func TestReverseGeocode(t *testing.T) {
31 | ts := testServer(response2)
32 | defer ts.Close()
33 |
34 | geocoder := amap.Geocoder(key, 1000, ts.URL+"/")
35 | address, err := geocoder.ReverseGeocode(116.3084202915042, 116.3084202915042)
36 |
37 | assert.NoError(t, err)
38 | assert.Equal(t, address.FormattedAddress, "北京市海淀区上地街道树村郊野公园")
39 | }
40 |
41 | func TestReverseGeocodeWithNoResult(t *testing.T) {
42 | ts := testServer(response3)
43 | defer ts.Close()
44 |
45 | geocoder := amap.Geocoder(key, 1000, ts.URL+"/")
46 | addr, err := geocoder.ReverseGeocode(-37.81375, 164.97176)
47 |
48 | assert.NoError(t, err)
49 | assert.Nil(t, addr)
50 | }
51 |
52 | func testServer(response string) *httptest.Server {
53 | return httptest.NewServer(http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
54 | resp.Write([]byte(response))
55 | }))
56 | }
57 |
58 | const (
59 | response1 = `1OK100001北京市海淀区清河街道西二旗西路领秀新硅谷中国北京市010北京市海淀区110108西二旗西路116.309866,40.055106住宅区`
60 | response2 = `1OK10000北京市海淀区上地街道树村郊野公园中国北京市010海淀区110108上地街道110108022000树村郊野公园风景名胜;公园广场;公园马连洼北路29号116.299587,40.034620北147.306116.303276,40.035542上地110108116.256057,40.054273西北 110108116.281156,40.028654马连洼110108`
61 | response3 = `1OK10000`
62 | )
63 |
--------------------------------------------------------------------------------
/locationiq/geocoder_test.go:
--------------------------------------------------------------------------------
1 | package locationiq
2 |
3 | import (
4 | "net/http"
5 | "net/http/httptest"
6 | "strings"
7 | "testing"
8 | )
9 |
10 | func TestGeocodeYieldsResult(t *testing.T) {
11 | ts := testServer(responseForGeocode)
12 | defer ts.Close()
13 |
14 | gc := Geocoder("foobar", 18, ts.URL+"/")
15 | l, err := gc.Geocode("Seidlstraße 26, 80335 München")
16 |
17 | expLat := 48.1453641
18 | expLon := 11.5582083
19 |
20 | if err != nil {
21 | t.Errorf("Expected nil error, got %v", err)
22 | }
23 |
24 | if expLat != l.Lat {
25 | t.Errorf("Expected latitude: %f, got %f", expLat, l.Lat)
26 | }
27 |
28 | if expLon != l.Lng {
29 | t.Errorf("Expected longitude %f, got %f", expLon, l.Lng)
30 | }
31 | }
32 |
33 | func TestGeocodeYieldsNoResult(t *testing.T) {
34 | ts := testServer("[]")
35 | defer ts.Close()
36 |
37 | gc := Geocoder("foobar", 18, ts.URL+"/")
38 | l, err := gc.Geocode("Seidlstraße 26, 80335 München")
39 |
40 | if l != nil {
41 | t.Errorf("Expected nil, got %#v", l)
42 | }
43 |
44 | if err != nil {
45 | t.Errorf("Expected nil error, got %v", err)
46 | }
47 | }
48 |
49 | func TestReverseGeocodeYieldsResult(t *testing.T) {
50 | ts := testServer(responseForReverse)
51 | defer ts.Close()
52 |
53 | gc := Geocoder("foobar", 18, ts.URL+"/")
54 | addr, err := gc.ReverseGeocode(48.1453641, 11.5582083)
55 |
56 | if err != nil {
57 | t.Errorf("Expected nil error, got %v", err)
58 | }
59 | if !strings.HasPrefix(addr.FormattedAddress, "26, Seidlstraße") {
60 | t.Errorf("Expected address string starting with %s, got string: %s", "26, Seidlstraße", addr)
61 | }
62 | }
63 |
64 | func TestReverseGeocodeYieldsNoResult(t *testing.T) {
65 | ts := testServer(errorResponse)
66 | defer ts.Close()
67 |
68 | gc := Geocoder("foobar", 18, ts.URL+"/")
69 | addr, err := gc.ReverseGeocode(48.1453641, 11.5582083)
70 |
71 | if err == nil {
72 | t.Error("Expected error, got nil")
73 | }
74 | if addr != nil {
75 | t.Errorf("Expected nil as address, got: %s", addr)
76 | }
77 | }
78 |
79 | func testServer(response string) *httptest.Server {
80 | return httptest.NewServer(http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
81 | resp.Write([]byte(response))
82 | }))
83 | }
84 |
85 | const (
86 | responseForGeocode = `[
87 | {
88 | "place_id": "25798174",
89 | "licence": "Data © OpenStreetMap contributors, ODbL 1.0. http://www.openstreetmap.org/copyright",
90 | "osm_type": "node",
91 | "osm_id": "2475749822",
92 | "boundingbox": [
93 | "48.1453141",
94 | "48.1454141",
95 | "11.5581583",
96 | "11.5582583"
97 | ],
98 | "lat": "48.1453641",
99 | "lon": "11.5582083",
100 | "display_name": "26, Seidlstraße, Bezirksteil Augustenstraße, Maxvorstadt, Munich, Upper Bavaria, Free State of Bavaria, 80335, Germany",
101 | "class": "place",
102 | "type": "house",
103 | "importance": 0.111
104 | }
105 | ]`
106 | responseForReverse = `{
107 | "place_id": "25798174",
108 | "licence": "Data © OpenStreetMap contributors, ODbL 1.0. http://www.openstreetmap.org/copyright",
109 | "osm_type": "node",
110 | "osm_id": "2475749822",
111 | "lat": "48.1453641",
112 | "lon": "11.5582083",
113 | "display_name": "26, Seidlstraße, Bezirksteil Augustenstraße, Maxvorstadt, Munich, Upper Bavaria, Free State of Bavaria, 80335, Germany",
114 | "address": {
115 | "house_number": "26",
116 | "road": "Seidlstraße",
117 | "suburb": "Maxvorstadt",
118 | "city": "Munich",
119 | "state_district": "Upper Bavaria",
120 | "state": "Free State of Bavaria",
121 | "postcode": "80335",
122 | "country": "Germany",
123 | "country_code": "de"
124 | },
125 | "boundingbox": [
126 | "48.1452641",
127 | "48.1454641",
128 | "11.5581083",
129 | "11.5583083"
130 | ]
131 | }`
132 | errorResponse = `{
133 | "error": "Unable to geocode"
134 | }`
135 | )
136 |
--------------------------------------------------------------------------------
/google/geocoder.go:
--------------------------------------------------------------------------------
1 | // Package google is a geo-golang based Google Geo Location API
2 | // https://developers.google.com/maps/documentation/geocoding/intro
3 | package google
4 |
5 | import (
6 | "fmt"
7 |
8 | "github.com/codingsince1985/geo-golang"
9 | )
10 |
11 | type (
12 | baseURL string
13 | geocodeResponse struct {
14 | Results []struct {
15 | FormattedAddress string `json:"formatted_address"`
16 | AddressComponents []googleAddressComponent `json:"address_components"`
17 | Geometry struct {
18 | Location geo.Location
19 | }
20 | }
21 | Status string `json:"status"`
22 | }
23 | googleAddressComponent struct {
24 | LongName string `json:"long_name"`
25 | ShortName string `json:"short_name"`
26 | Types []string `json:"types"`
27 | }
28 | )
29 |
30 | const (
31 | statusOK = "OK"
32 | statusNoResults = "ZERO_RESULTS"
33 | componentTypeHouseNumber = "street_number"
34 | componentTypeStreetName = "route"
35 | componentTypeSuburb = "sublocality"
36 | componentTypeLocality = "locality"
37 | componentTypeStateDistrict = "administrative_area_level_2"
38 | componentTypeState = "administrative_area_level_1"
39 | componentTypeCountry = "country"
40 | componentTypePostcode = "postal_code"
41 | )
42 |
43 | // Geocoder constructs Google geocoder
44 | func Geocoder(apiKey string, baseURLs ...string) geo.Geocoder {
45 | return geo.HTTPGeocoder{
46 | EndpointBuilder: baseURL(getURL(apiKey, baseURLs...)),
47 | ResponseParserFactory: func() geo.ResponseParser { return &geocodeResponse{} },
48 | }
49 | }
50 |
51 | func getURL(apiKey string, baseURLs ...string) string {
52 | if len(baseURLs) > 0 {
53 | return baseURLs[0]
54 | }
55 | return fmt.Sprintf("https://maps.googleapis.com/maps/api/geocode/json?key=%s&", apiKey)
56 | }
57 |
58 | func (b baseURL) GeocodeURL(address string) string { return string(b) + "address=" + address }
59 |
60 | func (b baseURL) ReverseGeocodeURL(l geo.Location) string {
61 | return string(b) + fmt.Sprintf("result_type=street_address&latlng=%f,%f", l.Lat, l.Lng)
62 | }
63 |
64 | func (r *geocodeResponse) Location() (*geo.Location, error) {
65 | if r.Status == statusNoResults {
66 | return nil, nil
67 | } else if r.Status != statusOK {
68 | return nil, fmt.Errorf("geocoding error: %s", r.Status)
69 | }
70 |
71 | return &r.Results[0].Geometry.Location, nil
72 | }
73 |
74 | func (r *geocodeResponse) Address() (*geo.Address, error) {
75 | if r.Status == statusNoResults {
76 | return nil, nil
77 | } else if r.Status != statusOK {
78 | return nil, fmt.Errorf("reverse geocoding error: %s", r.Status)
79 | }
80 |
81 | if len(r.Results) == 0 || len(r.Results[0].AddressComponents) == 0 {
82 | return nil, nil
83 | }
84 |
85 | addr := parseGoogleResult(r)
86 |
87 | return addr, nil
88 | }
89 |
90 | func parseGoogleResult(r *geocodeResponse) *geo.Address {
91 | addr := &geo.Address{}
92 | res := r.Results[0]
93 | addr.FormattedAddress = res.FormattedAddress
94 | OuterLoop:
95 | for _, comp := range res.AddressComponents {
96 | for _, typ := range comp.Types {
97 | switch typ {
98 | case componentTypeHouseNumber:
99 | addr.HouseNumber = comp.LongName
100 | continue OuterLoop
101 | case componentTypeStreetName:
102 | addr.Street = comp.LongName
103 | continue OuterLoop
104 | case componentTypeSuburb:
105 | addr.Suburb = comp.LongName
106 | continue OuterLoop
107 | case componentTypeLocality:
108 | addr.City = comp.LongName
109 | continue OuterLoop
110 | case componentTypeStateDistrict:
111 | addr.StateDistrict = comp.LongName
112 | continue OuterLoop
113 | case componentTypeState:
114 | addr.State = comp.LongName
115 | addr.StateCode = comp.ShortName
116 | continue OuterLoop
117 | case componentTypeCountry:
118 | addr.Country = comp.LongName
119 | addr.CountryCode = comp.ShortName
120 | continue OuterLoop
121 | case componentTypePostcode:
122 | addr.Postcode = comp.LongName
123 | continue OuterLoop
124 | }
125 | }
126 | }
127 |
128 | return addr
129 | }
130 |
--------------------------------------------------------------------------------
/amap/amap.go:
--------------------------------------------------------------------------------
1 | package amap
2 |
3 | import (
4 | "encoding/xml"
5 | "fmt"
6 | "strings"
7 |
8 | "github.com/codingsince1985/geo-golang"
9 | )
10 |
11 | type (
12 | baseURL string
13 | geocodeResponse struct {
14 | XMLName xml.Name `xml:"response"`
15 | Status int `xml:"status"`
16 | Info string `xml:"info"`
17 | Infocode int `xml:"infocode"`
18 | Count int `xml:"count"`
19 | Geocodes []struct {
20 | FormattedAddress string `xml:"formatted_address"`
21 | Country string `xml:"country"`
22 | Province string `xml:"province"`
23 | Citycode string `xml:"citycode"`
24 | City string `xml:"city"`
25 | District string `xml:"district"`
26 | Adcode string `xml:"adcode"`
27 | Street string `xml:"street"`
28 | Number string `xml:"number"`
29 | Location string `xml:"location"`
30 | Level string `xml:"level"`
31 | } `xml:"geocodes>geocode"`
32 | Regeocode struct {
33 | FormattedAddress string `xml:"formatted_address"`
34 | AddressComponent struct {
35 | Country string `xml:"country"`
36 | Township string `xml:"township"`
37 | District string `xml:"district"`
38 | Adcode string `xml:"adcode"`
39 | Province string `xml:"province"`
40 | Citycode string `xml:"citycode"`
41 | StreetNumber struct {
42 | Number string `xml:"number"`
43 | Location string `xml:"location"`
44 | Direction string `xml:"direction"`
45 | Distance string `xml:"distance"`
46 | Street string `xml:"street"`
47 | } `xml:"streetNumber"`
48 | } `xml:"addressComponent"`
49 | } `xml:"regeocode"`
50 | }
51 | )
52 |
53 | const (
54 | statusOK = 1
55 | )
56 |
57 | var r = 1000
58 |
59 | // Geocoder constructs AMAP geocoder
60 | func Geocoder(key string, radius int, baseURLs ...string) geo.Geocoder {
61 | if radius > 0 {
62 | r = radius
63 | }
64 | return geo.HTTPGeocoder{
65 | EndpointBuilder: baseURL(getURL(key, baseURLs...)),
66 | ResponseParserFactory: func() geo.ResponseParser { return &geocodeResponse{} },
67 | ResponseUnmarshaler: &geo.XMLUnmarshaler{},
68 | }
69 | }
70 |
71 | func getURL(apiKey string, baseURLs ...string) string {
72 | if len(baseURLs) > 0 {
73 | return baseURLs[0]
74 | }
75 | return fmt.Sprintf("https://restapi.amap.com/v3/geocode/*?key=%s&", apiKey)
76 | }
77 |
78 | // GeocodeURL https://restapi.amap.com/v3/geocode/geo?&output=XML&key=APPKEY&address=ADDRESS
79 | func (b baseURL) GeocodeURL(address string) string {
80 | return strings.Replace(string(b), "*", "geo", 1) + fmt.Sprintf("output=XML&address=%s", address)
81 | }
82 |
83 | // ReverseGeocodeURL https://restapi.amap.com/v3/geocode/regeo?output=XML&key=APPKEY&radius=1000&extensions=all&location=31.225696563611,121.49884033194
84 | func (b baseURL) ReverseGeocodeURL(l geo.Location) string {
85 | return strings.Replace(string(b), "*", "regeo", 1) + fmt.Sprintf("output=XML&location=%f,%f&radius=%d&extensions=all", l.Lng, l.Lat, r)
86 | }
87 |
88 | func (r *geocodeResponse) Location() (*geo.Location, error) {
89 | var location = &geo.Location{}
90 | if len(r.Geocodes) == 0 {
91 | return nil, nil
92 | }
93 | if r.Status != statusOK {
94 | return nil, fmt.Errorf("geocoding error: %v", r.Status)
95 | }
96 | fmt.Sscanf(string(r.Geocodes[0].Location), "%f,%f", &location.Lng, &location.Lat)
97 | return location, nil
98 | }
99 |
100 | func (r *geocodeResponse) Address() (*geo.Address, error) {
101 | if r.Status != statusOK {
102 | return nil, fmt.Errorf("reverse geocoding error: %v", r.Status)
103 | }
104 |
105 | addr := parseAmapResult(r)
106 |
107 | return addr, nil
108 | }
109 |
110 | func parseAmapResult(r *geocodeResponse) *geo.Address {
111 | addr := &geo.Address{}
112 | res := r.Regeocode
113 | addr.FormattedAddress = string(res.FormattedAddress)
114 | addr.HouseNumber = string(res.AddressComponent.StreetNumber.Number)
115 | addr.Street = string(res.AddressComponent.StreetNumber.Street)
116 | addr.Suburb = string(res.AddressComponent.District)
117 | addr.State = string(res.AddressComponent.Province)
118 | addr.Country = string(res.AddressComponent.Country)
119 |
120 | if (*addr == geo.Address{}) {
121 | return nil
122 | }
123 | return addr
124 | }
125 |
--------------------------------------------------------------------------------
/here/search/geocoder_test.go:
--------------------------------------------------------------------------------
1 | package search_test
2 |
3 | import (
4 | "net/http"
5 | "net/http/httptest"
6 | "os"
7 | "strings"
8 | "testing"
9 |
10 | "github.com/stretchr/testify/assert"
11 | "github.com/stretchr/testify/require"
12 |
13 | "github.com/codingsince1985/geo-golang"
14 | "github.com/codingsince1985/geo-golang/here/search"
15 | )
16 |
17 | var apiKey = os.Getenv("HERE_API_KEY")
18 |
19 | func TestGeocode(t *testing.T) {
20 | ts := testServer(response1)
21 | defer ts.Close()
22 |
23 | geocoder := search.Geocoder(apiKey, ts.URL+"/")
24 | location, err := geocoder.Geocode("60 Collins St, Melbourne VIC 3000")
25 | require.NoError(t, err, "Geocode error")
26 | require.NotNil(t, location, "Geocode location")
27 | assert.Equal(t, geo.Location{Lat: -37.81375, Lng: 144.97176}, *location)
28 | }
29 |
30 | func TestReverseGeocode(t *testing.T) {
31 | ts := testServer(response2)
32 | defer ts.Close()
33 |
34 | geocoder := search.Geocoder(apiKey, ts.URL+"/")
35 | address, err := geocoder.ReverseGeocode(-37.81375, 144.97176)
36 | require.NoError(t, err, "ReverseGeocode address")
37 | require.NotNil(t, address, "ReverseGeocode address")
38 | assert.True(t, strings.HasPrefix(address.FormattedAddress, "56-64 Collins St"))
39 | }
40 |
41 | func TestReverseGeocodeWithNoResult(t *testing.T) {
42 | ts := testServer(response3)
43 | defer ts.Close()
44 |
45 | geocoder := search.Geocoder(apiKey, ts.URL+"/")
46 | addr, _ := geocoder.ReverseGeocode(-37.81375, 164.97176)
47 | assert.Nil(t, addr)
48 | }
49 |
50 | func testServer(response string) *httptest.Server {
51 | return httptest.NewServer(http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
52 | resp.Write([]byte(response))
53 | }))
54 | }
55 |
56 | const (
57 | response1 = `{
58 | "items": [
59 | {
60 | "title": "60 Collins St, Melbourne VIC 3000, Australia",
61 | "id": "here:af:streetsection:ek.gZtN5HHOffWV1FK-xUB:CggIBCDu357RAhABGgI2MChk",
62 | "resultType": "houseNumber",
63 | "houseNumberType": "PA",
64 | "address": {
65 | "label": "60 Collins St, Melbourne VIC 3000, Australia",
66 | "countryCode": "AUS",
67 | "countryName": "Australia",
68 | "stateCode": "VIC",
69 | "state": "Victoria",
70 | "city": "Melbourne",
71 | "street": "Collins St",
72 | "postalCode": "3000",
73 | "houseNumber": "60"
74 | },
75 | "position": {
76 | "lat": -37.81375,
77 | "lng": 144.97176
78 | },
79 | "access": [
80 | {
81 | "lat": -37.81393,
82 | "lng": 144.97184
83 | }
84 | ],
85 | "mapView": {
86 | "west": 144.97062,
87 | "south": -37.81465,
88 | "east": 144.9729,
89 | "north": -37.81285
90 | },
91 | "scoring": {
92 | "queryScore": 1,
93 | "fieldScore": {
94 | "state": 1,
95 | "city": 1,
96 | "streets": [
97 | 1
98 | ],
99 | "houseNumber": 1,
100 | "postalCode": 1
101 | }
102 | }
103 | }
104 | ]
105 | }`
106 | response2 = `{
107 | "items": [
108 | {
109 | "title": "56-64 Collins St, Melbourne VIC 3000, Australia",
110 | "id": "here:af:streetsection:ek.gZtN5HHOffWV1FK-xUB:CggIBCChp5agARABGgU1Ni02NA",
111 | "resultType": "houseNumber",
112 | "houseNumberType": "PA",
113 | "address": {
114 | "label": "56-64 Collins St, Melbourne VIC 3000, Australia",
115 | "countryCode": "AUS",
116 | "countryName": "Australia",
117 | "stateCode": "VIC",
118 | "state": "Victoria",
119 | "city": "Melbourne",
120 | "street": "Collins St",
121 | "postalCode": "3000",
122 | "houseNumber": "56-64"
123 | },
124 | "position": {
125 | "lat": -37.81375,
126 | "lng": 144.97176
127 | },
128 | "access": [
129 | {
130 | "lat": -37.81393,
131 | "lng": 144.97184
132 | }
133 | ],
134 | "distance": 0,
135 | "mapView": {
136 | "west": 144.95405,
137 | "south": -37.81909,
138 | "east": 144.97391,
139 | "north": -37.81323
140 | }
141 | }
142 | ]
143 | }`
144 | response3 = `{
145 | "items": []
146 | }`
147 | )
148 |
--------------------------------------------------------------------------------
/mapquest/nominatim/geocoder_test.go:
--------------------------------------------------------------------------------
1 | package nominatim_test
2 |
3 | import (
4 | "net/http"
5 | "net/http/httptest"
6 | "os"
7 | "strings"
8 | "testing"
9 |
10 | "github.com/codingsince1985/geo-golang"
11 | "github.com/codingsince1985/geo-golang/mapquest/nominatim"
12 | "github.com/stretchr/testify/assert"
13 | )
14 |
15 | var key = os.Getenv("MAPQUEST_NOMINATUM_KEY")
16 |
17 | func TestGeocode(t *testing.T) {
18 | ts := testServer(response1)
19 | defer ts.Close()
20 |
21 | geocoder := nominatim.Geocoder(key, ts.URL+"/")
22 | location, err := geocoder.Geocode("60 Collins St, Melbourne VIC 3000")
23 | assert.NoError(t, err)
24 | assert.Equal(t, geo.Location{Lat: -37.8137433689794, Lng: 144.971745104488}, *location)
25 | }
26 |
27 | func TestReverseGeocode(t *testing.T) {
28 | ts := testServer(response2)
29 | defer ts.Close()
30 |
31 | geocoder := nominatim.Geocoder(key, ts.URL+"/")
32 | addr, err := geocoder.ReverseGeocode(-37.8137433689794, 144.971745104488)
33 | assert.NoError(t, err)
34 | assert.True(t, strings.HasPrefix(addr.FormattedAddress, "Reserve Bank of Australia"))
35 |
36 | ts2 := testServer(response4)
37 | defer ts2.Close()
38 |
39 | geocoder = nominatim.Geocoder(key, ts2.URL+"/")
40 | addr, err = geocoder.ReverseGeocode(43.0280986, -78.8136961)
41 | assert.NoError(t, err)
42 | assert.Equal(t, "Audubon Industrial Park", addr.City)
43 | }
44 |
45 | func TestReverseGeocodeWithNoResult(t *testing.T) {
46 | ts := testServer(response3)
47 | defer ts.Close()
48 |
49 | geocoder := nominatim.Geocoder(key, ts.URL+"/")
50 | //geocoder := nominatim.Geocoder(key)
51 | addr, err := geocoder.ReverseGeocode(-37.8137433689794, 164.971745104488)
52 | assert.NotNil(t, err)
53 | assert.Nil(t, addr)
54 | }
55 |
56 | func testServer(response string) *httptest.Server {
57 | return httptest.NewServer(http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
58 | resp.Write([]byte(response))
59 | }))
60 | }
61 |
62 | const (
63 | response1 = `[
64 | {
65 | "place_id":"2685280165",
66 | "licence":"Data \u00a9 OpenStreetMap contributors, ODbL 1.0. http:\/\/www.openstreetmap.org\/copyright",
67 | "osm_type":"node",
68 | "osm_id":"1038589059",
69 | "boundingbox":[
70 | "-37.8137433689794",
71 | "-37.8137433689794",
72 | "144.971745104488",
73 | "144.971745104488"
74 | ],
75 | "lat":"-37.8137433689794",
76 | "lon":"144.971745104488",
77 | "display_name":"60, Collins Street, Melbourne, City of Melbourne, Greater Melbourne, Victoria, 3000, Australia",
78 | "class":"place",
79 | "type":"house",
80 | "importance":0.411
81 | }
82 | ]`
83 | response2 = `{
84 | "place_id":"70709718",
85 | "licence":"Data \u00a9 OpenStreetMap contributors, ODbL 1.0. http:\/\/www.openstreetmap.org\/copyright",
86 | "osm_type":"way",
87 | "osm_id":"52554876",
88 | "lat":"-37.81362105",
89 | "lon":"144.971609569165",
90 | "display_name":"Reserve Bank of Australia, Exhibition Street, Melbourne, City of Melbourne, Greater Melbourne, Victoria, 3000, Australia",
91 | "address":{
92 | "building":"Reserve Bank of Australia",
93 | "road":"Exhibition Street",
94 | "suburb":"Melbourne",
95 | "city":"Melbourne",
96 | "county":"City of Melbourne",
97 | "region":"Greater Melbourne",
98 | "state":"Victoria",
99 | "postcode":"3000",
100 | "country":"Australia",
101 | "country_code":"au"
102 | }
103 | }`
104 | response3 = `{
105 | "error":"Unable to geocode"
106 | }`
107 | response4 = `{
108 | "place_id": 259174465,
109 | "licence": "Data \u00a9 OpenStreetMap contributors, ODbL 1.0. https:\/\/osm.org\/copyright",
110 | "osm_type": "way",
111 | "osm_id": 672030029,
112 | "lat": "43.0275119527027",
113 | "lon": "-78.81372135810811",
114 | "display_name": "188, Commerce Drive, Audubon Industrial Park, Amherst Town, Erie County, New York, 14228, United States of America",
115 | "address": {
116 | "house_number": "188",
117 | "road": "Commerce Drive",
118 | "hamlet": "Audubon Industrial Park",
119 | "county": "Erie County",
120 | "state": "New York",
121 | "postcode": "14228",
122 | "country": "United States of America",
123 | "country_code": "us"
124 | },
125 | "boundingbox": [
126 | "43.027411952703",
127 | "43.027611952703",
128 | "-78.813821358108",
129 | "-78.813621358108"
130 | ]
131 | }`
132 | )
133 |
--------------------------------------------------------------------------------
/http_geocoder.go:
--------------------------------------------------------------------------------
1 | package geo
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "encoding/xml"
7 | "errors"
8 | "io"
9 | "net/http"
10 | "net/url"
11 | "strconv"
12 | "strings"
13 | "time"
14 | )
15 |
16 | // DefaultTimeout for the request execution
17 | const DefaultTimeout = time.Second * 8
18 |
19 | // ErrTimeout occurs when no response returned within timeoutInSeconds
20 | var ErrTimeout = errors.New("TIMEOUT")
21 |
22 | // EndpointBuilder defines functions that build urls for geocode/reverse geocode
23 | type EndpointBuilder interface {
24 | GeocodeURL(string) string
25 | ReverseGeocodeURL(Location) string
26 | }
27 |
28 | // ResponseParserFactory creates a new ResponseParser
29 | type ResponseParserFactory func() ResponseParser
30 |
31 | // ResponseParser defines functions that parse response of geocode/reverse geocode
32 | type ResponseParser interface {
33 | Location() (*Location, error)
34 | Address() (*Address, error)
35 | }
36 |
37 | // HTTPGeocoder has EndpointBuilder and ResponseParser
38 | type HTTPGeocoder struct {
39 | EndpointBuilder
40 | ResponseParserFactory
41 | ResponseUnmarshaler
42 | }
43 |
44 | func (g HTTPGeocoder) geocodeWithContext(ctx context.Context, address string) (*Location, error) {
45 | responseParser := g.ResponseParserFactory()
46 | var responseUnmarshaler ResponseUnmarshaler = &JSONUnmarshaler{}
47 | if g.ResponseUnmarshaler != nil {
48 | responseUnmarshaler = g.ResponseUnmarshaler
49 | }
50 |
51 | type geoResp struct {
52 | l *Location
53 | e error
54 | }
55 | ch := make(chan geoResp, 1)
56 |
57 | go func(ch chan geoResp) {
58 | if err := response(ctx, g.GeocodeURL(url.QueryEscape(address)), responseUnmarshaler, responseParser); err != nil {
59 | ch <- geoResp{
60 | l: nil,
61 | e: err,
62 | }
63 | }
64 |
65 | loc, err := responseParser.Location()
66 | ch <- geoResp{
67 | l: loc,
68 | e: err,
69 | }
70 | }(ch)
71 |
72 | select {
73 | case <-ctx.Done():
74 | return nil, ErrTimeout
75 | case res := <-ch:
76 | return res.l, res.e
77 | }
78 | }
79 |
80 | // Geocode returns location for address
81 | func (g HTTPGeocoder) Geocode(address string) (*Location, error) {
82 | ctx, cancel := context.WithTimeout(context.TODO(), DefaultTimeout)
83 | defer cancel()
84 |
85 | return g.geocodeWithContext(ctx, address)
86 | }
87 |
88 | // ReverseGeocode returns address for location
89 | func (g HTTPGeocoder) ReverseGeocode(lat, lng float64) (*Address, error) {
90 | responseParser := g.ResponseParserFactory()
91 | var responseUnmarshaler ResponseUnmarshaler = &JSONUnmarshaler{}
92 | if g.ResponseUnmarshaler != nil {
93 | responseUnmarshaler = g.ResponseUnmarshaler
94 | }
95 |
96 | ctx, cancel := context.WithTimeout(context.TODO(), DefaultTimeout)
97 | defer cancel()
98 |
99 | type revResp struct {
100 | a *Address
101 | e error
102 | }
103 | ch := make(chan revResp, 1)
104 |
105 | go func(ch chan revResp) {
106 | if err := response(ctx, g.ReverseGeocodeURL(Location{lat, lng}), responseUnmarshaler, responseParser); err != nil {
107 | ch <- revResp{
108 | a: nil,
109 | e: err,
110 | }
111 | }
112 |
113 | addr, err := responseParser.Address()
114 | ch <- revResp{
115 | a: addr,
116 | e: err,
117 | }
118 | }(ch)
119 |
120 | select {
121 | case <-ctx.Done():
122 | return nil, ErrTimeout
123 | case res := <-ch:
124 | return res.a, res.e
125 | }
126 | }
127 |
128 | type ResponseUnmarshaler interface {
129 | Unmarshal(data []byte, v any) error
130 | }
131 |
132 | type JSONUnmarshaler struct{}
133 |
134 | func (*JSONUnmarshaler) Unmarshal(data []byte, v any) error {
135 | body := strings.Trim(string(data), " []")
136 | if body == "" {
137 | return nil
138 | }
139 | return json.Unmarshal([]byte(body), v)
140 | }
141 |
142 | type XMLUnmarshaler struct{}
143 |
144 | func (*XMLUnmarshaler) Unmarshal(data []byte, v any) error {
145 | return xml.Unmarshal(data, v)
146 | }
147 |
148 | // Response gets response from url
149 | func response(ctx context.Context, url string, unmarshaler ResponseUnmarshaler, obj ResponseParser) error {
150 | req, err := http.NewRequest(http.MethodGet, url, nil)
151 | if err != nil {
152 | return err
153 | }
154 | req = req.WithContext(ctx)
155 |
156 | req.Header.Add("User-Agent", "geo-golang/1.0")
157 |
158 | resp, err := http.DefaultClient.Do(req)
159 | if err != nil {
160 | return err
161 | }
162 |
163 | defer resp.Body.Close()
164 | data, err := io.ReadAll(resp.Body)
165 | if err != nil {
166 | return err
167 | }
168 |
169 | DebugLogger.Printf("Received response: %s\n", string(data))
170 | if err := unmarshaler.Unmarshal(data, obj); err != nil {
171 | ErrLogger.Printf("Error unmarshalling response: %s\n", err.Error())
172 | return err
173 | }
174 |
175 | return nil
176 | }
177 |
178 | // ParseFloat is a helper to parse a string to a float
179 | func ParseFloat(value string) float64 {
180 | f, _ := strconv.ParseFloat(value, 64)
181 | return f
182 | }
183 |
--------------------------------------------------------------------------------
/yandex/geocoder.go:
--------------------------------------------------------------------------------
1 | // Package yandex is a geo-golang based Yandex Maps Location API
2 | package yandex
3 |
4 | import (
5 | "fmt"
6 | "strconv"
7 | "strings"
8 |
9 | "github.com/codingsince1985/geo-golang"
10 | )
11 |
12 | type (
13 | baseURL string
14 | geocodeResponse struct {
15 | Response struct {
16 | GeoObjectCollection struct {
17 | MetaDataProperty struct {
18 | GeocoderResponseMetaData struct {
19 | Request string `json:"request"`
20 | Found string `json:"found"`
21 | Results string `json:"results"`
22 | } `json:"GeocoderResponseMetaData"`
23 | } `json:"metaDataProperty"`
24 | FeatureMember []*yandexFeatureMember `json:"featureMember"`
25 | } `json:"GeoObjectCollection"`
26 | } `json:"response"`
27 | }
28 |
29 | yandexFeatureMember struct {
30 | GeoObject struct {
31 | MetaDataProperty struct {
32 | GeocoderMetaData struct {
33 | Kind string `json:"kind"`
34 | Text string `json:"text"`
35 | Precision string `json:"precision"`
36 | Address struct {
37 | CountryCode string `json:"country_code"`
38 | PostalCode string `json:"postal_code"`
39 | Formatted string `json:"formatted"`
40 | Components []struct {
41 | Kind string `json:"kind"`
42 | Name string `json:"name"`
43 | } `json:"Components"`
44 | } `json:"Address"`
45 | } `json:"GeocoderMetaData"`
46 | } `json:"metaDataProperty"`
47 | Description string `json:"description"`
48 | Name string `json:"name"`
49 | BoundedBy struct {
50 | Envelope struct {
51 | LowerCorner string `json:"lowerCorner"`
52 | UpperCorner string `json:"upperCorner"`
53 | } `json:"Envelope"`
54 | } `json:"boundedBy"`
55 | Point struct {
56 | Pos string `json:"pos"`
57 | } `json:"Point"`
58 | } `json:"GeoObject"`
59 | }
60 | )
61 |
62 | const (
63 | componentTypeHouseNumber = "house"
64 | componentTypeStreetName = "street"
65 | componentTypeLocality = "locality"
66 | componentTypeStateDistrict = "area"
67 | componentTypeState = "province"
68 | componentTypeCountry = "country"
69 | )
70 |
71 | // Geocoder constructs Yandex geocoder
72 | func Geocoder(apiKey string, baseURLs ...string) geo.Geocoder {
73 | return geo.HTTPGeocoder{
74 | EndpointBuilder: baseURL(getURL(apiKey, baseURLs...)),
75 | ResponseParserFactory: func() geo.ResponseParser { return &geocodeResponse{} },
76 | }
77 | }
78 |
79 | func getURL(apiKey string, baseURLs ...string) string {
80 | if len(baseURLs) > 0 {
81 | return baseURLs[0]
82 | }
83 | return fmt.Sprintf("https://geocode-maps.yandex.ru/1.x/?results=1&lang=en_US&format=json&apikey=%s&", apiKey)
84 | }
85 |
86 | func (b baseURL) GeocodeURL(address string) string {
87 | return string(b) + "geocode=" + address
88 | }
89 |
90 | func (b baseURL) ReverseGeocodeURL(l geo.Location) string {
91 | return string(b) + fmt.Sprintf("sco=latlong&geocode=%f,%f", l.Lat, l.Lng)
92 | }
93 |
94 | func (r *geocodeResponse) Location() (*geo.Location, error) {
95 | if r.Response.GeoObjectCollection.MetaDataProperty.GeocoderResponseMetaData.Found == "0" {
96 | return nil, nil
97 | }
98 | if len(r.Response.GeoObjectCollection.FeatureMember) == 0 {
99 | return nil, nil
100 | }
101 | featureMember := r.Response.GeoObjectCollection.FeatureMember[0]
102 | result := &geo.Location{}
103 | latLng := strings.Split(featureMember.GeoObject.Point.Pos, " ")
104 | if len(latLng) > 1 {
105 | // Yandex return geo coord in format "long lat"
106 | result.Lat, _ = strconv.ParseFloat(latLng[1], 64)
107 | result.Lng, _ = strconv.ParseFloat(latLng[0], 64)
108 | }
109 |
110 | return result, nil
111 | }
112 |
113 | func (r *geocodeResponse) Address() (*geo.Address, error) {
114 | if r.Response.GeoObjectCollection.MetaDataProperty.GeocoderResponseMetaData.Found == "0" {
115 | return nil, nil
116 | }
117 | if len(r.Response.GeoObjectCollection.FeatureMember) == 0 {
118 | return nil, nil
119 | }
120 |
121 | return parseYandexResult(r.Response.GeoObjectCollection.FeatureMember[0]), nil
122 | }
123 |
124 | func parseYandexResult(r *yandexFeatureMember) *geo.Address {
125 | addr := &geo.Address{}
126 | res := r.GeoObject.MetaDataProperty.GeocoderMetaData
127 |
128 | for _, comp := range res.Address.Components {
129 | switch comp.Kind {
130 | case componentTypeHouseNumber:
131 | addr.HouseNumber = comp.Name
132 | continue
133 | case componentTypeStreetName:
134 | addr.Street = comp.Name
135 | continue
136 | case componentTypeLocality:
137 | addr.City = comp.Name
138 | continue
139 | case componentTypeStateDistrict:
140 | addr.StateDistrict = comp.Name
141 | continue
142 | case componentTypeState:
143 | addr.State = comp.Name
144 | continue
145 | case componentTypeCountry:
146 | addr.Country = comp.Name
147 | continue
148 | }
149 | }
150 |
151 | addr.Postcode = res.Address.PostalCode
152 | addr.CountryCode = res.Address.CountryCode
153 | addr.FormattedAddress = res.Address.Formatted
154 |
155 | return addr
156 | }
157 |
--------------------------------------------------------------------------------
/tomtom/geocoder_test.go:
--------------------------------------------------------------------------------
1 | package tomtom
2 |
3 | import (
4 | "math"
5 | "net/http"
6 | "net/http/httptest"
7 | "os"
8 | "testing"
9 |
10 | geo "github.com/codingsince1985/geo-golang"
11 | )
12 |
13 | var key = os.Getenv("TOMTOM_API_KEY")
14 |
15 | func TestGeocode(t *testing.T) {
16 | ts := testServer(geocodeResp)
17 | defer ts.Close()
18 |
19 | address := "1109 N Highland St, Arlington VA, US"
20 | geocoder := Geocoder(key, ts.URL)
21 | loc, err := geocoder.Geocode(address)
22 | if err != nil {
23 | t.Fatal(err)
24 | }
25 |
26 | if loc == nil {
27 | t.Fatalf("Address: %s - Not Found\n", address)
28 | }
29 | expected := geo.Location{Lng: -77.09464, Lat: 38.88669}
30 | if math.Abs(loc.Lng-expected.Lng) > eps {
31 | t.Fatalf("Got: %v\tExpected: %v\n", loc, expected)
32 | }
33 | if math.Abs(loc.Lat-expected.Lat) > eps {
34 | t.Fatalf("Got: %v\tExpected: %v\n", loc, expected)
35 | }
36 | }
37 |
38 | func TestReverseGeocode(t *testing.T) {
39 | ts := testServer(reverseResp)
40 | defer ts.Close()
41 |
42 | code := "US"
43 | state := "DC"
44 | lat := 38.88669
45 | lng := -77.09464
46 | geocoder := Geocoder(key, ts.URL)
47 | addr, err := geocoder.ReverseGeocode(lat, lng)
48 | if err != nil {
49 | t.Fatal(err)
50 | }
51 |
52 | if addr == nil {
53 | t.Fatalf("Location: lat:%f, lng:%f - Not Found\n", lat, lng)
54 | }
55 |
56 | if addr.CountryCode != code {
57 | t.Fatalf("Got: %v\tExpected: %v\n", addr.CountryCode, code)
58 | }
59 | if addr.State != state {
60 | t.Fatalf("Got: %v\tExpected: %v\n", addr.State, state)
61 | }
62 | }
63 |
64 | func testServer(response string) *httptest.Server {
65 | return httptest.NewServer(http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
66 | resp.Write([]byte(response))
67 | }))
68 | }
69 |
70 | const (
71 | eps = 1.0e-5
72 |
73 | geocodeResp = `
74 | {
75 | "summary": {
76 | "query": "1109 N Highland St, Arlington, VA 22201",
77 | "queryType": "NON_NEAR",
78 | "queryTime": 158,
79 | "numResults": 1,
80 | "offset": 0,
81 | "totalResults": 1,
82 | "fuzzyLevel": 1
83 | },
84 | "results": [
85 | {
86 | "type": "Point Address",
87 | "id": "US/PAD/p0/26924656",
88 | "score": 11.51,
89 | "address": {
90 | "streetNumber": "1109",
91 | "streetName": "N Highland St",
92 | "municipalitySubdivision": "Arlington, Clarendon Courthouse",
93 | "municipality": "Arlington",
94 | "countrySecondarySubdivision": "Arlington",
95 | "countryTertiarySubdivision": "Arlington",
96 | "countrySubdivision": "VA",
97 | "postalCode": "22201",
98 | "extendedPostalCode": "222012890",
99 | "countryCode": "US",
100 | "country": "United States Of America",
101 | "countryCodeISO3": "USA",
102 | "freeformAddress": "1109 N Highland St, Arlington, VA 222012890",
103 | "countrySubdivisionName": "Virginia"
104 | },
105 | "position": {
106 | "lat": 38.88669,
107 | "lon": -77.09464
108 | },
109 | "viewport": {
110 | "topLeftPoint": {
111 | "lat": 38.88759,
112 | "lon": -77.0958
113 | },
114 | "btmRightPoint": {
115 | "lat": 38.88579,
116 | "lon": -77.09348
117 | }
118 | },
119 | "entryPoints": [
120 | {
121 | "type": "main",
122 | "position": {
123 | "lat": 38.88667,
124 | "lon": -77.09488
125 | }
126 | }
127 | ]
128 | }
129 | ]
130 | }`
131 |
132 | reverseResp = `
133 | {
134 | "summary": {
135 | "queryTime": 504,
136 | "numResults": 1
137 | },
138 | "addresses": [
139 | {
140 | "address": {
141 | "buildingNumber": "414",
142 | "streetNumber": "414",
143 | "routeNumbers": [],
144 | "street": "Seward Sq SE",
145 | "streetName": "Seward Sq SE",
146 | "streetNameAndNumber": "414 Seward Sq SE",
147 | "countryCode": "US",
148 | "countrySubdivision": "DC",
149 | "countrySecondarySubdivision": "District of Columbia",
150 | "countryTertiarySubdivision": "Washington",
151 | "municipality": "Washington",
152 | "postalCode": "20003",
153 | "country": "United States Of America",
154 | "countryCodeISO3": "USA",
155 | "freeformAddress": "414 Seward Sq SE, Washington, DC 20003",
156 | "countrySubdivisionName": "District of Columbia"
157 | },
158 | "position": "38.886540,-77.000001"
159 | }
160 | ]
161 | }`
162 | )
163 |
--------------------------------------------------------------------------------
/geocod/geocoder_test.go:
--------------------------------------------------------------------------------
1 | package geocod
2 |
3 | import (
4 | "math"
5 | "net/http"
6 | "net/http/httptest"
7 | "os"
8 | "testing"
9 |
10 | geo "github.com/codingsince1985/geo-golang"
11 | )
12 |
13 | var key = os.Getenv("GECOD_API_KEY")
14 |
15 | func TestGeocode(t *testing.T) {
16 | ts := testServer(geocodeResp)
17 | defer ts.Close()
18 |
19 | address := "1109 N Highland St, Arlington VA"
20 | geocoder := Geocoder(key, ts.URL)
21 | loc, err := geocoder.Geocode(address)
22 | if err != nil {
23 | t.Fatal(err)
24 | }
25 |
26 | if loc == nil {
27 | t.Fatalf("Address: %s - Not Found\n", address)
28 | }
29 | expected := geo.Location{Lng: -77.094733, Lat: 38.886665}
30 | if math.Abs(loc.Lng-expected.Lng) > eps {
31 | t.Fatalf("Got: %v\tExpected: %v\n", loc, expected)
32 | }
33 | if math.Abs(loc.Lat-expected.Lat) > eps {
34 | t.Fatalf("Got: %v\tExpected: %v\n", loc, expected)
35 | }
36 | }
37 |
38 | func TestReverseGeocode(t *testing.T) {
39 | ts := testServer(reverseResp)
40 | defer ts.Close()
41 |
42 | code := "US"
43 | state := "DC"
44 | lat := 38.886665
45 | lng := -77.094733
46 | geocoder := Geocoder(key, ts.URL)
47 | addr, err := geocoder.ReverseGeocode(lat, lng)
48 | if err != nil {
49 | t.Fatal(err)
50 | }
51 |
52 | if addr == nil {
53 | t.Fatalf("Location: lat:%f, lng:%f - Not Found\n", lat, lng)
54 | }
55 |
56 | if addr.CountryCode != code {
57 | t.Fatalf("Got: %v\tExpected: %v\n", addr.CountryCode, code)
58 | }
59 | if addr.State != state {
60 | t.Fatalf("Got: %v\tExpected: %v\n", addr.State, state)
61 | }
62 | }
63 |
64 | func testServer(response string) *httptest.Server {
65 | return httptest.NewServer(http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
66 | resp.Write([]byte(response))
67 | }))
68 | }
69 |
70 | const (
71 | eps = 1.0e-5
72 |
73 | geocodeResp = `
74 | {
75 | "input": {
76 | "address_components": {
77 | "number": "1109",
78 | "predirectional": "N",
79 | "street": "Highland",
80 | "suffix": "St",
81 | "formatted_street": "N Highland St",
82 | "city": "Arlington",
83 | "state": "VA",
84 | "zip": "22201",
85 | "country": "US"
86 | },
87 | "formatted_address": "1109 N Highland St, Arlington, VA 22201"
88 | },
89 | "results": [
90 | {
91 | "address_components": {
92 | "number": "1109",
93 | "predirectional": "N",
94 | "street": "Highland",
95 | "suffix": "St",
96 | "formatted_street": "N Highland St",
97 | "city": "Arlington",
98 | "county": "Arlington County",
99 | "state": "VA",
100 | "zip": "22201",
101 | "country": "US"
102 | },
103 | "formatted_address": "1109 N Highland St, Arlington, VA 22201",
104 | "location": {
105 | "lat": 38.886665,
106 | "lng": -77.094733
107 | },
108 | "accuracy": 1,
109 | "accuracy_type": "rooftop",
110 | "source": "Virginia GIS Clearinghouse"
111 | }
112 | ]
113 | }`
114 |
115 | reverseResp = `
116 | {
117 | "results": [
118 | {
119 | "address_components": {
120 | "number": "500",
121 | "street": "H",
122 | "suffix": "St",
123 | "postdirectional": "NE",
124 | "formatted_street": "H St NE",
125 | "city": "Washington",
126 | "county": "District of Columbia",
127 | "state": "DC",
128 | "zip": "20002",
129 | "country": "US"
130 | },
131 | "formatted_address": "500 H St NE, Washington, DC 20002",
132 | "location": {
133 | "lat": 38.900203,
134 | "lng": -76.999507
135 | },
136 | "accuracy": 1,
137 | "accuracy_type": "nearest_street",
138 | "source": "TIGER/Line® dataset from the US Census Bureau"
139 | },
140 | {
141 | "address_components": {
142 | "number": "800",
143 | "street": "5th",
144 | "suffix": "St",
145 | "postdirectional": "NE",
146 | "formatted_street": "5th St NE",
147 | "city": "Washington",
148 | "county": "District of Columbia",
149 | "state": "DC",
150 | "zip": "20002",
151 | "country": "US"
152 | },
153 | "formatted_address": "800 5th St NE, Washington, DC 20002",
154 | "location": {
155 | "lat": 38.900203,
156 | "lng": -76.999507
157 | },
158 | "accuracy": 0.18,
159 | "accuracy_type": "nearest_street",
160 | "source": "TIGER/Line® dataset from the US Census Bureau"
161 | },
162 | {
163 | "address_components": {
164 | "number": "474",
165 | "street": "H",
166 | "suffix": "St",
167 | "postdirectional": "NE",
168 | "formatted_street": "H St NE",
169 | "city": "Washington",
170 | "county": "District of Columbia",
171 | "state": "DC",
172 | "zip": "20002",
173 | "country": "US"
174 | },
175 | "formatted_address": "474 H St NE, Washington, DC 20002",
176 | "location": {
177 | "lat": 38.900205,
178 | "lng": -76.99994
179 | },
180 | "accuracy": 0.18,
181 | "accuracy_type": "nearest_street",
182 | "source": "TIGER/Line® dataset from the US Census Bureau"
183 | }
184 | ]
185 | }`
186 | )
187 |
--------------------------------------------------------------------------------
/frenchapigouv/geocoder_test.go:
--------------------------------------------------------------------------------
1 | package frenchapigouv_test
2 |
3 | import (
4 | "strings"
5 | "testing"
6 |
7 | "net/http"
8 | "net/http/httptest"
9 |
10 | "github.com/codingsince1985/geo-golang"
11 | "github.com/codingsince1985/geo-golang/frenchapigouv"
12 | "github.com/stretchr/testify/assert"
13 | )
14 |
15 | func TestGeocode(t *testing.T) {
16 | ts := testServer(response1)
17 | defer ts.Close()
18 |
19 | geocoder := frenchapigouv.GeocoderWithURL(ts.URL + "/")
20 | location, err := geocoder.Geocode("Champ de Mars, 5 Avenue Anatole France, 75007 Paris")
21 | assert.Nil(t, err)
22 | assert.Equal(t, geo.Location{Lat: 48.859831, Lng: 2.328123}, *location)
23 | }
24 |
25 | func TestGeocodeWithNoResult(t *testing.T) {
26 | ts := testServer(response2)
27 | defer ts.Close()
28 |
29 | geocoder := frenchapigouv.GeocoderWithURL(ts.URL + "/")
30 | location, err := geocoder.Geocode("nowhere")
31 | assert.Nil(t, err)
32 | assert.Nil(t, location)
33 | }
34 |
35 | func TestReverseGeocode(t *testing.T) {
36 | ts := testServer(response1)
37 | defer ts.Close()
38 |
39 | geocoder := frenchapigouv.GeocoderWithURL(ts.URL + "/")
40 | address, err := geocoder.ReverseGeocode(48.859831, 2.328123)
41 | assert.Nil(t, err)
42 | assert.True(t, strings.HasPrefix(address.FormattedAddress, "5, Quai Anatole France,"))
43 | assert.Equal(t, "Île-de-France", address.State)
44 | assert.Equal(t, "Paris", address.County)
45 | }
46 |
47 | func TestReverseGeocodeWithTypeStreet(t *testing.T) {
48 | ts := testServer(responseStreet)
49 | defer ts.Close()
50 | geocoder := frenchapigouv.GeocoderWithURL(ts.URL + "/")
51 | address, err := geocoder.ReverseGeocode(50.720114, 3.156717)
52 | assert.Nil(t, err)
53 | assert.True(t, strings.HasPrefix(address.FormattedAddress, "Rue des Anges,"))
54 | assert.Equal(t, "Hauts-de-France", address.State)
55 | assert.Equal(t, "Nord", address.County)
56 | }
57 |
58 | func TestReverseGeocodeWithTypeLocality(t *testing.T) {
59 | ts := testServer(responseLocality)
60 | defer ts.Close()
61 | geocoder := frenchapigouv.GeocoderWithURL(ts.URL + "/")
62 | address, err := geocoder.ReverseGeocode(44.995637, 1.646584)
63 | assert.Nil(t, err)
64 | assert.True(t, strings.HasPrefix(address.FormattedAddress, "Route de Saint Denis les Martel (Les Quatre-Routes-du-Lot),"))
65 | assert.Equal(t, "Occitanie", address.State)
66 | assert.Equal(t, "Lot", address.County)
67 | }
68 |
69 | func TestReverseGeocodeWithNoResult(t *testing.T) {
70 | ts := testServer(response2)
71 | defer ts.Close()
72 |
73 | geocoder := frenchapigouv.GeocoderWithURL(ts.URL + "/")
74 | addr, err := geocoder.ReverseGeocode(0, 0.34)
75 | assert.Nil(t, addr)
76 | assert.Nil(t, err)
77 | }
78 |
79 | func TestReverseGeocodeWithNoResultByDefaultPoints(t *testing.T) {
80 | ts := testServer(response3)
81 | defer ts.Close()
82 |
83 | geocoder := frenchapigouv.GeocoderWithURL(ts.URL + "/")
84 | addr, err := geocoder.ReverseGeocode(0, 0)
85 | assert.Nil(t, addr)
86 | assert.Nil(t, err)
87 | }
88 |
89 | func testServer(response string) *httptest.Server {
90 | return httptest.NewServer(http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
91 | resp.Write([]byte(response))
92 | }))
93 | }
94 |
95 | const (
96 | response1 = `[
97 | {
98 | "type": "FeatureCollection",
99 | "version": "draft",
100 | "features": [{
101 | "type": "Feature",
102 | "geometry": {
103 | "type": "Point",
104 | "coordinates": [2.328123, 48.859831]
105 | },
106 | "properties": {
107 | "label": "5 Quai Anatole France 75007 Paris",
108 | "score": 0.4882581475128644,
109 | "housenumber": "5",
110 | "citycode": "75107",
111 | "context": "75, Paris, Île-de-France",
112 | "postcode": "75007",
113 | "name": "5 Quai Anatole France",
114 | "id": "ADRNIVX_0000000270768224",
115 | "y": 6862409.3,
116 | "importance": 0.2765,
117 | "type": "housenumber",
118 | "city": "Paris",
119 | "x": 650705.5,
120 | "street": "Quai Anatole France"
121 | }
122 | }],
123 | "attribution": "BAN",
124 | "licence": "ODbL 1.0",
125 | "query": "Champ de Mars, 5 Avenue Anatole France, 75007 Paris",
126 | "limit": 10
127 | }
128 | ]`
129 | response2 = `{
130 | "type": "FeatureCollection",
131 | "version": "draft",
132 | "features": [],
133 | "attribution": "BAN",
134 | "licence": "ODbL 1.0",
135 | "limit": 1
136 | }`
137 | response3 = `{
138 | "type": "FeatureCollection",
139 | "version": "draft",
140 | "features": [{
141 | "type": "Feature",
142 | "geometry": {
143 | "type": "Point",
144 | "coordinates": [0.0, 0.0]
145 | },
146 | "properties": {
147 | "label": "baninfo",
148 | "score": 1.0,
149 | "name": "baninfo",
150 | "id": "db",
151 | "type": "info",
152 | "context": "20181125T223127",
153 | "distance": 0
154 | }
155 | }],
156 | "attribution": "BAN",
157 | "licence": "ODbL 1.0",
158 | "limit": 1
159 | }`
160 | responseStreet = `{
161 | "type": "FeatureCollection",
162 | "version": "draft",
163 | "features": [{
164 | "type": "Feature",
165 | "geometry": {
166 | "type": "Point",
167 | "coordinates": [3.156717,50.720114]
168 | },
169 | "properties": {
170 | "label": "Rue des Anges 59200 Tourcoing",
171 | "score": 0.7319487603305784,
172 | "id": "59599_0310",
173 | "name": "Rue des Anges",
174 | "postcode": "59200",
175 | "citycode": "59599",
176 | "x": 711087.23,
177 | "y": 7069277.25,
178 | "city": "Tourcoing",
179 | "context": "59, Nord, Hauts-de-France",
180 | "type": "street",
181 | "importance": 0.6878
182 | }
183 | }],
184 | "attribution": "BAN",
185 | "licence": "ETALAB-2.0",
186 | "query": "11B Rue des Anges Tourcoing 59200",
187 | "limit": 1
188 | }`
189 | responseLocality = `{
190 | "type": "FeatureCollection",
191 | "version": "draft",
192 | "features": [{
193 | "type": "Feature",
194 | "geometry": {
195 | "type": "Point",
196 | "coordinates": [1.646584,44.995637]
197 | },
198 | "properties": {
199 | "label": "Route de Saint Denis les Martel (Les Quatre-Routes-du-Lot) 46110 Le Vignon-en-Quercy",
200 | "score": 0.8454463636363636,
201 | "type": "locality",
202 | "importance": 0.29991,
203 | "id": "46232_0023",
204 | "name": "Route de Saint Denis les Martel (Les Quatre-Routes-du-Lot)",
205 | "postcode": "46110",
206 | "citycode": "46232",
207 | "oldcitycode": "46232",
208 | "x": 593348.58,
209 | "y": 6433848.43,
210 | "city": "Le Vignon-en-Quercy",
211 | "oldcity": "Les Quatre-Routes-du-Lot",
212 | "context": "46, Lot, Occitanie"
213 | }
214 | }],
215 | "attribution": "BAN",
216 | "licence": "ETALAB-2.0",
217 | "query": "Route de Saint-Denis-lès-Martel",
218 | "limit": 1
219 | }`
220 | )
221 |
--------------------------------------------------------------------------------
/baidu/baidu.go:
--------------------------------------------------------------------------------
1 | package baidu
2 |
3 | import (
4 | "fmt"
5 | "github.com/codingsince1985/geo-golang"
6 | "strings"
7 | )
8 |
9 | var (
10 | languageList = []string{"el", "gu", "en", "vi", "ca", "it", "iw", "sv", "eu", "ar", "cs", "gl", "id", "es", "en-GB", "ru", "sr", "nl", "pt", "tr", "tl", "lv", "en-AU", "lt", "th", "ro", "fil", "ta", "fr", "bg", "hr", "bn", "de", "hu", "fa", "hi", "pt-BR", "fi", "da", "ja", "te", "pt-PT", "ml", "ko", "kn", "sk", "zh-CN", "pl", "uk", "sl", "mr", "local"}
11 | )
12 |
13 | type (
14 | baseURL string
15 |
16 | //Response payload
17 | //{'result': {'addressComponent': {'adcode': '310101',
18 | // 'city': '上海市',
19 | // 'city_level': 2,
20 | // 'country': '中国',
21 | // 'country_code': 0,
22 | // 'country_code_iso': 'CHN',
23 | // 'country_code_iso2': 'CN',
24 | // 'direction': '东北',
25 | // 'distance': '91',
26 | // 'district': '黄浦区',
27 | // 'province': '上海市',
28 | // 'street': '中山南路',
29 | // 'street_number': '187',
30 | // 'town': '',
31 | // 'town_code': ''},
32 | // 'business': '外滩,陆家嘴,董家渡',
33 | // 'cityCode': 289,
34 | // 'formatted_address': '上海市黄浦区中山南路187',
35 | // 'location': {'lat': 31.22932842411674, 'lng': 121.50989077799083},
36 | // 'poiRegions': [],
37 | // 'pois': [],
38 | // 'roads': [],
39 | // 'sematic_description': ''},
40 | //'status': 0}
41 | geocodeResponse struct {
42 | Result struct {
43 | AddressComponent struct {
44 | AdCode string `json:"adcode"`
45 | City string `json:"city"`
46 | CityLevel int `json:"city_level"`
47 | Country string `json:"country"`
48 | CountryCode int `json:"country_code"`
49 | CountryCodeIso string `json:"country_code_iso"`
50 | CountryCodeIso2 string `json:"country_code_iso2"`
51 | Direction string `json:"direction"`
52 | Distance string `json:"distance"`
53 | District string `json:"district"`
54 | Province string `json:"province"`
55 | Street string `json:"street"`
56 | StreetNumber string `json:"street_number"`
57 | Town string `json:"town"`
58 | TownCode string `json:"town_code"`
59 | } `json:"addressComponent"`
60 | Business string `json:"business"`
61 | CityCode int `json:"cityCode"`
62 | FormattedAddress string `json:"formatted_address"`
63 | Location struct {
64 | Lat float64 `json:"lat"`
65 | Lng float64 `json:"lng"`
66 | } `json:"location"`
67 | PoiRegions []interface{} `json:"poiRegions"`
68 | Pois []interface{} `json:"pois"`
69 | Roads []interface{} `json:"roads"`
70 | SematicDescription string `json:"sematic_description"`
71 | } `json:"result"`
72 | Status int `json:"status"`
73 | }
74 | )
75 |
76 | const (
77 | statusOK = 0
78 | )
79 |
80 | var lang string
81 | var coordtype string
82 |
83 | // Geocoder constructs Baidu geocoder
84 | //
85 | // language: Baidu Map's API uses Chinese (zh-CN) by default. but it supports language argument to specify which language it
86 | // will use. If some fields don't have the target language version, it will be filled with Chinese version by default.
87 | // Acceptable languages arguments are :
88 | // "el", "gu", "en", "vi", "ca", "it", "iw", "sv", "eu", "ar", "cs", "gl", "id", "es", "en-GB", "ru", "sr", "nl", "pt",
89 | // "tr", "tl", "lv", "en-AU", "lt", "th", "ro", "fil", "ta", "fr", "bg", "hr", "bn", "de", "hu", "fa", "hi", "pt-BR",
90 | // "fi", "da", "ja", "te", "pt-PT", "ml", "ko", "kn", "sk", "zh-CN", "pl", "uk", "sl", "mr", "local"
91 | //
92 | // "local" is a special argument for language specifying, which means the language it uses depends on your location.
93 | //
94 | // coordtype:
95 | // According to league reasons, Chinese geo service providers uses encrypted coordinate system (GCJ02ll, BD09ll, BD09mc,
96 | // etc.) other than the World Geodetic System (WGS, especially WGS84ll).
97 | // Acceptable coordtype arguments are :
98 | // "bd09ll", "bd09mc", "gcj02ll", "wgs84ll"
99 | // default: "bd09ll"
100 | // You can use https://api.map.baidu.com/geoconv/v1/?coords=LONGITUDE,LATITUDE&from=1&to=5&ak=AK to convert from WGS84ll
101 | // to BD09ll coordination. API document: https://lbsyun.baidu.com/index.php?title=webapi/guide/changeposition
102 | func Geocoder(apiKey string, language string, coordtype string, baseURLs ...string) geo.Geocoder {
103 | if lang != "" {
104 | for _, item := range languageList {
105 | if language == item {
106 | lang = fmt.Sprintf("&language=%s", language)
107 | }
108 | }
109 | }
110 | switch coordtype {
111 | case "bd09ll":
112 | coordtype = "bd09ll"
113 | case "bd09mc":
114 | coordtype = "bd09mc"
115 | case "gcj02ll":
116 | coordtype = "gcj02ll"
117 | case "wgs84ll":
118 | coordtype = "wgs84ll"
119 | default:
120 | coordtype = "bd09ll"
121 | }
122 |
123 | return geo.HTTPGeocoder{
124 | EndpointBuilder: baseURL(getURL(apiKey, baseURLs...)),
125 | ResponseParserFactory: func() geo.ResponseParser { return &geocodeResponse{} },
126 | }
127 | }
128 |
129 | func getURL(apiKey string, baseURLs ...string) string {
130 | if len(baseURLs) > 0 {
131 | return baseURLs[0]
132 | }
133 | return fmt.Sprintf("https://api.map.baidu.com/*/v3/?ak=%s&", apiKey)
134 | }
135 |
136 | // GeocodeURL https://api.map.baidu.com/geocoding/v3/?ak=APPKEY&output=json&address=ADDRESS
137 | func (b baseURL) GeocodeURL(address string) string {
138 | return strings.Replace(string(b), "*", "geocoding", 1) + fmt.Sprintf("output=json&address=%s", address)
139 | }
140 |
141 | // ReverseGeocodeURL https://api.map.baidu.com/reverse_geocoding/v3/?ak=APPKEY&output=json&&coordtype=wgs84ll&location=31.225696563611,121.49884033194
142 | func (b baseURL) ReverseGeocodeURL(l geo.Location) string {
143 | return strings.Replace(string(b), "*", "reverse_geocoding", 1) + fmt.Sprintf("output=json&coordtype=%s&location=%f,%f", coordtype, l.Lat, l.Lng) + lang
144 | }
145 |
146 | func (r *geocodeResponse) Location() (*geo.Location, error) {
147 | var location = &geo.Location{}
148 | if r.Status == 1 {
149 | return nil, nil
150 | } else if r.Status != statusOK {
151 | return nil, fmt.Errorf("geocoding error: %v", r.Status)
152 | }
153 | location.Lat = r.Result.Location.Lat
154 | location.Lng = r.Result.Location.Lng
155 | return location, nil
156 |
157 | }
158 |
159 | func (r *geocodeResponse) Address() (*geo.Address, error) {
160 | if r.Status == 1 {
161 | return nil, nil
162 | } else if r.Status != statusOK {
163 | return nil, fmt.Errorf("reverse geocoding error: %v", r.Status)
164 | }
165 |
166 | addr := parseBaiduResult(r)
167 |
168 | return addr, nil
169 | }
170 |
171 | func parseBaiduResult(r *geocodeResponse) *geo.Address {
172 | addr := &geo.Address{}
173 | res := r.Result
174 | addr.FormattedAddress = res.FormattedAddress
175 | addr.HouseNumber = res.AddressComponent.StreetNumber
176 | addr.Street = res.AddressComponent.Street
177 | addr.Suburb = res.AddressComponent.District
178 | addr.City = res.AddressComponent.City
179 | addr.State = res.AddressComponent.Province
180 | addr.Country = res.AddressComponent.Country
181 | addr.CountryCode = res.AddressComponent.CountryCodeIso
182 |
183 | if (*addr == geo.Address{}) {
184 | return nil
185 | }
186 | return addr
187 | }
188 |
--------------------------------------------------------------------------------
/mapzen/geocoder_test.go:
--------------------------------------------------------------------------------
1 | package mapzen
2 |
3 | import (
4 | "math"
5 | "net/http"
6 | "net/http/httptest"
7 | "os"
8 | "testing"
9 |
10 | geo "github.com/codingsince1985/geo-golang"
11 | )
12 |
13 | var key = os.Getenv("MAPZEN_API_KEY")
14 |
15 | func TestGeocode(t *testing.T) {
16 | ts := testServer(geocodeResp)
17 | defer ts.Close()
18 |
19 | address := "1109 N Highland St, Arlington VA"
20 | geocoder := Geocoder(key, ts.URL)
21 | loc, err := geocoder.Geocode(address)
22 | if err != nil {
23 | t.Fatal(err)
24 | }
25 |
26 | if loc == nil {
27 | t.Fatalf("Address: %s - Not Found\n", address)
28 | }
29 | expected := geo.Location{Lng: -77.094733, Lat: 38.886665}
30 | if math.Abs(loc.Lng-expected.Lng) > eps {
31 | t.Fatalf("Got: %v\tExpected: %v\n", loc, expected)
32 | }
33 | if math.Abs(loc.Lat-expected.Lat) > eps {
34 | t.Fatalf("Got: %v\tExpected: %v\n", loc, expected)
35 | }
36 | }
37 |
38 | func TestReverseGeocode(t *testing.T) {
39 | ts := testServer(reverseResp)
40 | defer ts.Close()
41 |
42 | house := "1109"
43 | code := "USA"
44 | state := "Virginia"
45 | stateCode := "VA"
46 | lat := 38.886665
47 | lng := -77.094733
48 | geocoder := Geocoder(key, ts.URL)
49 | addr, err := geocoder.ReverseGeocode(lat, lng)
50 | if err != nil {
51 | t.Fatal(err)
52 | }
53 |
54 | if addr == nil {
55 | t.Fatalf("Location: lat:%f, lng:%f - Not Found\n", lat, lng)
56 | }
57 |
58 | if addr.HouseNumber != house {
59 | t.Fatalf("Got: %v\tExpected: %v\n", addr.HouseNumber, house)
60 | }
61 | if addr.CountryCode != code {
62 | t.Fatalf("Got: %v\tExpected: %v\n", addr.CountryCode, code)
63 | }
64 | if addr.State != state {
65 | t.Fatalf("Got: %v\tExpected: %v\n", addr.State, state)
66 | }
67 | if addr.StateCode != stateCode {
68 | t.Fatalf("Got: %v\tExpected: %v\n", addr.StateCode, stateCode)
69 | }
70 | }
71 |
72 | func testServer(response string) *httptest.Server {
73 | return httptest.NewServer(http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
74 | resp.Write([]byte(response))
75 | }))
76 | }
77 |
78 | const (
79 | eps = 1.0e-5
80 |
81 | geocodeResp = `
82 | {
83 | "geocoding": {
84 | "version": "0.2",
85 | "attribution": "https://search.mapzen.com/v1/attribution",
86 | "query": {
87 | "text": "1109 N Highland St, Arlington VA",
88 | "parsed_text": {
89 | "number": "1109",
90 | "street": "n highland st",
91 | "city": "arlington",
92 | "state": "va"
93 | },
94 | "size": 1,
95 | "private": false,
96 | "lang": {
97 | "name": "English",
98 | "iso6391": "en",
99 | "iso6393": "eng",
100 | "defaulted": true
101 | },
102 | "querySize": 20
103 | },
104 | "engine": {
105 | "name": "Pelias",
106 | "author": "Mapzen",
107 | "version": "1.0"
108 | },
109 | "timestamp": 1499034434562
110 | },
111 | "type": "FeatureCollection",
112 | "features": [
113 | {
114 | "type": "Feature",
115 | "geometry": {
116 | "type": "Point",
117 | "coordinates": [
118 | -77.094733,
119 | 38.886665
120 | ]
121 | },
122 | "properties": {
123 | "id": "us/va/statewide:7120c09d09e25349",
124 | "gid": "openaddresses:address:us/va/statewide:7120c09d09e25349",
125 | "layer": "address",
126 | "source": "openaddresses",
127 | "source_id": "us/va/statewide:7120c09d09e25349",
128 | "name": "1109 N Highland St",
129 | "housenumber": "1109",
130 | "street": "N Highland St",
131 | "postalcode": "22201",
132 | "confidence": 1,
133 | "match_type": "exact",
134 | "accuracy": "point",
135 | "country": "United States",
136 | "country_gid": "whosonfirst:country:85633793",
137 | "country_a": "USA",
138 | "region": "Virginia",
139 | "region_gid": "whosonfirst:region:85688747",
140 | "region_a": "VA",
141 | "county": "Arlington County",
142 | "county_gid": "whosonfirst:county:102085953",
143 | "locality": "Arlington",
144 | "locality_gid": "whosonfirst:locality:101729469",
145 | "neighbourhood": "Courthouse",
146 | "neighbourhood_gid": "whosonfirst:neighbourhood:85811147",
147 | "label": "1109 N Highland St, Arlington, VA, USA"
148 | }
149 | }
150 | ],
151 | "bbox": [
152 | -77.094733,
153 | 38.886665,
154 | -77.094733,
155 | 38.886665
156 | ]
157 | }
158 | `
159 |
160 | reverseResp = `
161 | {
162 | "geocoding": {
163 | "version": "0.2",
164 | "attribution": "https://search.mapzen.com/v1/attribution",
165 | "query": {
166 | "size": 1,
167 | "private": false,
168 | "point.lat": 38.886665,
169 | "point.lon": -77.094733,
170 | "boundary.circle.radius": 1,
171 | "boundary.circle.lat": 38.886665,
172 | "boundary.circle.lon": -77.094733,
173 | "lang": {
174 | "name": "English",
175 | "iso6391": "en",
176 | "iso6393": "eng",
177 | "defaulted": true
178 | },
179 | "querySize": 20
180 | },
181 | "engine": {
182 | "name": "Pelias",
183 | "author": "Mapzen",
184 | "version": "1.0"
185 | },
186 | "timestamp": 1499034728798
187 | },
188 | "type": "FeatureCollection",
189 | "features": [
190 | {
191 | "type": "Feature",
192 | "geometry": {
193 | "type": "Point",
194 | "coordinates": [
195 | -77.094733,
196 | 38.886665
197 | ]
198 | },
199 | "properties": {
200 | "id": "us/va/statewide:7120c09d09e25349",
201 | "gid": "openaddresses:address:us/va/statewide:7120c09d09e25349",
202 | "layer": "address",
203 | "source": "openaddresses",
204 | "source_id": "us/va/statewide:7120c09d09e25349",
205 | "name": "1109 N Highland St",
206 | "housenumber": "1109",
207 | "street": "N Highland St",
208 | "postalcode": "22201",
209 | "confidence": 1,
210 | "distance": 0,
211 | "accuracy": "point",
212 | "country": "United States",
213 | "country_gid": "whosonfirst:country:85633793",
214 | "country_a": "USA",
215 | "region": "Virginia",
216 | "region_gid": "whosonfirst:region:85688747",
217 | "region_a": "VA",
218 | "county": "Arlington County",
219 | "county_gid": "whosonfirst:county:102085953",
220 | "locality": "Arlington",
221 | "locality_gid": "whosonfirst:locality:101729469",
222 | "neighbourhood": "Courthouse",
223 | "neighbourhood_gid": "whosonfirst:neighbourhood:85811147",
224 | "label": "1109 N Highland St, Arlington, VA, USA"
225 | }
226 | }
227 | ],
228 | "bbox": [
229 | -77.094733,
230 | 38.886665,
231 | -77.094733,
232 | 38.886665
233 | ]
234 | }`
235 | )
236 |
--------------------------------------------------------------------------------
/mapquest/open/geocoder_test.go:
--------------------------------------------------------------------------------
1 | package open_test
2 |
3 | import (
4 | "net/http"
5 | "net/http/httptest"
6 | "os"
7 | "strings"
8 | "testing"
9 |
10 | "github.com/codingsince1985/geo-golang"
11 | "github.com/codingsince1985/geo-golang/mapquest/open"
12 | "github.com/stretchr/testify/assert"
13 | )
14 |
15 | var key = os.Getenv("MAPQUEST_OPEN_KEY")
16 |
17 | func TestGeocode(t *testing.T) {
18 | ts := testServer(response1)
19 | defer ts.Close()
20 |
21 | geocoder := open.Geocoder(key, ts.URL+"/")
22 | location, err := geocoder.Geocode("60 Collins St, Melbourne VIC 3000")
23 | assert.NoError(t, err)
24 | assert.Equal(t, geo.Location{Lat: -37.813743, Lng: 144.971745}, *location)
25 | }
26 |
27 | func TestReverseGeocode(t *testing.T) {
28 | ts := testServer(response2)
29 | defer ts.Close()
30 |
31 | geocoder := open.Geocoder(key, ts.URL+"/")
32 | address, err := geocoder.ReverseGeocode(-37.813743, 144.971745)
33 | assert.NoError(t, err)
34 | assert.True(t, strings.HasPrefix(address.FormattedAddress, "Exhibition Street"))
35 | }
36 |
37 | func TestReverseGeocodeWithNoResult(t *testing.T) {
38 | ts := testServer(response3)
39 | defer ts.Close()
40 |
41 | geocoder := open.Geocoder(key, ts.URL+"/")
42 | //geocoder := open.Geocoder(key)
43 | addr, err := geocoder.ReverseGeocode(-37.813743, 164.971745)
44 | assert.Nil(t, err)
45 | assert.Nil(t, addr)
46 | }
47 |
48 | func testServer(response string) *httptest.Server {
49 | return httptest.NewServer(http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
50 | resp.Write([]byte(response))
51 | }))
52 | }
53 |
54 | const (
55 | response1 = `{
56 | "info":{
57 | "statuscode":0,
58 | "copyright":{
59 | "text":"\u00A9 2016 MapQuest, Inc.",
60 | "imageUrl":"http://api.mqcdn.com/res/mqlogo.gif",
61 | "imageAltText":"\u00A9 2016 MapQuest, Inc."
62 | },
63 | "messages":[
64 |
65 | ]
66 | },
67 | "options":{
68 | "maxResults":-1,
69 | "thumbMaps":true,
70 | "ignoreLatLngInput":false
71 | },
72 | "results":[
73 | {
74 | "providedLocation":{
75 | "location":"60 Collins St, Melbourne VIC 3000"
76 | },
77 | "locations":[
78 | {
79 | "street":"60 Collins Street",
80 | "adminArea6":"Melbourne",
81 | "adminArea6Type":"Neighborhood",
82 | "adminArea5":"Melbourne",
83 | "adminArea5Type":"City",
84 | "adminArea4":"City of Melbourne",
85 | "adminArea4Type":"County",
86 | "adminArea3":"Victoria",
87 | "adminArea3Type":"State",
88 | "adminArea1":"AU",
89 | "adminArea1Type":"Country",
90 | "postalCode":"3000",
91 | "geocodeQualityCode":"P1XAA",
92 | "geocodeQuality":"POINT",
93 | "dragPoint":false,
94 | "sideOfStreet":"N",
95 | "linkId":"0",
96 | "unknownInput":"",
97 | "type":"s",
98 | "latLng":{
99 | "lat":-37.813743,
100 | "lng":144.971745
101 | },
102 | "displayLatLng":{
103 | "lat":-37.813743,
104 | "lng":144.971745
105 | },
106 | "mapUrl":"http://open.mapquestapi.com/staticmap/v4/getmap?key=Fmjtd|luu8216t21,rn=o5-94255w&type=map&size=225,160&pois=purple-1,-37.8137433689794,144.971745104488,0,0,|¢er=-37.8137433689794,144.971745104488&zoom=15&rand=-2113669715"
107 | }
108 | ]
109 | }
110 | ]
111 | }`
112 | response2 = `{
113 | "info":{
114 | "statuscode":0,
115 | "copyright":{
116 | "text":"\u00A9 2016 MapQuest, Inc.",
117 | "imageUrl":"http://api.mqcdn.com/res/mqlogo.gif",
118 | "imageAltText":"\u00A9 2016 MapQuest, Inc."
119 | },
120 | "messages":[
121 |
122 | ]
123 | },
124 | "options":{
125 | "maxResults":1,
126 | "thumbMaps":true,
127 | "ignoreLatLngInput":false
128 | },
129 | "results":[
130 | {
131 | "providedLocation":{
132 | "latLng":{
133 | "lat":-37.813743,
134 | "lng":144.971745
135 | }
136 | },
137 | "locations":[
138 | {
139 | "street":"Exhibition Street",
140 | "adminArea6":"Melbourne",
141 | "adminArea6Type":"Neighborhood",
142 | "adminArea5":"Melbourne",
143 | "adminArea5Type":"City",
144 | "adminArea4":"City of Melbourne",
145 | "adminArea4Type":"County",
146 | "adminArea3":"Victoria",
147 | "adminArea3Type":"State",
148 | "adminArea1":"AU",
149 | "adminArea1Type":"Country",
150 | "postalCode":"3000",
151 | "geocodeQualityCode":"L1AAA",
152 | "geocodeQuality":"ADDRESS",
153 | "dragPoint":false,
154 | "sideOfStreet":"N",
155 | "linkId":"0",
156 | "unknownInput":"",
157 | "type":"s",
158 | "latLng":{
159 | "lat":-37.813621,
160 | "lng":144.97161
161 | },
162 | "displayLatLng":{
163 | "lat":-37.813621,
164 | "lng":144.97161
165 | },
166 | "mapUrl":"http://open.mapquestapi.com/staticmap/v4/getmap?key=Fmjtd|luu8216t21,rn=o5-94255w&type=map&size=225,160&pois=purple-1,-37.81362105,144.971609569165,0,0,|¢er=-37.81362105,144.971609569165&zoom=15&rand=2088657841"
167 | }
168 | ]
169 | }
170 | ]
171 | }`
172 | response3 = `{
173 | "info":{
174 | "statuscode":0,
175 | "copyright":{
176 | "text":"\u00A9 2016 MapQuest, Inc.",
177 | "imageUrl":"http://api.mqcdn.com/res/mqlogo.gif",
178 | "imageAltText":"\u00A9 2016 MapQuest, Inc."
179 | },
180 | "messages":[
181 |
182 | ]
183 | },
184 | "options":{
185 | "maxResults":1,
186 | "thumbMaps":true,
187 | "ignoreLatLngInput":false
188 | },
189 | "results":[
190 | {
191 | "providedLocation":{
192 | "latLng":{
193 | "lat":-37.813743,
194 | "lng":164.971745
195 | }
196 | },
197 | "locations":[
198 | {
199 | "street":"",
200 | "adminArea6":"",
201 | "adminArea6Type":"Neighborhood",
202 | "adminArea5":"",
203 | "adminArea5Type":"City",
204 | "adminArea4":"",
205 | "adminArea4Type":"County",
206 | "adminArea3":"",
207 | "adminArea3Type":"State",
208 | "adminArea1":"",
209 | "adminArea1Type":"Country",
210 | "postalCode":"",
211 | "geocodeQualityCode":"XXXXX",
212 | "geocodeQuality":"UNKNOWN",
213 | "dragPoint":false,
214 | "sideOfStreet":"N",
215 | "linkId":"0",
216 | "unknownInput":"",
217 | "type":"s",
218 | "latLng":{
219 | "lat":-37.813743,
220 | "lng":164.971745
221 | },
222 | "displayLatLng":{
223 | "lat":-37.813743,
224 | "lng":164.971745
225 | },
226 | "mapUrl":"http://open.mapquestapi.com/staticmap/v4/getmap?key=Fmjtd|luu8216t21,rn=o5-94255w&type=map&size=225,160&pois=purple-1,-37.813743,164.971745,0,0,|¢er=-37.813743,164.971745&zoom=15&rand=342866536"
227 | }
228 | ]
229 | }
230 | ]
231 | }`
232 | )
233 |
--------------------------------------------------------------------------------
/examples/geocoder_example.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "os"
6 |
7 | "github.com/codingsince1985/geo-golang"
8 | "github.com/codingsince1985/geo-golang/amap"
9 | "github.com/codingsince1985/geo-golang/arcgis"
10 | "github.com/codingsince1985/geo-golang/baidu"
11 | "github.com/codingsince1985/geo-golang/bing"
12 | "github.com/codingsince1985/geo-golang/chained"
13 | "github.com/codingsince1985/geo-golang/frenchapigouv"
14 | "github.com/codingsince1985/geo-golang/geocod"
15 | "github.com/codingsince1985/geo-golang/google"
16 | "github.com/codingsince1985/geo-golang/here"
17 | "github.com/codingsince1985/geo-golang/locationiq"
18 | "github.com/codingsince1985/geo-golang/mapbox"
19 | "github.com/codingsince1985/geo-golang/mapquest/nominatim"
20 | "github.com/codingsince1985/geo-golang/mapquest/open"
21 | "github.com/codingsince1985/geo-golang/mapzen"
22 | "github.com/codingsince1985/geo-golang/opencage"
23 | "github.com/codingsince1985/geo-golang/openstreetmap"
24 | "github.com/codingsince1985/geo-golang/pickpoint"
25 | "github.com/codingsince1985/geo-golang/tomtom"
26 | "github.com/codingsince1985/geo-golang/yandex"
27 | )
28 |
29 | const (
30 | addr = "Melbourne VIC"
31 | lat, lng = -37.813611, 144.963056
32 | radius = 50
33 | zoom = 18
34 | addrFR = "Champs de Mars Paris"
35 | addrAmap = "北京市海淀区清河街道西二旗西路领秀新硅谷" // China only
36 | latFR, lngFR = 48.854395, 2.304770
37 | latAmap, lngAmap = 40.05510, 116.309866
38 | )
39 |
40 | func main() {
41 | ExampleGeocoder()
42 | }
43 |
44 | // ExampleGeocoder demonstrates the different geocoding services
45 | func ExampleGeocoder() {
46 | fmt.Println("Google Geocoding API")
47 | try(google.Geocoder(os.Getenv("GOOGLE_API_KEY")))
48 |
49 | fmt.Println("Mapquest Nominatim")
50 | try(nominatim.Geocoder(os.Getenv("MAPQUEST_NOMINATIM_KEY")))
51 |
52 | fmt.Println("Mapquest Open streetmaps")
53 | try(open.Geocoder(os.Getenv("MAPQUEST_OPEN_KEY")))
54 |
55 | fmt.Println("OpenCage Data")
56 | try(opencage.Geocoder(os.Getenv("OPENCAGE_API_KEY")))
57 |
58 | fmt.Println("HERE Geocoder API")
59 | try(here.Geocoder(os.Getenv("HERE_APP_ID"), os.Getenv("HERE_APP_CODE"), radius))
60 |
61 | fmt.Println("Bing Geocoding API")
62 | try(bing.Geocoder(os.Getenv("BING_API_KEY")))
63 |
64 | fmt.Println("Baidu Geocoding API")
65 | try(baidu.Geocoder(os.Getenv("BAIDU_API_KEY"), "en", "bd09ll"))
66 |
67 | fmt.Println("Amap Geocoding API")
68 | tryOnlyForAmap(amap.Geocoder(os.Getenv("AMAP_API_KEY"), radius))
69 |
70 | fmt.Println("Mapbox API")
71 | try(mapbox.Geocoder(os.Getenv("MAPBOX_API_KEY")))
72 |
73 | fmt.Println("OpenStreetMap")
74 | try(openstreetmap.Geocoder())
75 |
76 | fmt.Println("PickPoint")
77 | try(pickpoint.Geocoder(os.Getenv("PICKPOINT_API_KEY")))
78 |
79 | fmt.Println("LocationIQ")
80 | try(locationiq.Geocoder(os.Getenv("LOCATIONIQ_API_KEY"), zoom))
81 |
82 | fmt.Println("ArcGIS")
83 | try(arcgis.Geocoder(os.Getenv("ARCGIS_TOKEN")))
84 |
85 | fmt.Println("geocod.io")
86 | try(geocod.Geocoder(os.Getenv("GEOCOD_API_KEY")))
87 |
88 | fmt.Println("Mapzen")
89 | try(mapzen.Geocoder(os.Getenv("MAPZEN_API_KEY")))
90 |
91 | fmt.Println("TomTom")
92 | try(tomtom.Geocoder(os.Getenv("TOMTOM_API_KEY")))
93 |
94 | fmt.Println("Yandex")
95 | try(yandex.Geocoder(os.Getenv("YANDEX_API_KEY")))
96 |
97 | // To use only with french locations or addresses,
98 | // or values could be estimated and will be false
99 | fmt.Println("FrenchAPIGouv")
100 | tryOnlyFRData(frenchapigouv.Geocoder())
101 |
102 | // Chained geocoder will fallback to subsequent geocoders
103 | fmt.Println("ChainedAPI[OpenStreetmap -> Google]")
104 | try(chained.Geocoder(
105 | openstreetmap.Geocoder(),
106 | google.Geocoder(os.Getenv("GOOGLE_API_KEY")),
107 | ))
108 | // Output: Google Geocoding API
109 | // Melbourne VIC location is (-37.813611, 144.963056)
110 | // Address of (-37.813611,144.963056) is 197 Elizabeth St, Melbourne VIC 3000, Australia
111 | // Detailed address: &geo.Address{FormattedAddress:"197 Elizabeth St, Melbourne VIC 3000, Australia",
112 | // Street:"Elizabeth Street", HouseNumber:"197", Suburb:"", Postcode:"3000", State:"Victoria",
113 | // StateDistrict:"Melbourne City", County:"", Country:"Australia", CountryCode:"AU", City:"Melbourne"}
114 | //
115 | // Mapquest Nominatim
116 | // Melbourne VIC location is (-37.814218, 144.963161)
117 | // Address of (-37.813611,144.963056) is Melbourne's GPO, Postal Lane, Melbourne, City of Melbourne, Greater Melbourne, Victoria, 3000, Australia
118 | // Detailed address: &geo.Address{FormattedAddress:"Melbourne's GPO, Postal Lane, Melbourne, City of Melbourne,
119 | // Greater Melbourne, Victoria, 3000, Australia", Street:"Postal Lane", HouseNumber:"", Suburb:"Melbourne",
120 | // Postcode:"3000", State:"Victoria", StateDistrict:"", County:"City of Melbourne", Country:"Australia", CountryCode:"AU", City:"Melbourne"}
121 | //
122 | // Mapquest Open streetmaps
123 | // Melbourne VIC location is (-37.814218, 144.963161)
124 | // Address of (-37.813611,144.963056) is Elizabeth Street, Melbourne, Victoria, AU
125 | // Detailed address: &geo.Address{FormattedAddress:"Elizabeth Street, 3000, Melbourne, Victoria, AU",
126 | // Street:"Elizabeth Street", HouseNumber:"", Suburb:"", Postcode:"3000", State:"Victoria", StateDistrict:"",
127 | // County:"", Country:"", CountryCode:"AU", City:"Melbourne"}
128 | //
129 | // OpenCage Data
130 | // Melbourne VIC location is (-37.814217, 144.963161)
131 | // Address of (-37.813611,144.963056) is Melbourne's GPO, Postal Lane, Melbourne VIC 3000, Australia
132 | // Detailed address: &geo.Address{FormattedAddress:"Melbourne's GPO, Postal Lane, Melbourne VIC 3000, Australia",
133 | // Street:"Postal Lane", HouseNumber:"", Suburb:"Melbourne (3000)", Postcode:"3000", State:"Victoria",
134 | // StateDistrict:"", County:"City of Melbourne", Country:"Australia", CountryCode:"AU", City:"Melbourne"}
135 | //
136 | // HERE Geocoder API
137 | // Melbourne VIC location is (-37.817530, 144.967150)
138 | // Address of (-37.813611,144.963056) is 197 Elizabeth St, Melbourne VIC 3000, Australia
139 | // Detailed address: &geo.Address{FormattedAddress:"197 Elizabeth St, Melbourne VIC 3000, Australia", Street:"Elizabeth St",
140 | // HouseNumber:"197", Suburb:"", Postcode:"3000", State:"Victoria", StateDistrict:"", County:"", Country:"Australia",
141 | // CountryCode:"AUS", City:"Melbourne"}
142 | //
143 | // Bing Geocoding API
144 | // Melbourne VIC location is (-37.824299, 144.977997)
145 | // Address of (-37.813611,144.963056) is Elizabeth St, Melbourne, VIC 3000
146 | // Detailed address: &geo.Address{FormattedAddress:"Elizabeth St, Melbourne, VIC 3000", Street:"Elizabeth St",
147 | // HouseNumber:"", Suburb:"", Postcode:"3000", State:"", StateDistrict:"", County:"", Country:"Australia", CountryCode:"", City:"Melbourne"}
148 | //
149 | // Baidu Geocoding API
150 | // Melbourne VIC location is (31.227015, 121.456967)
151 | // Address of (-37.813611,144.963056) is 341 Little Bourke Street, Melbourne, Victoria, Australia
152 | // Detailed address: &geo.Address{FormattedAddress:"341 Little Bourke Street, Melbourne, Victoria, Australia", Street:"Little Bourke Street", HouseNumber:"341", Suburb:"", Postcode:"", State:"Victoria", StateCode:"", StateDistrict:"", County:"", Country:"Australia", CountryCode:"AUS", City:"Melbourne"}
153 | //
154 | // Amap Geocoding API
155 | // 北京市海淀区清河街道西二旗西路领秀新硅谷 location is (40.05510, 116.309866)
156 | // Address of (40.05510, 116.309866) is 北京市海淀区清河街道领秀新硅谷领秀新硅谷B区
157 | // Detailed address: &geo.Address{FormattedAddress:"北京市海淀区清河街道领秀新硅谷领秀新硅谷B区", Street:"西二旗西路", HouseNumber:"2号楼", Suburb:"海 淀区", Postcode:"", State:"北京市", StateCode:"", StateDistrict:"", County:"", Country:"中国", CountryCode:"", City:""}
158 | //
159 | // Mapbox API
160 | // Melbourne VIC location is (-37.814200, 144.963200)
161 | // Address of (-37.813611,144.963056) is Elwood Park Playground, Melbourne, Victoria 3000, Australia
162 | // Detailed address: &geo.Address{FormattedAddress:"Elwood Park Playground, Melbourne, Victoria 3000, Australia",
163 | // Street:"Elwood Park Playground", HouseNumber:"", Suburb:"", Postcode:"3000", State:"Victoria", StateDistrict:"",
164 | // County:"", Country:"Australia", CountryCode:"AU", City:"Melbourne"}
165 | //
166 | // OpenStreetMap
167 | // Melbourne VIC location is (-37.814217, 144.963161)
168 | // Address of (-37.813611,144.963056) is Melbourne's GPO, Postal Lane, Chinatown, Melbourne, City of Melbourne, Greater Melbourne, Victoria, 3000, Australia
169 | // Detailed address: &geo.Address{FormattedAddress:"Melbourne's GPO, Postal Lane, Chinatown, Melbourne, City of Melbourne, Greater Melbourne,
170 | // Victoria, 3000, Australia", Street:"Postal Lane", HouseNumber:"", Suburb:"Melbourne", Postcode:"3000", State:"Victoria",
171 | // StateDistrict:"", County:"", Country:"Australia", CountryCode:"AU", City:"Melbourne"}
172 | //
173 | // PickPoint
174 | // Melbourne VIC location is (-37.814217, 144.963161)
175 | // Address of (-37.813611,144.963056) is Melbourne's GPO, Postal Lane, Chinatown, Melbourne, City of Melbourne, Greater Melbourne, Victoria, 3000, Australia
176 | // Detailed address: &geo.Address{FormattedAddress:"Melbourne's GPO, Postal Lane, Chinatown, Melbourne, City of Melbourne, Greater Melbourne,
177 | // Victoria, 3000, Australia", Street:"Postal Lane", HouseNumber:"", Suburb:"Melbourne", Postcode:"3000", State:"Victoria",
178 | // StateDistrict:"", County:"", Country:"Australia", CountryCode:"AU", City:"Melbourne"}
179 | //
180 | // LocationIQ
181 | // Melbourne VIC location is (-37.814217, 144.963161)
182 | // Address of (-37.813611,144.963056) is Melbourne's GPO, Postal Lane, Chinatown, Melbourne, City of Melbourne, Greater Melbourne, Victoria, 3000, Australia
183 | // Detailed address: &geo.Address{FormattedAddress:"Melbourne's GPO, Postal Lane, Chinatown, Melbourne, City of Melbourne, Greater Melbourne,
184 | // Victoria, 3000, Australia", Street:"Postal Lane", HouseNumber:"", Suburb:"Melbourne", Postcode:"3000", State:"Victoria",
185 | // StateDistrict:"", County:"", Country:"Australia", CountryCode:"AU", City:"Melbourne"}
186 | //
187 | // ArcGIS
188 | // Melbourne VIC location is (-37.817530, 144.967150)
189 | // Address of (-37.813611,144.963056) is Melbourne's Gpo
190 | // Detailed address: &geo.Address{FormattedAddress:"Melbourne's Gpo", Street:"350 Bourke Street Mall", HouseNumber:"350", Suburb:"", Postcode:"3000", State:"Victoria", StateDistrict:"", County:"", Country:"", CountryCode:"AUS", City:""}
191 | //
192 | // geocod.io
193 | // Melbourne VIC location is (28.079357, -80.623618)
194 | // got address
195 | //
196 | // Mapzen
197 | // Melbourne VIC location is (45.551136, 11.533929)
198 | // Address of (-37.813611,144.963056) is Stop 3: Bourke Street Mall, Bourke Street, Melbourne, Australia
199 | // Detailed address: &geo.Address{FormattedAddress:"Stop 3: Bourke Street Mall, Bourke Street, Melbourne, Australia", Street:"", HouseNumber:"", Suburb:"", Postcode:"", State:"Victoria", StateDistrict:"", County:"", Country:"Australia", CountryCode:"AUS", City:""}
200 | //
201 | // TomTom
202 | // Melbourne VIC location is (-37.815340, 144.963230)
203 | // Address of (-37.813611,144.963056) is Doyles Road, Elaine, West Central Victoria, Victoria, 3334
204 | // Detailed address: &geo.Address{FormattedAddress:"Doyles Road, Elaine, West Central Victoria, Victoria, 3334", Street:"Doyles Road", HouseNumber:"", Suburb:"", Postcode:"3334", State:"Victoria", StateDistrict:"", County:"", Country:"Australia", CountryCode:"AU", City:"Elaine"}
205 | //
206 | // Yandex
207 | // Melbourne VIC location is (41.926823, 2.254232)
208 | // Address of (-37.813611,144.963056) is Victoria, City of Melbourne, Elizabeth Street
209 | // Detailed address: &geo.Address{FormattedAddress:"Victoria, City of Melbourne, Elizabeth Street", Street:"Elizabeth Street",
210 | // HouseNumber:"", Suburb:"", Postcode:"", State:"Victoria", StateDistrict:"", County:"", Country:"Australia", CountryCode:"AU",
211 | // City:"City of Melbourne"}
212 | //
213 | // FrenchAPIGouv
214 | // Champs de Mars Paris location is (2.304770, 48.854395)
215 | // Address of (48.854395,2.304770) is 9001, Parc du Champs de Mars, 75007, Paris, Paris, Île-de-France, France
216 | // Detailed address: &geo.Address{FormattedAddress:"9001, Parc du Champs de Mars, 75007, Paris, Paris, Île-de-France, France",
217 | // Street:"Parc du Champs de Mars", HouseNumber:"9001", Suburb:"", Postcode:"75007", State:" Île-de-France", StateDistrict:"",
218 | // County:" Paris", Country:"France", CountryCode:"FRA", City:"Paris"}
219 | //
220 | // ChainedAPI[OpenStreetmap -> Google]
221 | // Melbourne VIC location is (-37.814217, 144.963161)
222 | // Address of (-37.813611,144.963056) is Melbourne's GPO, Postal Lane, Chinatown, Melbourne, City of Melbourne, Greater Melbourne, Victoria, 3000, Australia
223 | // Detailed address: &geo.Address{FormattedAddress:"Melbourne's GPO, Postal Lane, Chinatown, Melbourne, City of Melbourne, Greater Melbourne,
224 | // Victoria, 3000, Australia", Street:"Postal Lane", HouseNumber:"", Suburb:"Melbourne", Postcode:"3000", State:"Victoria",
225 | // StateDistrict:"", County:"", Country:"Australia", CountryCode:"AU", City:"Melbourne"}
226 | }
227 |
228 | func try(geocoder geo.Geocoder) {
229 | location, _ := geocoder.Geocode(addr)
230 | if location != nil {
231 | fmt.Printf("%s location is (%.6f, %.6f)\n", addr, location.Lat, location.Lng)
232 | } else {
233 | fmt.Println("got location")
234 | }
235 | address, _ := geocoder.ReverseGeocode(lat, lng)
236 | if address != nil {
237 | fmt.Printf("Address of (%.6f,%.6f) is %s\n", lat, lng, address.FormattedAddress)
238 | fmt.Printf("Detailed address: %#v\n", address)
239 | } else {
240 | fmt.Println("got address")
241 | }
242 | fmt.Print("\n")
243 | }
244 |
245 | func tryOnlyForAmap(geocoder geo.Geocoder) {
246 | location, _ := geocoder.Geocode(addrAmap)
247 | if location != nil {
248 | fmt.Printf("%s location is (%.6f, %.6f)\n", addr, location.Lat, location.Lng)
249 | } else {
250 | fmt.Println("got location")
251 | }
252 | address, _ := geocoder.ReverseGeocode(latAmap, lngAmap)
253 | if address != nil {
254 | fmt.Printf("Address of (%.6f,%.6f) is %s\n", lat, lng, address.FormattedAddress)
255 | fmt.Printf("Detailed address: %#v\n", address)
256 | } else {
257 | fmt.Println("got address")
258 | }
259 | fmt.Print("\n")
260 | }
261 |
262 | func tryOnlyFRData(geocoder geo.Geocoder) {
263 | location, _ := geocoder.Geocode(addrFR)
264 | if location != nil {
265 | fmt.Printf("%s location is (%.6f, %.6f)\n", addrFR, location.Lat, location.Lng)
266 | } else {
267 | fmt.Println("got location")
268 | }
269 | address, _ := geocoder.ReverseGeocode(latFR, lngFR)
270 | if address != nil {
271 | fmt.Printf("Address of (%.6f,%.6f) is %s\n", latFR, lngFR, address.FormattedAddress)
272 | fmt.Printf("Detailed address: %#v\n", address)
273 | } else {
274 | fmt.Println("got address")
275 | }
276 | fmt.Print("\n")
277 | }
278 |
--------------------------------------------------------------------------------
/bing/geocoder_test.go:
--------------------------------------------------------------------------------
1 | package bing_test
2 |
3 | import (
4 | "net/http"
5 | "net/http/httptest"
6 | "os"
7 | "strings"
8 | "testing"
9 |
10 | "github.com/codingsince1985/geo-golang"
11 | "github.com/codingsince1985/geo-golang/bing"
12 | "github.com/stretchr/testify/assert"
13 | )
14 |
15 | var key = os.Getenv("BING_API_KEY")
16 |
17 | func TestGeocode(t *testing.T) {
18 | ts := testServer(response1)
19 | defer ts.Close()
20 |
21 | geocoder := bing.Geocoder(key, ts.URL+"/")
22 | location, err := geocoder.Geocode("60 Collins St, Melbourne VIC")
23 |
24 | assert.NoError(t, err)
25 | assert.Equal(t, geo.Location{Lat: -37.81375, Lng: 144.97176}, *location)
26 | }
27 |
28 | func TestReverseGeocode(t *testing.T) {
29 | ts := testServer(response2)
30 | defer ts.Close()
31 |
32 | geocoder := bing.Geocoder(key, ts.URL+"/")
33 | address, err := geocoder.ReverseGeocode(-37.81375, 144.97176)
34 |
35 | assert.NoError(t, err)
36 | assert.True(t, strings.Index(address.FormattedAddress, "Collins St") > 0)
37 | }
38 |
39 | func TestReverseGeocodeWithNoResult(t *testing.T) {
40 | ts := testServer(response3)
41 | defer ts.Close()
42 |
43 | geocoder := bing.Geocoder(key, ts.URL+"/")
44 | addr, err := geocoder.ReverseGeocode(-37.81375, 164.97176)
45 |
46 | assert.NoError(t, err)
47 | assert.Nil(t, addr)
48 | }
49 |
50 | func testServer(response string) *httptest.Server {
51 | return httptest.NewServer(http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
52 | resp.Write([]byte(response))
53 | }))
54 | }
55 |
56 | const (
57 | response1 = `{
58 | "authenticationResultCode":"ValidCredentials",
59 | "brandLogoUri":"http:\/\/dev.virtualearth.net\/Branding\/logo_powered_by.png",
60 | "copyright":"Copyright © 2016 Microsoft and its suppliers. All rights reserved. This API cannot be accessed and the content and any results may not be used, reproduced or transmitted in any manner without express written permission from Microsoft Corporation.",
61 | "resourceSets":[
62 | {
63 | "estimatedTotal":4,
64 | "resources":[
65 | {
66 | "__type":"Location:http:\/\/schemas.microsoft.com\/search\/local\/ws\/rest\/v1",
67 | "bbox":[
68 | -37.8148742,
69 | 144.970337,
70 | -37.8126258,
71 | 144.973183
72 | ],
73 | "name":"60 Collins St, Melbourne VIC 3000, Australia",
74 | "point":{
75 | "type":"Point",
76 | "coordinates":[
77 | -37.81375,
78 | 144.97176
79 | ]
80 | },
81 | "address":{
82 | "addressLine":"60 Collins St",
83 | "adminDistrict":"VIC",
84 | "countryRegion":"Australia",
85 | "formattedAddress":"60 Collins St, Melbourne VIC 3000, Australia",
86 | "locality":"Melbourne",
87 | "postalCode":"3000"
88 | },
89 | "confidence":"Medium",
90 | "entityType":"Address",
91 | "geocodePoints":[
92 | {
93 | "type":"Point",
94 | "coordinates":[
95 | -37.81375,
96 | 144.97176
97 | ],
98 | "calculationMethod":"Rooftop",
99 | "usageTypes":[
100 | "Display"
101 | ]
102 | },
103 | {
104 | "type":"Point",
105 | "coordinates":[
106 | -37.81393,
107 | 144.97185
108 | ],
109 | "calculationMethod":"Rooftop",
110 | "usageTypes":[
111 | "Route"
112 | ]
113 | }
114 | ],
115 | "matchCodes":[
116 | "Ambiguous",
117 | "Good"
118 | ]
119 | },
120 | {
121 | "__type":"Location:http:\/\/schemas.microsoft.com\/search\/local\/ws\/rest\/v1",
122 | "bbox":[
123 | -37.7563242,
124 | 145.0049782,
125 | -37.7540758,
126 | 145.0078218
127 | ],
128 | "name":"60 Collins St, Thornbury VIC 3071, Australia",
129 | "point":{
130 | "type":"Point",
131 | "coordinates":[
132 | -37.7552,
133 | 145.0064
134 | ]
135 | },
136 | "address":{
137 | "addressLine":"60 Collins St",
138 | "adminDistrict":"VIC",
139 | "countryRegion":"Australia",
140 | "formattedAddress":"60 Collins St, Thornbury VIC 3071, Australia",
141 | "locality":"Thornbury",
142 | "postalCode":"3071"
143 | },
144 | "confidence":"Medium",
145 | "entityType":"Address",
146 | "geocodePoints":[
147 | {
148 | "type":"Point",
149 | "coordinates":[
150 | -37.7552,
151 | 145.0064
152 | ],
153 | "calculationMethod":"Rooftop",
154 | "usageTypes":[
155 | "Display"
156 | ]
157 | },
158 | {
159 | "type":"Point",
160 | "coordinates":[
161 | -37.75505,
162 | 145.00642
163 | ],
164 | "calculationMethod":"Rooftop",
165 | "usageTypes":[
166 | "Route"
167 | ]
168 | }
169 | ],
170 | "matchCodes":[
171 | "Ambiguous",
172 | "Good"
173 | ]
174 | },
175 | {
176 | "__type":"Location:http:\/\/schemas.microsoft.com\/search\/local\/ws\/rest\/v1",
177 | "bbox":[
178 | -37.9809042,
179 | 145.0587838,
180 | -37.9786558,
181 | 145.0616362
182 | ],
183 | "name":"60 Collins St, Mentone VIC 3194, Australia",
184 | "point":{
185 | "type":"Point",
186 | "coordinates":[
187 | -37.97978,
188 | 145.06021
189 | ]
190 | },
191 | "address":{
192 | "addressLine":"60 Collins St",
193 | "adminDistrict":"VIC",
194 | "countryRegion":"Australia",
195 | "formattedAddress":"60 Collins St, Mentone VIC 3194, Australia",
196 | "locality":"Mentone",
197 | "postalCode":"3194"
198 | },
199 | "confidence":"Medium",
200 | "entityType":"Address",
201 | "geocodePoints":[
202 | {
203 | "type":"Point",
204 | "coordinates":[
205 | -37.97978,
206 | 145.06021
207 | ],
208 | "calculationMethod":"Rooftop",
209 | "usageTypes":[
210 | "Display"
211 | ]
212 | },
213 | {
214 | "type":"Point",
215 | "coordinates":[
216 | -37.97961,
217 | 145.06024
218 | ],
219 | "calculationMethod":"Rooftop",
220 | "usageTypes":[
221 | "Route"
222 | ]
223 | }
224 | ],
225 | "matchCodes":[
226 | "Ambiguous",
227 | "Good"
228 | ]
229 | },
230 | {
231 | "__type":"Location:http:\/\/schemas.microsoft.com\/search\/local\/ws\/rest\/v1",
232 | "bbox":[
233 | -37.7392142,
234 | 144.8016785,
235 | -37.7369658,
236 | 144.8045215
237 | ],
238 | "name":"60 Collins St, St Albans VIC 3021, Australia",
239 | "point":{
240 | "type":"Point",
241 | "coordinates":[
242 | -37.73809,
243 | 144.8031
244 | ]
245 | },
246 | "address":{
247 | "addressLine":"60 Collins St",
248 | "adminDistrict":"VIC",
249 | "countryRegion":"Australia",
250 | "formattedAddress":"60 Collins St, St Albans VIC 3021, Australia",
251 | "locality":"St Albans",
252 | "postalCode":"3021"
253 | },
254 | "confidence":"Medium",
255 | "entityType":"Address",
256 | "geocodePoints":[
257 | {
258 | "type":"Point",
259 | "coordinates":[
260 | -37.73809,
261 | 144.8031
262 | ],
263 | "calculationMethod":"Rooftop",
264 | "usageTypes":[
265 | "Display"
266 | ]
267 | },
268 | {
269 | "type":"Point",
270 | "coordinates":[
271 | -37.73807,
272 | 144.80292
273 | ],
274 | "calculationMethod":"Rooftop",
275 | "usageTypes":[
276 | "Route"
277 | ]
278 | }
279 | ],
280 | "matchCodes":[
281 | "Ambiguous",
282 | "Good"
283 | ]
284 | }
285 | ]
286 | }
287 | ],
288 | "statusCode":200,
289 | "statusDescription":"OK",
290 | "traceId":"f0ff69bb189c4fdba96a05a0ad86658c|HK20271556|02.00.164.2600|HK2SCH010280621, HK2SCH010281221, HK2SCH010281326, HK2SCH010260633, HK2SCH010310422, i-0186bca5.ap-southeast-1b"
291 | }`
292 | response2 = `{
293 | "authenticationResultCode":"ValidCredentials",
294 | "brandLogoUri":"http:\/\/dev.virtualearth.net\/Branding\/logo_powered_by.png",
295 | "copyright":"Copyright © 2016 Microsoft and its suppliers. All rights reserved. This API cannot be accessed and the content and any results may not be used, reproduced or transmitted in any manner without express written permission from Microsoft Corporation.",
296 | "resourceSets":[
297 | {
298 | "estimatedTotal":2,
299 | "resources":[
300 | {
301 | "__type":"Location:http:\/\/schemas.microsoft.com\/search\/local\/ws\/rest\/v1",
302 | "bbox":[
303 | -37.817612717570675,
304 | 144.96524105171127,
305 | -37.809887282429322,
306 | 144.9782789482887
307 | ],
308 | "name":"58 Collins St, Melbourne, VIC 3000",
309 | "point":{
310 | "type":"Point",
311 | "coordinates":[
312 | -37.81375,
313 | 144.97176
314 | ]
315 | },
316 | "address":{
317 | "addressLine":"58 Collins St",
318 | "adminDistrict":"VIC",
319 | "countryRegion":"Australia",
320 | "formattedAddress":"58 Collins St, Melbourne, VIC 3000",
321 | "locality":"Melbourne",
322 | "postalCode":"3000"
323 | },
324 | "confidence":"Medium",
325 | "entityType":"Address",
326 | "geocodePoints":[
327 | {
328 | "type":"Point",
329 | "coordinates":[
330 | -37.81375,
331 | 144.97176
332 | ],
333 | "calculationMethod":"Interpolation",
334 | "usageTypes":[
335 | "Display",
336 | "Route"
337 | ]
338 | }
339 | ],
340 | "matchCodes":[
341 | "Ambiguous",
342 | "Good"
343 | ]
344 | },
345 | {
346 | "__type":"Location:http:\/\/schemas.microsoft.com\/search\/local\/ws\/rest\/v1",
347 | "bbox":[
348 | -37.817612717570675,
349 | 144.96524105171127,
350 | -37.809887282429322,
351 | 144.9782789482887
352 | ],
353 | "name":"Collins St, Melbourne, VIC 3000",
354 | "point":{
355 | "type":"Point",
356 | "coordinates":[
357 | -37.81375,
358 | 144.97176
359 | ]
360 | },
361 | "address":{
362 | "addressLine":"Collins St",
363 | "adminDistrict":"VIC",
364 | "countryRegion":"Australia",
365 | "formattedAddress":"Collins St, Melbourne, VIC 3000",
366 | "locality":"Melbourne",
367 | "postalCode":"3000"
368 | },
369 | "confidence":"Medium",
370 | "entityType":"Address",
371 | "geocodePoints":[
372 | {
373 | "type":"Point",
374 | "coordinates":[
375 | -37.81375,
376 | 144.97176
377 | ],
378 | "calculationMethod":"Interpolation",
379 | "usageTypes":[
380 | "Display",
381 | "Route"
382 | ]
383 | }
384 | ],
385 | "matchCodes":[
386 | "Ambiguous",
387 | "Good"
388 | ]
389 | }
390 | ]
391 | }
392 | ],
393 | "statusCode":200,
394 | "statusDescription":"OK",
395 | "traceId":"2c056005fa6643a6ac6bb8a9bb2a3a5c|HK20271556|02.00.164.2600|HK2SCH010280621, HK2SCH010281619"
396 | }`
397 | response3 = `{
398 | "authenticationResultCode":"ValidCredentials",
399 | "brandLogoUri":"http:\/\/dev.virtualearth.net\/Branding\/logo_powered_by.png",
400 | "copyright":"Copyright © 2016 Microsoft and its suppliers. All rights reserved. This API cannot be accessed and the content and any results may not be used, reproduced or transmitted in any manner without express written permission from Microsoft Corporation.",
401 | "resourceSets":[
402 | {
403 | "estimatedTotal":0,
404 | "resources":[
405 |
406 | ]
407 | }
408 | ],
409 | "statusCode":200,
410 | "statusDescription":"OK",
411 | "traceId":"101c3af9b0984cd0937b9e4dde2910a6|HK20271556|02.00.164.2600|HK2SCH010280621, HK2SCH010281619"
412 | }`
413 | )
414 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | GeoService in Go
2 | ==
3 | [](https://pkg.go.dev/github.com/codingsince1985/geo-golang)
4 | [](https://travis-ci.org/codingsince1985/geo-golang)
5 | [](https://codecov.io/gh/codingsince1985/geo-golang)
6 | [](https://goreportcard.com/report/codingsince1985/geo-golang)
7 |
8 | ## Code Coverage
9 | [](https://codecov.io/gh/codingsince1985/geo-golang)
10 |
11 | A geocoding service developed in Go's way, idiomatic and elegant, not just in golang.
12 |
13 | This product is designed to open to any Geocoding service. Based on it,
14 | + [Google Maps](https://developers.google.com/maps/documentation/geocoding/)
15 | + MapQuest
16 | - [Nominatim Search](http://open.mapquestapi.com/nominatim/)
17 | - [Open Geocoding](http://open.mapquestapi.com/geocoding/)
18 | + [OpenCage](https://opencagedata.com/api)
19 | + HERE
20 | - [Geocoder API](https://developer.here.com/documentation/geocoder/dev_guide/topics/quick-start-geocode.html)
21 | - [Geocoding and Search API](https://developer.here.com/documentation/geocoding-search-api/dev_guide/index.html)
22 | + [Bing](https://msdn.microsoft.com/en-us/library/ff701715.aspx)
23 | + [Mapbox](https://www.mapbox.com/developers/api/geocoding/)
24 | + [OpenStreetMap](https://wiki.openstreetmap.org/wiki/Nominatim)
25 | + [PickPoint](https://pickpoint.io)
26 | + [LocationIQ](http://locationiq.org/)
27 | + [ArcGIS](https://www.arcgis.com/features/index.html)
28 | + [geocodio](https://geocod.io)
29 | + [Mapzen](https://mapzen.com)
30 | + [TomTom](https://www.tomtom.com)
31 | + [Yandex.Maps](https://tech.yandex.com/maps/doc/geocoder/desc/concepts/About-docpage/)
32 | + [French API Gouv](https://adresse.data.gouv.fr/api)
33 | + [Baidu Map](https://lbsyun.baidu.com/index.php?title=webapi/guide/webservice-geocoding)
34 |
35 | clients are implemented in ~50 LoC each.
36 |
37 | It allows you to switch from one service to another by changing only 1 line, or enjoy all the free quota (requests/sec, day, month...) from them at the same time. Just like this.
38 |
39 | ```go
40 | package main
41 |
42 | import (
43 | "fmt"
44 | "os"
45 |
46 | "github.com/codingsince1985/geo-golang"
47 | "github.com/codingsince1985/geo-golang/arcgis"
48 | "github.com/codingsince1985/geo-golang/baidu"
49 | "github.com/codingsince1985/geo-golang/bing"
50 | "github.com/codingsince1985/geo-golang/chained"
51 | "github.com/codingsince1985/geo-golang/frenchapigouv"
52 | "github.com/codingsince1985/geo-golang/geocod"
53 | "github.com/codingsince1985/geo-golang/google"
54 | "github.com/codingsince1985/geo-golang/here"
55 | "github.com/codingsince1985/geo-golang/locationiq"
56 | "github.com/codingsince1985/geo-golang/mapbox"
57 | "github.com/codingsince1985/geo-golang/mapquest/nominatim"
58 | "github.com/codingsince1985/geo-golang/mapquest/open"
59 | "github.com/codingsince1985/geo-golang/mapzen"
60 | "github.com/codingsince1985/geo-golang/opencage"
61 | "github.com/codingsince1985/geo-golang/openstreetmap"
62 | "github.com/codingsince1985/geo-golang/pickpoint"
63 | "github.com/codingsince1985/geo-golang/tomtom"
64 | "github.com/codingsince1985/geo-golang/yandex"
65 | )
66 |
67 | const (
68 | addr = "Melbourne VIC"
69 | lat, lng = -37.813611, 144.963056
70 | radius = 50
71 | zoom = 18
72 | addrFR = "Champs de Mars Paris"
73 | latFR, lngFR = 48.854395, 2.304770
74 | )
75 |
76 | func main() {
77 | ExampleGeocoder()
78 | }
79 |
80 | // ExampleGeocoder demonstrates the different geocoding services
81 | func ExampleGeocoder() {
82 | fmt.Println("Google Geocoding API")
83 | try(google.Geocoder(os.Getenv("GOOGLE_API_KEY")))
84 |
85 | fmt.Println("Mapquest Nominatim")
86 | try(nominatim.Geocoder(os.Getenv("MAPQUEST_NOMINATIM_KEY")))
87 |
88 | fmt.Println("Mapquest Open streetmaps")
89 | try(open.Geocoder(os.Getenv("MAPQUEST_OPEN_KEY")))
90 |
91 | fmt.Println("OpenCage Data")
92 | try(opencage.Geocoder(os.Getenv("OPENCAGE_API_KEY")))
93 |
94 | fmt.Println("HERE API")
95 | try(here.Geocoder(os.Getenv("HERE_APP_ID"), os.Getenv("HERE_APP_CODE"), radius))
96 |
97 | fmt.Println("Bing Geocoding API")
98 | try(bing.Geocoder(os.Getenv("BING_API_KEY")))
99 |
100 | fmt.Println("Baidu Geocoding API")
101 | try(baidu.Geocoder(os.Getenv("BAIDU_API_KEY")))
102 |
103 | fmt.Println("Amap Geocoding API")
104 | try(amap.Geocoder(os.Getenv("BAIDU_API_KEY")))
105 |
106 | fmt.Println("Mapbox API")
107 | try(mapbox.Geocoder(os.Getenv("MAPBOX_API_KEY")))
108 |
109 | fmt.Println("OpenStreetMap")
110 | try(openstreetmap.Geocoder())
111 |
112 | fmt.Println("PickPoint")
113 | try(pickpoint.Geocoder(os.Getenv("PICKPOINT_API_KEY")))
114 |
115 | fmt.Println("LocationIQ")
116 | try(locationiq.Geocoder(os.Getenv("LOCATIONIQ_API_KEY"), zoom))
117 |
118 | fmt.Println("ArcGIS")
119 | try(arcgis.Geocoder(os.Getenv("ARCGIS_TOKEN")))
120 |
121 | fmt.Println("geocod.io")
122 | try(geocod.Geocoder(os.Getenv("GEOCOD_API_KEY")))
123 |
124 | fmt.Println("Mapzen")
125 | try(mapzen.Geocoder(os.Getenv("MAPZEN_API_KEY")))
126 |
127 | fmt.Println("TomTom")
128 | try(tomtom.Geocoder(os.Getenv("TOMTOM_API_KEY")))
129 |
130 | fmt.Println("Yandex")
131 | try(yandex.Geocoder(os.Getenv("YANDEX_API_KEY")))
132 |
133 | // To use only with french locations or addresses,
134 | // or values could be estimated and will be false
135 | fmt.Println("FrenchAPIGouv")
136 | tryOnlyFRData(frenchapigouv.Geocoder())
137 |
138 | // Chained geocoder will fallback to subsequent geocoders
139 | fmt.Println("ChainedAPI[OpenStreetmap -> Google]")
140 | try(chained.Geocoder(
141 | openstreetmap.Geocoder(),
142 | google.Geocoder(os.Getenv("GOOGLE_API_KEY")),
143 | ))
144 | }
145 |
146 | func try(geocoder geo.Geocoder) {
147 | location, _ := geocoder.Geocode(addr)
148 | if location != nil {
149 | fmt.Printf("%s location is (%.6f, %.6f)\n", addr, location.Lat, location.Lng)
150 | } else {
151 | fmt.Println("got location")
152 | }
153 | address, _ := geocoder.ReverseGeocode(lat, lng)
154 | if address != nil {
155 | fmt.Printf("Address of (%.6f,%.6f) is %s\n", lat, lng, address.FormattedAddress)
156 | fmt.Printf("Detailed address: %#v\n", address)
157 | } else {
158 | fmt.Println("got address")
159 | }
160 | fmt.Print("\n")
161 | }
162 |
163 | func tryOnlyFRData(geocoder geo.Geocoder) {
164 | location, _ := geocoder.Geocode(addrFR)
165 | if location != nil {
166 | fmt.Printf("%s location is (%.6f, %.6f)\n", addrFR, location.Lat, location.Lng)
167 | } else {
168 | fmt.Println("got location")
169 | }
170 | address, _ := geocoder.ReverseGeocode(latFR, lngFR)
171 | if address != nil {
172 | fmt.Printf("Address of (%.6f,%.6f) is %s\n", latFR, lngFR, address.FormattedAddress)
173 | fmt.Printf("Detailed address: %#v\n", address)
174 | } else {
175 | fmt.Println("got address")
176 | }
177 | fmt.Print("\n")
178 | }
179 |
180 | ```
181 | ### Result
182 | ```
183 | Google Geocoding API
184 | Melbourne VIC location is (-37.813611, 144.963056)
185 | Address of (-37.813611,144.963056) is 197 Elizabeth St, Melbourne VIC 3000, Australia
186 | Detailed address: &geo.Address{FormattedAddress:"197 Elizabeth St, Melbourne VIC 3000, Australia", Street:"Elizabeth Street", HouseNumber:"197", Suburb:"", Postcode:"3000", State:"Victoria", StateDistrict:"Melbourne City", County:"", Country:"Australia", CountryCode:"AU", City:"Melbourne"}
187 |
188 | Mapquest Nominatim
189 | Melbourne VIC location is (-37.814218, 144.963161)
190 | Address of (-37.813611,144.963056) is Melbourne's GPO, Postal Lane, Melbourne, City of Melbourne, Greater Melbourne, Victoria, 3000, Australia
191 | Detailed address: &geo.Address{FormattedAddress:"Melbourne's GPO, Postal Lane, Melbourne, City of Melbourne, Greater Melbourne, Victoria, 3000, Australia", Street:"Postal Lane", HouseNumber:"", Suburb:"Melbourne", Postcode:"3000", State:"Victoria", StateDistrict:"", County:"City of Melbourne", Country:"Australia", CountryCode:"AU", City:"Melbourne"}
192 |
193 | Mapquest Open streetmaps
194 | Melbourne VIC location is (-37.814218, 144.963161)
195 | Address of (-37.813611,144.963056) is Elizabeth Street, 3000, Melbourne, Victoria, AU
196 | Detailed address: &geo.Address{FormattedAddress:"Elizabeth Street, 3000, Melbourne, Victoria, AU", Street:"Elizabeth Street", HouseNumber:"", Suburb:"", Postcode:"3000", State:"Victoria", StateDistrict:"", County:"", Country:"", CountryCode:"AU", City:"Melbourne"}
197 |
198 | OpenCage Data
199 | Melbourne VIC location is (-37.814217, 144.963161)
200 | Address of (-37.813611,144.963056) is Melbourne's GPO, Postal Lane, Melbourne VIC 3000, Australia
201 | Detailed address: &geo.Address{FormattedAddress:"Melbourne's GPO, Postal Lane, Melbourne VIC 3000, Australia", Street:"Postal Lane", HouseNumber:"", Suburb:"Melbourne (3000)", Postcode:"3000", State:"Victoria", StateDistrict:"", County:"City of Melbourne", Country:"Australia", CountryCode:"AU", City:"Melbourne"}
202 |
203 | HERE API
204 | Melbourne VIC location is (-37.817530, 144.967150)
205 | Address of (-37.813611,144.963056) is 197 Elizabeth St, Melbourne VIC 3000, Australia
206 | Detailed address: &geo.Address{FormattedAddress:"197 Elizabeth St, Melbourne VIC 3000, Australia", Street:"Elizabeth St", HouseNumber:"197", Suburb:"", Postcode:"3000", State:"Victoria", StateDistrict:"", County:"", Country:"Australia", CountryCode:"AUS", City:"Melbourne"}
207 |
208 | Bing Geocoding API
209 | Melbourne VIC location is (-37.824299, 144.977997)
210 | Address of (-37.813611,144.963056) is Elizabeth St, Melbourne, VIC 3000
211 | Detailed address: &geo.Address{FormattedAddress:"Elizabeth St, Melbourne, VIC 3000", Street:"Elizabeth St", HouseNumber:"", Suburb:"", Postcode:"3000", State:"", StateDistrict:"", County:"", Country:"Australia", CountryCode:"", City:"Melbourne"}
212 |
213 | Baidu Geocoding API
214 | Melbourne VIC location is (31.227015, 121.456967)
215 | Address of (-37.813611,144.963056) is 341 Little Bourke Street, Melbourne, Victoria, Australia
216 | Detailed address: &geo.Address{FormattedAddress:"341 Little Bourke Street, Melbourne, Victoria, Australia", Street:"Little Bourke Street", HouseNumber:"341", Suburb:"", Postcode:"", State:"Victoria", StateCode:"", StateDistrict:"", County:"", Country:"Australia", CountryCode:"AUS", City:"Melbourne"}
217 |
218 | Mapbox API
219 | Melbourne VIC location is (-37.814200, 144.963200)
220 | Address of (-37.813611,144.963056) is Elwood Park Playground, Melbourne, Victoria 3000, Australia
221 | Detailed address: &geo.Address{FormattedAddress:"Elwood Park Playground, Melbourne, Victoria 3000, Australia", Street:"Elwood Park Playground", HouseNumber:"", Suburb:"", Postcode:"3000", State:"Victoria", StateDistrict:"", County:"", Country:"Australia", CountryCode:"AU", City:"Melbourne"}
222 |
223 | OpenStreetMap
224 | Melbourne VIC location is (-37.814217, 144.963161)
225 | Address of (-37.813611,144.963056) is Melbourne's GPO, Postal Lane, Chinatown, Melbourne, City of Melbourne, Greater Melbourne, Victoria, 3000, Australia
226 | Detailed address: &geo.Address{FormattedAddress:"Melbourne's GPO, Postal Lane, Chinatown, Melbourne, City of Melbourne, Greater Melbourne, Victoria, 3000, Australia", Street:"Postal Lane", HouseNumber:"", Suburb:"Melbourne", Postcode:"3000", State:"Victoria", StateDistrict:"", County:"", Country:"Australia", CountryCode:"AU", City:"Melbourne"}
227 |
228 | PickPoint
229 | Melbourne VIC location is (-37.814217, 144.963161)
230 | Address of (-37.813611,144.963056) is Melbourne's GPO, Postal Lane, Chinatown, Melbourne, City of Melbourne, Greater Melbourne, Victoria, 3000, Australia
231 | Detailed address: &geo.Address{FormattedAddress:"Melbourne's GPO, Postal Lane, Chinatown, Melbourne, City of Melbourne, Greater Melbourne, Victoria, 3000, Australia", Street:"Postal Lane", HouseNumber:"", Suburb:"Melbourne", Postcode:"3000", State:"Victoria", StateDistrict:"", County:"", Country:"Australia", CountryCode:"AU", City:"Melbourne"}
232 |
233 | LocationIQ
234 | Melbourne VIC location is (-37.814217, 144.963161)
235 | Address of (-37.813611,144.963056) is Melbourne's GPO, Postal Lane, Chinatown, Melbourne, City of Melbourne, Greater Melbourne, Victoria, 3000, Australia
236 | Detailed address: &geo.Address{FormattedAddress:"Melbourne's GPO, Postal Lane, Chinatown, Melbourne, City of Melbourne, Greater Melbourne, Victoria, 3000, Australia", Street:"Postal Lane", HouseNumber:"", Suburb:"Melbourne", Postcode:"3000", State:"Victoria", StateDistrict:"", County:"", Country:"Australia", CountryCode:"AU", City:"Melbourne"}
237 |
238 | ArcGIS
239 | Melbourne VIC location is (-37.817530, 144.967150)
240 | Address of (-37.813611,144.963056) is Melbourne's Gpo
241 | Detailed address: &geo.Address{FormattedAddress:"Melbourne's Gpo", Street:"350 Bourke Street Mall", HouseNumber:"350", Suburb:"", Postcode:"3000", State:"Victoria", StateDistrict:"", County:"", Country:"", CountryCode:"AUS", City:""}
242 |
243 | geocod.io
244 | Melbourne VIC location is (28.079357, -80.623618)
245 | got address
246 |
247 | Mapzen
248 | Melbourne VIC location is (45.551136, 11.533929)
249 | Address of (-37.813611,144.963056) is Stop 3: Bourke Street Mall, Bourke Street, Melbourne, Australia
250 | Detailed address: &geo.Address{FormattedAddress:"Stop 3: Bourke Street Mall, Bourke Street, Melbourne, Australia", Street:"", HouseNumber:"", Suburb:"", Postcode:"", State:"Victoria", StateDistrict:"", County:"", Country:"Australia", CountryCode:"AUS", City:""}
251 |
252 | TomTom
253 | Melbourne VIC location is (-37.815340, 144.963230)
254 | Address of (-37.813611,144.963056) is Doyles Road, Elaine, West Central Victoria, Victoria, 3334
255 | Detailed address: &geo.Address{FormattedAddress:"Doyles Road, Elaine, West Central Victoria, Victoria, 3334", Street:"Doyles Road", HouseNumber:"", Suburb:"", Postcode:"3334", State:"Victoria", StateDistrict:"", County:"", Country:"Australia", CountryCode:"AU", City:"Elaine"}
256 |
257 | Yandex
258 | Melbourne VIC location is (41.926823, 2.254232)
259 | Address of (-37.813611,144.963056) is Victoria, City of Melbourne, Elizabeth Street
260 | Detailed address: &geo.Address{FormattedAddress:"Victoria, City of Melbourne, Elizabeth Street", Street:"Elizabeth Street", HouseNumber:"", Suburb:"", Postcode:"", State:"Victoria", StateDistrict:"", County:"", Country:"Australia", CountryCode:"AU", City:"City of Melbourne"}
261 |
262 | FrenchAPIGouv
263 | Champs de Mars Paris location is (2.304770, 48.854395)
264 | Address of (48.854395,2.304770) is 9001, Parc du Champs de Mars, 75007, Paris, Paris, Île-de-France, France
265 | Detailed address: &geo.Address{FormattedAddress:"9001, Parc du Champs de Mars, 75007, Paris, Paris, Île-de-France, France", Street:"Parc du Champs de Mars", HouseNumber:"9001", Suburb:"", Postcode:"75007", State:" Île-de-France", StateDistrict:"", County:" Paris", Country:"France", CountryCode:"FRA", City:"Paris"}
266 |
267 | ChainedAPI[OpenStreetmap -> Google]
268 | Melbourne VIC location is (-37.814217, 144.963161)
269 | Address of (-37.813611,144.963056) is Melbourne's GPO, Postal Lane, Chinatown, Melbourne, City of Melbourne, Greater Melbourne, Victoria, 3000, Australia
270 | Detailed address: &geo.Address{FormattedAddress:"Melbourne's GPO, Postal Lane, Chinatown, Melbourne, City of Melbourne, Greater Melbourne, Victoria, 3000, Australia", Street:"Postal Lane", HouseNumber:"", Suburb:"Melbourne", Postcode:"3000", State:"Victoria", StateDistrict:"", County:"", Country:"Australia", CountryCode:"AU", City:"Melbourne"}
271 | ```
272 | License
273 | ==
274 | geo-golang is distributed under the terms of the MIT license. See LICENSE for details.
275 |
--------------------------------------------------------------------------------
/google/geocoder_test.go:
--------------------------------------------------------------------------------
1 | package google_test
2 |
3 | import (
4 | "net/http"
5 | "net/http/httptest"
6 | "os"
7 | "strings"
8 | "testing"
9 |
10 | "github.com/codingsince1985/geo-golang"
11 | "github.com/codingsince1985/geo-golang/google"
12 | "github.com/stretchr/testify/assert"
13 | )
14 |
15 | var token = os.Getenv("GOOGLE_API_KEY")
16 |
17 | func TestGeocode(t *testing.T) {
18 | ts := testServer(response1)
19 | defer ts.Close()
20 |
21 | geocoder := google.Geocoder(token, ts.URL+"/")
22 | location, err := geocoder.Geocode("60 Collins St, Melbourne VIC 3000")
23 | assert.NoError(t, err)
24 | assert.Equal(t, geo.Location{Lat: -37.8137683, Lng: 144.9718448}, *location)
25 | }
26 |
27 | func TestReverseGeocode(t *testing.T) {
28 | ts := testServer(response2)
29 | defer ts.Close()
30 |
31 | geocoder := google.Geocoder(token, ts.URL+"/")
32 | address, err := geocoder.ReverseGeocode(-37.8137683, 144.9718448)
33 | assert.NoError(t, err)
34 | assert.True(t, strings.HasPrefix(address.FormattedAddress, "60 Collins St"))
35 | assert.True(t, strings.HasPrefix(address.Street, "Collins St"))
36 | assert.Equal(t, address.StateCode, "VIC")
37 | }
38 |
39 | func TestReverseGeocodeWithNoResult(t *testing.T) {
40 | ts := testServer(response3)
41 | defer ts.Close()
42 |
43 | geocoder := google.Geocoder(token, ts.URL+"/")
44 | addr, err := geocoder.ReverseGeocode(-37.8137683, 164.9718448)
45 | assert.Nil(t, err)
46 | assert.Nil(t, addr)
47 | }
48 |
49 | func testServer(response string) *httptest.Server {
50 | return httptest.NewServer(http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
51 | resp.Write([]byte(response))
52 | }))
53 | }
54 |
55 | const (
56 | response1 = `{
57 | "results" : [
58 | {
59 | "address_components" : [
60 | {
61 | "long_name" : "60",
62 | "short_name" : "60",
63 | "types" : [ "street_number" ]
64 | },
65 | {
66 | "long_name" : "Collins Street",
67 | "short_name" : "Collins St",
68 | "types" : [ "route" ]
69 | },
70 | {
71 | "long_name" : "Melbourne",
72 | "short_name" : "Melbourne",
73 | "types" : [ "locality", "political" ]
74 | },
75 | {
76 | "long_name" : "Victoria",
77 | "short_name" : "VIC",
78 | "types" : [ "administrative_area_level_1", "political" ]
79 | },
80 | {
81 | "long_name" : "Australia",
82 | "short_name" : "AU",
83 | "types" : [ "country", "political" ]
84 | },
85 | {
86 | "long_name" : "3000",
87 | "short_name" : "3000",
88 | "types" : [ "postal_code" ]
89 | }
90 | ],
91 | "formatted_address" : "60 Collins St, Melbourne VIC 3000, Australia",
92 | "geometry" : {
93 | "location" : {
94 | "lat" : -37.8137683,
95 | "lng" : 144.9718448
96 | },
97 | "location_type" : "ROOFTOP",
98 | "viewport" : {
99 | "northeast" : {
100 | "lat" : -37.8124193197085,
101 | "lng" : 144.9731937802915
102 | },
103 | "southwest" : {
104 | "lat" : -37.8151172802915,
105 | "lng" : 144.9704958197085
106 | }
107 | }
108 | },
109 | "place_id" : "ChIJq9MCeshC1moRB2Z1U9DOwK0",
110 | "types" : [ "street_address" ]
111 | }
112 | ],
113 | "status" : "OK"
114 | }`
115 | response2 = `{
116 | "results" : [
117 | {
118 | "address_components" : [
119 | {
120 | "long_name" : "60",
121 | "short_name" : "60",
122 | "types" : [ "street_number" ]
123 | },
124 | {
125 | "long_name" : "Collins Street",
126 | "short_name" : "Collins St",
127 | "types" : [ "route" ]
128 | },
129 | {
130 | "long_name" : "Melbourne",
131 | "short_name" : "Melbourne",
132 | "types" : [ "locality", "political" ]
133 | },
134 | {
135 | "long_name" : "Victoria",
136 | "short_name" : "VIC",
137 | "types" : [ "administrative_area_level_1", "political" ]
138 | },
139 | {
140 | "long_name" : "Australia",
141 | "short_name" : "AU",
142 | "types" : [ "country", "political" ]
143 | },
144 | {
145 | "long_name" : "3000",
146 | "short_name" : "3000",
147 | "types" : [ "postal_code" ]
148 | }
149 | ],
150 | "formatted_address" : "60 Collins St, Melbourne VIC 3000, Australia",
151 | "geometry" : {
152 | "location" : {
153 | "lat" : -37.8137683,
154 | "lng" : 144.9718448
155 | },
156 | "location_type" : "ROOFTOP",
157 | "viewport" : {
158 | "northeast" : {
159 | "lat" : -37.8124193197085,
160 | "lng" : 144.9731937802915
161 | },
162 | "southwest" : {
163 | "lat" : -37.8151172802915,
164 | "lng" : 144.9704958197085
165 | }
166 | }
167 | },
168 | "place_id" : "ChIJq9MCeshC1moRB2Z1U9DOwK0",
169 | "types" : [ "street_address" ]
170 | },
171 | {
172 | "address_components" : [
173 | {
174 | "long_name" : "Melbourne",
175 | "short_name" : "Melbourne",
176 | "types" : [ "locality", "political" ]
177 | },
178 | {
179 | "long_name" : "Victoria",
180 | "short_name" : "VIC",
181 | "types" : [ "administrative_area_level_1", "political" ]
182 | },
183 | {
184 | "long_name" : "Australia",
185 | "short_name" : "AU",
186 | "types" : [ "country", "political" ]
187 | }
188 | ],
189 | "formatted_address" : "Melbourne VIC, Australia",
190 | "geometry" : {
191 | "bounds" : {
192 | "northeast" : {
193 | "lat" : -37.7994893,
194 | "lng" : 144.9890618
195 | },
196 | "southwest" : {
197 | "lat" : -37.8546255,
198 | "lng" : 144.9514222
199 | }
200 | },
201 | "location" : {
202 | "lat" : -37.8142155,
203 | "lng" : 144.9632307
204 | },
205 | "location_type" : "APPROXIMATE",
206 | "viewport" : {
207 | "northeast" : {
208 | "lat" : -37.7994893,
209 | "lng" : 144.9890618
210 | },
211 | "southwest" : {
212 | "lat" : -37.8546255,
213 | "lng" : 144.9514222
214 | }
215 | }
216 | },
217 | "place_id" : "ChIJgf0RD69C1moR4OeMIXVWBAU",
218 | "types" : [ "locality", "political" ]
219 | },
220 | {
221 | "address_components" : [
222 | {
223 | "long_name" : "Melbourne",
224 | "short_name" : "Melbourne",
225 | "types" : [ "colloquial_area", "locality", "political" ]
226 | },
227 | {
228 | "long_name" : "Victoria",
229 | "short_name" : "VIC",
230 | "types" : [ "administrative_area_level_1", "political" ]
231 | },
232 | {
233 | "long_name" : "Australia",
234 | "short_name" : "AU",
235 | "types" : [ "country", "political" ]
236 | }
237 | ],
238 | "formatted_address" : "Melbourne VIC, Australia",
239 | "geometry" : {
240 | "bounds" : {
241 | "northeast" : {
242 | "lat" : -37.4598457,
243 | "lng" : 145.76474
244 | },
245 | "southwest" : {
246 | "lat" : -38.2607199,
247 | "lng" : 144.3944921
248 | }
249 | },
250 | "location" : {
251 | "lat" : -37.814107,
252 | "lng" : 144.96328
253 | },
254 | "location_type" : "APPROXIMATE",
255 | "viewport" : {
256 | "northeast" : {
257 | "lat" : -37.4598457,
258 | "lng" : 145.76474
259 | },
260 | "southwest" : {
261 | "lat" : -38.2607199,
262 | "lng" : 144.3944921
263 | }
264 | }
265 | },
266 | "place_id" : "ChIJ90260rVG1moRkM2MIXVWBAQ",
267 | "types" : [ "colloquial_area", "locality", "political" ]
268 | },
269 | {
270 | "address_components" : [
271 | {
272 | "long_name" : "3000",
273 | "short_name" : "3000",
274 | "types" : [ "postal_code" ]
275 | },
276 | {
277 | "long_name" : "Melbourne",
278 | "short_name" : "Melbourne",
279 | "types" : [ "locality", "political" ]
280 | },
281 | {
282 | "long_name" : "Victoria",
283 | "short_name" : "VIC",
284 | "types" : [ "administrative_area_level_1", "political" ]
285 | },
286 | {
287 | "long_name" : "Australia",
288 | "short_name" : "AU",
289 | "types" : [ "country", "political" ]
290 | }
291 | ],
292 | "formatted_address" : "Melbourne VIC 3000, Australia",
293 | "geometry" : {
294 | "bounds" : {
295 | "northeast" : {
296 | "lat" : -37.7994893,
297 | "lng" : 144.9890618
298 | },
299 | "southwest" : {
300 | "lat" : -37.8300559,
301 | "lng" : 144.9514222
302 | }
303 | },
304 | "location" : {
305 | "lat" : -37.8152065,
306 | "lng" : 144.963937
307 | },
308 | "location_type" : "APPROXIMATE",
309 | "viewport" : {
310 | "northeast" : {
311 | "lat" : -37.7994893,
312 | "lng" : 144.9890618
313 | },
314 | "southwest" : {
315 | "lat" : -37.8300559,
316 | "lng" : 144.9514222
317 | }
318 | }
319 | },
320 | "place_id" : "ChIJm7IcwrhC1moREDUuRnhWBBw",
321 | "types" : [ "postal_code" ]
322 | },
323 | {
324 | "address_components" : [
325 | {
326 | "long_name" : "CBD & South Melbourne",
327 | "short_name" : "CBD & South Melbourne",
328 | "types" : [ "political" ]
329 | },
330 | {
331 | "long_name" : "Victoria",
332 | "short_name" : "VIC",
333 | "types" : [ "administrative_area_level_1", "political" ]
334 | },
335 | {
336 | "long_name" : "Australia",
337 | "short_name" : "AU",
338 | "types" : [ "country", "political" ]
339 | }
340 | ],
341 | "formatted_address" : "CBD & South Melbourne, VIC, Australia",
342 | "geometry" : {
343 | "bounds" : {
344 | "northeast" : {
345 | "lat" : -37.7729624,
346 | "lng" : 145.0155638
347 | },
348 | "southwest" : {
349 | "lat" : -37.8574994,
350 | "lng" : 144.8984946
351 | }
352 | },
353 | "location" : {
354 | "lat" : -37.8362164,
355 | "lng" : 144.9501708
356 | },
357 | "location_type" : "APPROXIMATE",
358 | "viewport" : {
359 | "northeast" : {
360 | "lat" : -37.7729624,
361 | "lng" : 145.0155638
362 | },
363 | "southwest" : {
364 | "lat" : -37.8574994,
365 | "lng" : 144.8984946
366 | }
367 | }
368 | },
369 | "place_id" : "ChIJORuuCkxd1moRNMrml7yk-C8",
370 | "types" : [ "political" ]
371 | },
372 | {
373 | "address_components" : [
374 | {
375 | "long_name" : "Victoria",
376 | "short_name" : "VIC",
377 | "types" : [ "administrative_area_level_1", "political" ]
378 | },
379 | {
380 | "long_name" : "Australia",
381 | "short_name" : "AU",
382 | "types" : [ "country", "political" ]
383 | }
384 | ],
385 | "formatted_address" : "Victoria, Australia",
386 | "geometry" : {
387 | "bounds" : {
388 | "northeast" : {
389 | "lat" : -33.9810507,
390 | "lng" : 149.9764882
391 | },
392 | "southwest" : {
393 | "lat" : -39.2247306,
394 | "lng" : 140.9624773
395 | }
396 | },
397 | "location" : {
398 | "lat" : -37.4713077,
399 | "lng" : 144.7851531
400 | },
401 | "location_type" : "APPROXIMATE",
402 | "viewport" : {
403 | "northeast" : {
404 | "lat" : -33.9810507,
405 | "lng" : 149.9764882
406 | },
407 | "southwest" : {
408 | "lat" : -39.2247306,
409 | "lng" : 140.9624773
410 | }
411 | }
412 | },
413 | "place_id" : "ChIJT5UYfksx1GoRNJWCvuL8Tlo",
414 | "types" : [ "administrative_area_level_1", "political" ]
415 | },
416 | {
417 | "address_components" : [
418 | {
419 | "long_name" : "Australia",
420 | "short_name" : "AU",
421 | "types" : [ "country", "political" ]
422 | }
423 | ],
424 | "formatted_address" : "Australia",
425 | "geometry" : {
426 | "bounds" : {
427 | "northeast" : {
428 | "lat" : -9.2198215,
429 | "lng" : 159.255497
430 | },
431 | "southwest" : {
432 | "lat" : -54.7772185,
433 | "lng" : 112.9214336
434 | }
435 | },
436 | "location" : {
437 | "lat" : -25.274398,
438 | "lng" : 133.775136
439 | },
440 | "location_type" : "APPROXIMATE",
441 | "viewport" : {
442 | "northeast" : {
443 | "lat" : -0.6911343999999999,
444 | "lng" : 166.7429167
445 | },
446 | "southwest" : {
447 | "lat" : -51.66332320000001,
448 | "lng" : 100.0911072
449 | }
450 | }
451 | },
452 | "place_id" : "ChIJ38WHZwf9KysRUhNblaFnglM",
453 | "types" : [ "country", "political" ]
454 | }
455 | ],
456 | "status" : "OK"
457 | }`
458 | response3 = `{
459 | "results" : [],
460 | "status" : "ZERO_RESULTS"
461 | }`
462 | )
463 |
--------------------------------------------------------------------------------
/opencage/geocoder_test.go:
--------------------------------------------------------------------------------
1 | package opencage_test
2 |
3 | import (
4 | "net/http"
5 | "net/http/httptest"
6 | "os"
7 | "strings"
8 | "testing"
9 |
10 | "github.com/codingsince1985/geo-golang/opencage"
11 | "github.com/stretchr/testify/assert"
12 | )
13 |
14 | var key = os.Getenv("OPENCAGE_API_KEY")
15 |
16 | // locDelta is the lat long precision significant digits we care about
17 | // because the opencage API isn't the same exact lat,long as other systems
18 | var locDelta = 0.01
19 |
20 | func TestGeocode(t *testing.T) {
21 | ts := testServer(response1)
22 | defer ts.Close()
23 |
24 | geocoder := opencage.Geocoder(key, ts.URL+"/")
25 | location, err := geocoder.Geocode("60 Collins St, Melbourne VIC 3000")
26 | assert.Nil(t, err)
27 | assert.InDelta(t, -37.8154176, location.Lat, locDelta)
28 | assert.InDelta(t, 144.9665563, location.Lng, locDelta)
29 | }
30 |
31 | func TestReverseGeocode(t *testing.T) {
32 | ts := testServer(response2)
33 | defer ts.Close()
34 |
35 | geocoder := opencage.Geocoder(key, ts.URL+"/")
36 | address, err := geocoder.ReverseGeocode(-37.8154176, 144.9665563)
37 | assert.NoError(t, err)
38 | assert.True(t, strings.Index(address.FormattedAddress, "Collins St") > 0)
39 | }
40 |
41 | func TestReverseGeocodeWithNoResult(t *testing.T) {
42 | ts := testServer(response3)
43 | defer ts.Close()
44 |
45 | geocoder := opencage.Geocoder(key, ts.URL+"/")
46 | address, err := geocoder.ReverseGeocode(-37.8154176, 164.9665563)
47 | assert.Nil(t, err)
48 | assert.Nil(t, address)
49 | }
50 |
51 | func TestReverseGeocodeUsingSuburbAsLocality(t *testing.T) {
52 | ts := testServer(responseMissingLocality)
53 | defer ts.Close()
54 |
55 | geocoder := opencage.Geocoder(key, ts.URL+"/")
56 | address, err := geocoder.ReverseGeocode(-37.8154176, 164.9665563)
57 | assert.Nil(t, err)
58 | assert.NotNil(t, address)
59 | assert.Equal(t, "Lütten Klein", address.City)
60 | }
61 |
62 | func testServer(response string) *httptest.Server {
63 | return httptest.NewServer(http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
64 | resp.Write([]byte(response))
65 | }))
66 | }
67 |
68 | const (
69 | response1 = `{
70 | "documentation":"https://geocoder.opencagedata.com/api",
71 | "licenses":[
72 | {
73 | "name":"CC-BY-SA",
74 | "url":"http://creativecommons.org/licenses/by-sa/3.0/"
75 | },
76 | {
77 | "name":"ODbL",
78 | "url":"http://opendatacommons.org/licenses/odbl/summary/"
79 | }
80 | ],
81 | "rate":{
82 | "limit":2500,
83 | "remaining":2383,
84 | "reset":1463184000
85 | },
86 | "results":[
87 | {
88 | "annotations":{
89 | "DMS":{
90 | "lat":"37\u00b0 48' 59.45148'' S",
91 | "lng":"144\u00b0 57' 47.23560'' E"
92 | },
93 | "MGRS":"55HCU2071312588",
94 | "Maidenhead":"QF22le54na",
95 | "Mercator":{
96 | "x":16137220.814,
97 | "y":-4527336.343
98 | },
99 | "OSM":{
100 | "edit_url":"https://www.openstreetmap.org/edit?way=215320282#map=17/-37.81651/144.96312",
101 | "url":"https://www.openstreetmap.org/?mlat=-37.81651&mlon=144.96312#map=17/-37.81651/144.96312"
102 | },
103 | "callingcode":61,
104 | "geohash":"r1r0fewyvdc3r425n4nx",
105 | "sun":{
106 | "rise":{
107 | "apparent":1463173980,
108 | "astronomical":1463168580,
109 | "civil":1463172300,
110 | "nautical":1463170440
111 | },
112 | "set":{
113 | "apparent":1463124120,
114 | "astronomical":1463129580,
115 | "civil":1463125800,
116 | "nautical":1463127720
117 | }
118 | },
119 | "timezone":{
120 | "name":"Australia/Melbourne",
121 | "now_in_dst":0,
122 | "offset_sec":36000,
123 | "offset_string":1000,
124 | "short_name":"AEST"
125 | },
126 | "what3words":{
127 | "words":"themes.fees.tolls"
128 | }
129 | },
130 | "bounds":{
131 | "northeast":{
132 | "lat":-37.8162553,
133 | "lng":144.9640149
134 | },
135 | "southwest":{
136 | "lat":-37.8169249,
137 | "lng":144.9617036
138 | }
139 | },
140 | "components":{
141 | "_type":"road",
142 | "city":"Melbourne",
143 | "country":"Australia",
144 | "country_code":"au",
145 | "county":"City of Melbourne",
146 | "postcode":"3000",
147 | "region":"Greater Melbourne",
148 | "road":"Collins Street",
149 | "state":"Victoria",
150 | "suburb":"Melbourne"
151 | },
152 | "confidence":10,
153 | "formatted":"Collins Street, Melbourne VIC 3000, Australia",
154 | "geometry":{
155 | "lat":-37.8165143,
156 | "lng":144.963121
157 | }
158 | },
159 | {
160 | "annotations":{
161 | "DMS":{
162 | "lat":"37\u00b0 48' 50.40000'' S",
163 | "lng":"144\u00b0 57' 47.88000'' E"
164 | },
165 | "MGRS":"55HCU2072312867",
166 | "Maidenhead":"QF22le54op",
167 | "Mercator":{
168 | "x":16137240.74,
169 | "y":-4526983.531
170 | },
171 | "OSM":{
172 | "url":"https://www.openstreetmap.org/?mlat=-37.81400&mlon=144.96330#map=17/-37.81400/144.96330"
173 | },
174 | "geohash":"r1r0fspj3xqzwneegpsn",
175 | "sun":{
176 | "rise":{
177 | "apparent":1463173980,
178 | "astronomical":1463168580,
179 | "civil":1463172300,
180 | "nautical":1463170440
181 | },
182 | "set":{
183 | "apparent":1463124120,
184 | "astronomical":1463129580,
185 | "civil":1463125800,
186 | "nautical":1463127720
187 | }
188 | },
189 | "timezone":{
190 | "name":"Australia/Melbourne",
191 | "now_in_dst":0,
192 | "offset_sec":36000,
193 | "offset_string":1000,
194 | "short_name":"AEST"
195 | },
196 | "what3words":{
197 | "words":"chat.seat.fortunate"
198 | }
199 | },
200 | "components":{
201 | "_type":"postcode",
202 | "country":"Australia",
203 | "postcode":"3000"
204 | },
205 | "confidence":10,
206 | "formatted":"3000, Australia",
207 | "geometry":{
208 | "lat":-37.814,
209 | "lng":144.9633
210 | }
211 | }
212 | ],
213 | "status":{
214 | "code":200,
215 | "message":"OK"
216 | },
217 | "stay_informed":{
218 | "blog":"http://blog.opencagedata.com",
219 | "twitter":"https://twitter.com/opencagedata"
220 | },
221 | "thanks":"For using an OpenCage Data API",
222 | "timestamp":{
223 | "created_http":"Fri, 13 May 2016 10:39:20 GMT",
224 | "created_unix":1463135960
225 | },
226 | "total_results":2
227 | }`
228 | response2 = `{
229 | "documentation":"https://geocoder.opencagedata.com/api",
230 | "licenses":[
231 | {
232 | "name":"CC-BY-SA",
233 | "url":"http://creativecommons.org/licenses/by-sa/3.0/"
234 | },
235 | {
236 | "name":"ODbL",
237 | "url":"http://opendatacommons.org/licenses/odbl/summary/"
238 | }
239 | ],
240 | "rate":{
241 | "limit":2500,
242 | "remaining":2382,
243 | "reset":1463184000
244 | },
245 | "results":[
246 | {
247 | "annotations":{
248 | "DMS":{
249 | "lat":"37\u00b0 48' 55.11266'' S",
250 | "lng":"144\u00b0 57' 59.36892'' E"
251 | },
252 | "MGRS":"55HCU2100712728",
253 | "Maidenhead":"QF22le54xh",
254 | "Mercator":{
255 | "x":16137596.001,
256 | "y":-4527167.222
257 | },
258 | "OSM":{
259 | "edit_url":"https://www.openstreetmap.org/edit?way=87337974#map=17/-37.81531/144.96649",
260 | "url":"https://www.openstreetmap.org/?mlat=-37.81531&mlon=144.96649#map=17/-37.81531/144.96649"
261 | },
262 | "callingcode":61,
263 | "geohash":"r1r0fgcmu56wg15ek296",
264 | "sun":{
265 | "rise":{
266 | "apparent":1463173980,
267 | "astronomical":1463168580,
268 | "civil":1463172300,
269 | "nautical":1463170440
270 | },
271 | "set":{
272 | "apparent":1463124120,
273 | "astronomical":1463129580,
274 | "civil":1463125800,
275 | "nautical":1463127720
276 | }
277 | },
278 | "timezone":{
279 | "name":"Australia/Melbourne",
280 | "now_in_dst":0,
281 | "offset_sec":36000,
282 | "offset_string":1000,
283 | "short_name":"AEST"
284 | },
285 | "what3words":{
286 | "words":"clocks.beard.slices"
287 | }
288 | },
289 | "components":{
290 | "_type":"building",
291 | "city":"Melbourne",
292 | "country":"Australia",
293 | "country_code":"au",
294 | "county":"City of Melbourne",
295 | "house_number":"230",
296 | "postcode":"3000",
297 | "region":"Greater Melbourne",
298 | "road":"Collins Street",
299 | "state":"Victoria",
300 | "suburb":"Melbourne"
301 | },
302 | "confidence":10,
303 | "formatted":"230 Collins Street, Melbourne VIC 3000, Australia",
304 | "geometry":{
305 | "lat":-37.8153091,
306 | "lng":144.9664914
307 | }
308 | }
309 | ],
310 | "status":{
311 | "code":200,
312 | "message":"OK"
313 | },
314 | "stay_informed":{
315 | "blog":"http://blog.opencagedata.com",
316 | "twitter":"https://twitter.com/opencagedata"
317 | },
318 | "thanks":"For using an OpenCage Data API",
319 | "timestamp":{
320 | "created_http":"Fri, 13 May 2016 10:43:19 GMT",
321 | "created_unix":1463136199
322 | },
323 | "total_results":1
324 | }`
325 | response3 = `{
326 | "documentation":"https://geocoder.opencagedata.com/api",
327 | "licenses":[
328 | {
329 | "name":"CC-BY-SA",
330 | "url":"http://creativecommons.org/licenses/by-sa/3.0/"
331 | },
332 | {
333 | "name":"ODbL",
334 | "url":"http://opendatacommons.org/licenses/odbl/summary/"
335 | }
336 | ],
337 | "rate":{
338 | "limit":2500,
339 | "remaining":2381,
340 | "reset":1463184000
341 | },
342 | "results":[
343 |
344 | ],
345 | "status":{
346 | "code":200,
347 | "message":"OK"
348 | },
349 | "stay_informed":{
350 | "blog":"http://blog.opencagedata.com",
351 | "twitter":"https://twitter.com/opencagedata"
352 | },
353 | "thanks":"For using an OpenCage Data API",
354 | "timestamp":{
355 | "created_http":"Fri, 13 May 2016 10:34:05 GMT",
356 | "created_unix":1463135645
357 | },
358 | "total_results":0
359 | }`
360 | responseMissingLocality = `{
361 | "documentation": "https://opencagedata.com/api",
362 | "licenses": [
363 | {
364 | "name": "CC-BY-SA",
365 | "url": "https://creativecommons.org/licenses/by-sa/3.0/"
366 | },
367 | {
368 | "name": "ODbL",
369 | "url": "https://opendatacommons.org/licenses/odbl/summary/"
370 | }
371 | ],
372 | "results": [
373 | {
374 | "annotations": {
375 | "DMS": {
376 | "lat": "54° 8' 30.29676'' N",
377 | "lng": "12° 3' 0.72252'' E"
378 | },
379 | "MGRS": "33UUA0732603314",
380 | "Maidenhead": "JO64ad64aa",
381 | "Mercator": {
382 | "x": 1341422.206,
383 | "y": 7162391.731
384 | },
385 | "OSM": {
386 | "edit_url": "https://www.openstreetmap.org/edit?way=89311416#map=17/54.14175/12.05020",
387 | "url": "https://www.openstreetmap.org/?mlat=54.14175&mlon=12.05020#map=17/54.14175/12.05020"
388 | },
389 | "callingcode": 49,
390 | "currency": {
391 | "alternate_symbols": [],
392 | "decimal_mark": ",",
393 | "html_entity": "€",
394 | "iso_code": "EUR",
395 | "iso_numeric": 978,
396 | "name": "Euro",
397 | "smallest_denomination": 1,
398 | "subunit": "Cent",
399 | "subunit_to_unit": 100,
400 | "symbol": "€",
401 | "symbol_first": 1,
402 | "thousands_separator": "."
403 | },
404 | "flag": "\ud83c\udde9\ud83c\uddea🇩🇪",
405 | "geohash": "u38s40nww18cs2tdhe9q",
406 | "qibla": 136.28,
407 | "sun": {
408 | "rise": {
409 | "apparent": 1542177540,
410 | "astronomical": 1542170040,
411 | "civil": 1542175140,
412 | "nautical": 1542172560
413 | },
414 | "set": {
415 | "apparent": 1542208380,
416 | "astronomical": 1542215880,
417 | "civil": 1542210780,
418 | "nautical": 1542213360
419 | }
420 | },
421 | "timezone": {
422 | "name": "Europe/Berlin",
423 | "now_in_dst": 0,
424 | "offset_sec": 3600,
425 | "offset_string": 100,
426 | "short_name": "CET"
427 | },
428 | "what3words": {
429 | "words": "staked.nimbly.scatter"
430 | }
431 | },
432 | "bounds": {
433 | "northeast": {
434 | "lat": 54.1418491,
435 | "lng": 12.0503007
436 | },
437 | "southwest": {
438 | "lat": 54.1416491,
439 | "lng": 12.0501007
440 | }
441 | },
442 | "components": {
443 | "ISO_3166-1_alpha-2": "DE",
444 | "_type": "building",
445 | "city_district": "Ortsbeirat 5 : Lütten Klein",
446 | "country": "Germany",
447 | "country_code": "de",
448 | "county": "Rostock",
449 | "house_number": "30",
450 | "political_union": "European Union",
451 | "postcode": "18107",
452 | "road": "Turkuer Straße",
453 | "state": "Mecklenburg-Vorpommern",
454 | "suburb": "Lütten Klein"
455 | },
456 | "confidence": 10,
457 | "formatted": "Turkuer Straße 30, 18107 Rostock, Germany",
458 | "geometry": {
459 | "lat": 54.1417491,
460 | "lng": 12.0502007
461 | }
462 | }
463 | ],
464 | "status": {
465 | "code": 200,
466 | "message": "OK"
467 | },
468 | "stay_informed": {
469 | "blog": "https://blog.opencagedata.com",
470 | "twitter": "https://twitter.com/opencagedata"
471 | },
472 | "thanks": "For using an OpenCage Data API",
473 | "timestamp": {
474 | "created_http": "Wed, 14 Nov 2018 11:55:11 GMT",
475 | "created_unix": 1542196511
476 | },
477 | "total_results": 1
478 | }`
479 | )
480 |
--------------------------------------------------------------------------------