Skip to content

Commit 2a8ea2b

Browse files
author
Lyuben Manolov
authored
Merge pull request #34 from manolovl/add-structured-address
Add structured address
2 parents f44149a + 22149d5 commit 2a8ea2b

31 files changed

Lines changed: 988 additions & 440 deletions

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
language: go
22
sudo: false
33
go:
4-
- '1.5'
4+
- '1.7'
55
- tip
66
go_import_path: github.com/codingsince1985/geo-golang
77
before_install:

README.md

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
GeoService in Go
22
==
3-
[![GoDoc](https://godoc.org/github.com/codingsince1985/geo-golang?status.svg)](https://godoc.org/github.com/codingsince1985/geo-golang) [![Build Status](https://travis-ci.org/codingsince1985/geo-golang.svg?branch=master)](https://travis-ci.org/codingsince1985/geo-golang)
3+
[![GoDoc](https://godoc.org/github.com/codingsince1985/geo-golang?status.svg)](https://godoc.org/github.com/codingsince1985/geo-golang) [![Build Status](https://travis-ci.org/codingsince1985/geo-golang.svg?branch=master)](https://travis-ci.org/codingsince1985/geo-golang)
44
[![codecov](https://codecov.io/gh/codingsince1985/geo-golang/branch/master/graph/badge.svg)](https://codecov.io/gh/codingsince1985/geo-golang)
55
[![Go Report Card](https://goreportcard.com/badge/codingsince1985/geo-golang)](https://goreportcard.com/report/codingsince1985/geo-golang)
66

@@ -51,11 +51,15 @@ const (
5151
)
5252

5353
func main() {
54+
ExampleGeocoder()
55+
}
56+
57+
func ExampleGeocoder() {
5458
fmt.Println("Google Geocoding API")
5559
try(google.Geocoder(os.Getenv("GOOGLE_API_KEY")))
5660

5761
fmt.Println("Mapquest Nominatim")
58-
try(nominatim.Geocoder(os.Getenv("MAPQUEST_NOMINATUM_KEY")))
62+
try(nominatim.Geocoder(os.Getenv("MAPQUEST_NOMINATIM_KEY")))
5963

6064
fmt.Println("Mapquest Open streetmaps")
6165
try(open.Geocoder(os.Getenv("MAPQUEST_OPEN_KEY")))
@@ -74,7 +78,7 @@ func main() {
7478

7579
fmt.Println("OpenStreetMap")
7680
try(openstreetmap.Geocoder())
77-
81+
7882
fmt.Println("LocationIQ")
7983
try(locationiq.Geocoder(os.Getenv("LOCATIONIQ_API_KEY"), ZOOM))
8084

@@ -88,52 +92,72 @@ func main() {
8892

8993
func try(geocoder geo.Geocoder) {
9094
location, _ := geocoder.Geocode(addr)
91-
fmt.Printf("%s location is %v\n", addr, location)
95+
if location != nil {
96+
fmt.Printf("%s location is (%.6f, %.6f)\n", addr, location.Lat, location.Lng)
97+
} else {
98+
fmt.Println("got <nil> location")
99+
}
92100
address, _ := geocoder.ReverseGeocode(lat, lng)
93-
fmt.Printf("Address of (%f,%f) is %s\n\n", lat, lng, address)
101+
if address != nil {
102+
fmt.Printf("Address of (%.6f,%.6f) is %s\n", lat, lng, address.FormattedAddress)
103+
fmt.Printf("Detailed address: %#v\n", address)
104+
} else {
105+
fmt.Println("got <nil> address")
106+
}
107+
fmt.Println("\n")
94108
}
95109
```
96110
###Result
97111
```
98112
Google Geocoding API
99113
Melbourne VIC location is (-37.813611, 144.963056)
100-
Address of (-37.813611,144.963056) is 350 Bourke St, Melbourne VIC 3004, Australia
114+
Address of (-37.813611,144.963056) is 197 Elizabeth St, Melbourne VIC 3000, Australia
115+
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"}
101116
102117
Mapquest Nominatim
103118
Melbourne VIC location is (-37.814218, 144.963161)
104-
Address of (-37.813611,144.963056) is Melbourne's GPO, Postal Lane, Chinatown, Melbourne, City of Melbourne, Greater Melbourne, Victoria, 3000, Australia
119+
Address of (-37.813611,144.963056) is Melbourne's GPO, Postal Lane, Melbourne, City of Melbourne, Greater Melbourne, Victoria, 3000, Australia
120+
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"}
105121
106122
Mapquest Open streetmaps
107123
Melbourne VIC location is (-37.814218, 144.963161)
108-
Address of (-37.813611,144.963056) is Postal Lane, Melbourne, Victoria, AU
124+
Address of (-37.813611,144.963056) is Elizabeth Street, 3000, Melbourne, Victoria, AU
125+
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"}
109126
110127
OpenCage Data
111128
Melbourne VIC location is (-37.814217, 144.963161)
112129
Address of (-37.813611,144.963056) is Melbourne's GPO, Postal Lane, Melbourne VIC 3000, Australia
130+
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"}
113131
114132
HERE API
115133
Melbourne VIC location is (-37.817530, 144.967150)
116134
Address of (-37.813611,144.963056) is 197 Elizabeth St, Melbourne VIC 3000, Australia
135+
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"}
117136
118137
Bing Geocoding API
119138
Melbourne VIC location is (-37.824299, 144.977997)
120139
Address of (-37.813611,144.963056) is Elizabeth St, Melbourne, VIC 3000
140+
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"}
121141
122142
Mapbox API
123143
Melbourne VIC location is (-37.814200, 144.963200)
124-
Address of (-37.813611,144.963056) is Elwood Park Playground, 3000 Melbourne, Australia
144+
Address of (-37.813611,144.963056) is Elwood Park Playground, Melbourne, Victoria 3000, Australia
145+
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"}
125146
126147
OpenStreetMap
127148
Melbourne VIC location is (-37.814217, 144.963161)
128149
Address of (-37.813611,144.963056) is Melbourne's GPO, Postal Lane, Chinatown, Melbourne, City of Melbourne, Greater Melbourne, Victoria, 3000, Australia
150+
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"}
129151
130152
LocationIQ
131153
Melbourne VIC location is (-37.814217, 144.963161)
132154
Address of (-37.813611,144.963056) is Melbourne's GPO, Postal Lane, Chinatown, Melbourne, City of Melbourne, Greater Melbourne, Victoria, 3000, Australia
155+
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"}
133156
134157
ChainedAPI[OpenStreetmap -> Google]
135158
Melbourne VIC location is (-37.814217, 144.963161)
136159
Address of (-37.813611,144.963056) is Melbourne's GPO, Postal Lane, Chinatown, Melbourne, City of Melbourne, Greater Melbourne, Victoria, 3000, Australia
160+
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"}
137161
```
138162
License
139163
==

bing/geocoder.go

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
package bing
33

44
import (
5+
"errors"
56
"fmt"
6-
"github.com/codingsince1985/geo-golang"
77
"strings"
8+
9+
"github.com/codingsince1985/geo-golang"
810
)
911

1012
type (
@@ -17,9 +19,16 @@ type (
1719
}
1820
Address struct {
1921
FormattedAddress string
22+
AddressLine string
23+
AdminDistrict string
24+
AdminDistrict2 string
25+
CountryRegion string
26+
Locality string
27+
PostalCode string
2028
}
2129
}
2230
}
31+
ErrorDetails []string
2332
}
2433
)
2534

@@ -46,17 +55,31 @@ func (b baseURL) ReverseGeocodeURL(l geo.Location) string {
4655
return strings.Replace(string(b), "*", fmt.Sprintf("/%f,%f?", l.Lat, l.Lng), 1)
4756
}
4857

49-
func (r *geocodeResponse) Location() geo.Location {
50-
if len(r.ResourceSets) == 0 || len(r.ResourceSets[0].Resources) == 0 {
51-
return geo.Location{}
58+
func (r *geocodeResponse) Location() (*geo.Location, error) {
59+
if len(r.ResourceSets) <= 0 || len(r.ResourceSets[0].Resources) <= 0 {
60+
return nil, nil
5261
}
5362
c := r.ResourceSets[0].Resources[0].Point.Coordinates
54-
return geo.Location{c[0], c[1]}
63+
return &geo.Location{
64+
Lat: c[0],
65+
Lng: c[1],
66+
}, nil
5567
}
5668

57-
func (r *geocodeResponse) Address() string {
58-
if len(r.ResourceSets) == 0 || len(r.ResourceSets[0].Resources) == 0 {
59-
return ""
69+
func (r *geocodeResponse) Address() (*geo.Address, error) {
70+
if len(r.ErrorDetails) > 0 {
71+
return nil, errors.New(strings.Join(r.ErrorDetails, " "))
6072
}
61-
return r.ResourceSets[0].Resources[0].Address.FormattedAddress
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
6285
}

bing/geocoder_test.go

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
package bing_test
22

33
import (
4-
"fmt"
5-
"github.com/codingsince1985/geo-golang"
6-
"github.com/codingsince1985/geo-golang/bing"
7-
"github.com/stretchr/testify/assert"
84
"net/http"
95
"net/http/httptest"
106
"os"
117
"strings"
128
"testing"
9+
10+
"github.com/codingsince1985/geo-golang"
11+
"github.com/codingsince1985/geo-golang/bing"
12+
"github.com/stretchr/testify/assert"
1313
)
1414

1515
var key = os.Getenv("BING_API_KEY")
@@ -20,9 +20,9 @@ func TestGeocode(t *testing.T) {
2020

2121
geocoder := bing.Geocoder(key, ts.URL+"/")
2222
location, err := geocoder.Geocode("60 Collins St, Melbourne VIC")
23+
2324
assert.NoError(t, err)
24-
fmt.Println(location)
25-
assert.Equal(t, geo.Location{Lat: -37.81375, Lng: 144.97176}, location)
25+
assert.Equal(t, geo.Location{Lat: -37.81375, Lng: 144.97176}, *location)
2626
}
2727

2828
func TestReverseGeocode(t *testing.T) {
@@ -31,18 +31,20 @@ func TestReverseGeocode(t *testing.T) {
3131

3232
geocoder := bing.Geocoder(key, ts.URL+"/")
3333
address, err := geocoder.ReverseGeocode(-37.81375, 144.97176)
34+
3435
assert.NoError(t, err)
35-
fmt.Println(address)
36-
assert.True(t, strings.Index(address, "Collins St") > 0)
36+
assert.True(t, strings.Index(address.FormattedAddress, "Collins St") > 0)
3737
}
3838

3939
func TestReverseGeocodeWithNoResult(t *testing.T) {
4040
ts := testServer(response3)
4141
defer ts.Close()
4242

4343
geocoder := bing.Geocoder(key, ts.URL+"/")
44-
_, err := geocoder.ReverseGeocode(-37.81375, 164.97176)
45-
assert.Equal(t, err, geo.ErrNoResult)
44+
addr, err := geocoder.ReverseGeocode(-37.81375, 164.97176)
45+
46+
assert.NoError(t, err)
47+
assert.Nil(t, addr)
4648
}
4749

4850
func testServer(response string) *httptest.Server {

cached/geocoder.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ func Geocoder(geocoder geo.Geocoder, cache *cache.Cache) geo.Geocoder {
1818
}
1919

2020
// Geocode returns location for address
21-
func (c cachedGeocoder) Geocode(address string) (geo.Location, error) {
21+
func (c cachedGeocoder) Geocode(address string) (*geo.Location, error) {
2222
// Check if we've cached this response
2323
if cachedLoc, found := c.Cache.Get(address); found {
24-
return cachedLoc.(geo.Location), nil
24+
return cachedLoc.(*geo.Location), nil
2525
}
2626

2727
if loc, err := c.Geocoder.Geocode(address); err != nil {
@@ -33,15 +33,15 @@ func (c cachedGeocoder) Geocode(address string) (geo.Location, error) {
3333
}
3434

3535
// ReverseGeocode returns address for location
36-
func (c cachedGeocoder) ReverseGeocode(lat, lng float64) (string, error) {
36+
func (c cachedGeocoder) ReverseGeocode(lat, lng float64) (*geo.Address, error) {
3737
// Check if we've cached this response
3838
locKey := fmt.Sprintf("geo.Location{%f,%f}", lat, lng)
3939
if cachedAddr, found := c.Cache.Get(locKey); found {
40-
return cachedAddr.(string), nil
40+
return cachedAddr.(*geo.Address), nil
4141
}
4242

4343
if addr, err := c.Geocoder.ReverseGeocode(lat, lng); err != nil {
44-
return "", err
44+
return nil, err
4545
} else {
4646
c.Cache.Set(locKey, addr, 0)
4747
return addr, nil

cached/geocoder_test.go

Lines changed: 41 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,85 @@
11
package cached_test
22

33
import (
4+
"strings"
5+
"testing"
6+
"time"
7+
48
"github.com/codingsince1985/geo-golang"
59
"github.com/codingsince1985/geo-golang/cached"
610
"github.com/codingsince1985/geo-golang/data"
711
"github.com/patrickmn/go-cache"
812
"github.com/stretchr/testify/assert"
9-
10-
"strings"
11-
"testing"
12-
"time"
1313
)
1414

1515
var geoCache = cache.New(5*time.Minute, 30*time.Second)
1616

1717
// geocoder is chained with one data geocoder with address -> location data
1818
// the other has location -> address data
1919
// this will exercise the chained fallback handling
20-
var geocoder = cached.Geocoder(
21-
data.Geocoder(
22-
data.AddressToLocation{
23-
"Melbourne VIC": geo.Location{Lat: -37.814107, Lng: 144.96328},
24-
},
25-
data.LocationToAddress{
26-
geo.Location{Lat: -37.816742, Lng: 144.964463}: "Melbourne VIC 3000, Australia",
27-
},
28-
),
29-
geoCache,
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+
)
3039
)
3140

3241
func TestGeocode(t *testing.T) {
33-
location, err := geocoder.Geocode("Melbourne VIC")
42+
location, err := geocoder.Geocode("64 Elizabeth Street, Melbourne, Victoria 3000, Australia")
3443
assert.NoError(t, err)
35-
assert.Equal(t, geo.Location{Lat: -37.814107, Lng: 144.96328}, location)
44+
assert.Equal(t, locationFixture, *location)
3645
}
3746

3847
func TestReverseGeocode(t *testing.T) {
39-
address, err := geocoder.ReverseGeocode(-37.816742, 144.964463)
48+
address, err := geocoder.ReverseGeocode(locationFixture.Lat, locationFixture.Lng)
4049
assert.NoError(t, err)
41-
assert.True(t, strings.HasSuffix(address, "Melbourne VIC 3000, Australia"))
50+
assert.True(t, strings.HasSuffix(address.FormattedAddress, "Melbourne, Victoria 3000, Australia"))
4251
}
4352

4453
func TestReverseGeocodeWithNoResult(t *testing.T) {
45-
_, err := geocoder.ReverseGeocode(-37.816742, 164.964463)
46-
assert.Equal(t, err, geo.ErrNoResult)
54+
addr, err := geocoder.ReverseGeocode(1, 2)
55+
assert.Nil(t, err)
56+
assert.Nil(t, addr)
4757
}
4858

4959
func TestCachedGeocode(t *testing.T) {
60+
mockAddr := geo.Address{
61+
FormattedAddress: "42, Some Street, Austin, Texas",
62+
}
5063
mock1 := data.Geocoder(
5164
data.AddressToLocation{
52-
"Austin,TX": geo.Location{Lat: 1, Lng: 2},
65+
mockAddr: geo.Location{Lat: 1, Lng: 2},
5366
},
5467
data.LocationToAddress{},
5568
)
5669

5770
c := cached.Geocoder(mock1, geoCache)
5871

59-
l, err := c.Geocode("Austin,TX")
72+
l, err := c.Geocode("42, Some Street, Austin, Texas")
6073
assert.NoError(t, err)
61-
assert.Equal(t, geo.Location{Lat: 1, Lng: 2}, l)
74+
assert.Equal(t, geo.Location{Lat: 1, Lng: 2}, *l)
6275

6376
// Should be cached
6477
// TODO: write a mock Cache impl to test cache is being used
65-
l, err = c.Geocode("Austin,TX")
78+
l, err = c.Geocode("42, Some Street, Austin, Texas")
6679
assert.NoError(t, err)
67-
assert.Equal(t, geo.Location{Lat: 1, Lng: 2}, l)
80+
assert.Equal(t, geo.Location{Lat: 1, Lng: 2}, *l)
6881

69-
_, err = c.Geocode("NOWHERE,TX")
70-
assert.Equal(t, geo.ErrNoResult, err)
82+
addr, err := c.Geocode("NOWHERE,TX")
83+
assert.Nil(t, err)
84+
assert.Nil(t, addr)
7185
}

0 commit comments

Comments
 (0)