Skip to content

Commit

Permalink
Merge pull request #34 from manolovl/add-structured-address
Browse files Browse the repository at this point in the history
Add structured address
  • Loading branch information
Lyuben Manolov authored Feb 11, 2017
2 parents f44149a + 22149d5 commit 2a8ea2b
Show file tree
Hide file tree
Showing 31 changed files with 988 additions and 440 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
language: go
sudo: false
go:
- '1.5'
- '1.7'
- tip
go_import_path: github.com/codingsince1985/geo-golang
before_install:
Expand Down
42 changes: 33 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
GeoService in Go
==
[![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)
[![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)
[![codecov](https://codecov.io/gh/codingsince1985/geo-golang/branch/master/graph/badge.svg)](https://codecov.io/gh/codingsince1985/geo-golang)
[![Go Report Card](https://goreportcard.com/badge/codingsince1985/geo-golang)](https://goreportcard.com/report/codingsince1985/geo-golang)

Expand Down Expand Up @@ -51,11 +51,15 @@ const (
)

func main() {
ExampleGeocoder()
}

func ExampleGeocoder() {
fmt.Println("Google Geocoding API")
try(google.Geocoder(os.Getenv("GOOGLE_API_KEY")))

fmt.Println("Mapquest Nominatim")
try(nominatim.Geocoder(os.Getenv("MAPQUEST_NOMINATUM_KEY")))
try(nominatim.Geocoder(os.Getenv("MAPQUEST_NOMINATIM_KEY")))

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

fmt.Println("OpenStreetMap")
try(openstreetmap.Geocoder())

fmt.Println("LocationIQ")
try(locationiq.Geocoder(os.Getenv("LOCATIONIQ_API_KEY"), ZOOM))

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

func try(geocoder geo.Geocoder) {
location, _ := geocoder.Geocode(addr)
fmt.Printf("%s location is %v\n", addr, location)
if location != nil {
fmt.Printf("%s location is (%.6f, %.6f)\n", addr, location.Lat, location.Lng)
} else {
fmt.Println("got <nil> location")
}
address, _ := geocoder.ReverseGeocode(lat, lng)
fmt.Printf("Address of (%f,%f) is %s\n\n", lat, lng, address)
if address != nil {
fmt.Printf("Address of (%.6f,%.6f) is %s\n", lat, lng, address.FormattedAddress)
fmt.Printf("Detailed address: %#v\n", address)
} else {
fmt.Println("got <nil> address")
}
fmt.Println("\n")
}
```
###Result
```
Google Geocoding API
Melbourne VIC location is (-37.813611, 144.963056)
Address of (-37.813611,144.963056) is 350 Bourke St, Melbourne VIC 3004, Australia
Address of (-37.813611,144.963056) is 197 Elizabeth St, Melbourne VIC 3000, Australia
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"}
Mapquest Nominatim
Melbourne VIC location is (-37.814218, 144.963161)
Address of (-37.813611,144.963056) is Melbourne's GPO, Postal Lane, Chinatown, Melbourne, City of Melbourne, Greater Melbourne, Victoria, 3000, Australia
Address of (-37.813611,144.963056) is Melbourne's GPO, Postal Lane, Melbourne, City of Melbourne, Greater Melbourne, Victoria, 3000, Australia
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"}
Mapquest Open streetmaps
Melbourne VIC location is (-37.814218, 144.963161)
Address of (-37.813611,144.963056) is Postal Lane, Melbourne, Victoria, AU
Address of (-37.813611,144.963056) is Elizabeth Street, 3000, Melbourne, Victoria, AU
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"}
OpenCage Data
Melbourne VIC location is (-37.814217, 144.963161)
Address of (-37.813611,144.963056) is Melbourne's GPO, Postal Lane, Melbourne VIC 3000, Australia
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"}
HERE API
Melbourne VIC location is (-37.817530, 144.967150)
Address of (-37.813611,144.963056) is 197 Elizabeth St, Melbourne VIC 3000, Australia
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"}
Bing Geocoding API
Melbourne VIC location is (-37.824299, 144.977997)
Address of (-37.813611,144.963056) is Elizabeth St, Melbourne, VIC 3000
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"}
Mapbox API
Melbourne VIC location is (-37.814200, 144.963200)
Address of (-37.813611,144.963056) is Elwood Park Playground, 3000 Melbourne, Australia
Address of (-37.813611,144.963056) is Elwood Park Playground, Melbourne, Victoria 3000, Australia
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"}
OpenStreetMap
Melbourne VIC location is (-37.814217, 144.963161)
Address of (-37.813611,144.963056) is Melbourne's GPO, Postal Lane, Chinatown, Melbourne, City of Melbourne, Greater Melbourne, Victoria, 3000, Australia
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"}
LocationIQ
Melbourne VIC location is (-37.814217, 144.963161)
Address of (-37.813611,144.963056) is Melbourne's GPO, Postal Lane, Chinatown, Melbourne, City of Melbourne, Greater Melbourne, Victoria, 3000, Australia
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"}
ChainedAPI[OpenStreetmap -> Google]
Melbourne VIC location is (-37.814217, 144.963161)
Address of (-37.813611,144.963056) is Melbourne's GPO, Postal Lane, Chinatown, Melbourne, City of Melbourne, Greater Melbourne, Victoria, 3000, Australia
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"}
```
License
==
Expand Down
41 changes: 32 additions & 9 deletions bing/geocoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
package bing

import (
"errors"
"fmt"
"github.com/codingsince1985/geo-golang"
"strings"

"github.com/codingsince1985/geo-golang"
)

type (
Expand All @@ -17,9 +19,16 @@ type (
}
Address struct {
FormattedAddress string
AddressLine string
AdminDistrict string
AdminDistrict2 string
CountryRegion string
Locality string
PostalCode string
}
}
}
ErrorDetails []string
}
)

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

func (r *geocodeResponse) Location() geo.Location {
if len(r.ResourceSets) == 0 || len(r.ResourceSets[0].Resources) == 0 {
return geo.Location{}
func (r *geocodeResponse) Location() (*geo.Location, error) {
if len(r.ResourceSets) <= 0 || len(r.ResourceSets[0].Resources) <= 0 {
return nil, nil
}
c := r.ResourceSets[0].Resources[0].Point.Coordinates
return geo.Location{c[0], c[1]}
return &geo.Location{
Lat: c[0],
Lng: c[1],
}, nil
}

func (r *geocodeResponse) Address() string {
if len(r.ResourceSets) == 0 || len(r.ResourceSets[0].Resources) == 0 {
return ""
func (r *geocodeResponse) Address() (*geo.Address, error) {
if len(r.ErrorDetails) > 0 {
return nil, errors.New(strings.Join(r.ErrorDetails, " "))
}
return r.ResourceSets[0].Resources[0].Address.FormattedAddress
if len(r.ResourceSets) <= 0 || len(r.ResourceSets[0].Resources) <= 0 {
return nil, nil
}

a := r.ResourceSets[0].Resources[0].Address
return &geo.Address{
FormattedAddress: a.FormattedAddress,
Street: a.AddressLine,
City: a.Locality,
Postcode: a.PostalCode,
Country: a.CountryRegion,
}, nil
}
22 changes: 12 additions & 10 deletions bing/geocoder_test.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
package bing_test

import (
"fmt"
"github.com/codingsince1985/geo-golang"
"github.com/codingsince1985/geo-golang/bing"
"github.com/stretchr/testify/assert"
"net/http"
"net/http/httptest"
"os"
"strings"
"testing"

"github.com/codingsince1985/geo-golang"
"github.com/codingsince1985/geo-golang/bing"
"github.com/stretchr/testify/assert"
)

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

geocoder := bing.Geocoder(key, ts.URL+"/")
location, err := geocoder.Geocode("60 Collins St, Melbourne VIC")

assert.NoError(t, err)
fmt.Println(location)
assert.Equal(t, geo.Location{Lat: -37.81375, Lng: 144.97176}, location)
assert.Equal(t, geo.Location{Lat: -37.81375, Lng: 144.97176}, *location)
}

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

geocoder := bing.Geocoder(key, ts.URL+"/")
address, err := geocoder.ReverseGeocode(-37.81375, 144.97176)

assert.NoError(t, err)
fmt.Println(address)
assert.True(t, strings.Index(address, "Collins St") > 0)
assert.True(t, strings.Index(address.FormattedAddress, "Collins St") > 0)
}

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

geocoder := bing.Geocoder(key, ts.URL+"/")
_, err := geocoder.ReverseGeocode(-37.81375, 164.97176)
assert.Equal(t, err, geo.ErrNoResult)
addr, err := geocoder.ReverseGeocode(-37.81375, 164.97176)

assert.NoError(t, err)
assert.Nil(t, addr)
}

func testServer(response string) *httptest.Server {
Expand Down
10 changes: 5 additions & 5 deletions cached/geocoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ func Geocoder(geocoder geo.Geocoder, cache *cache.Cache) geo.Geocoder {
}

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

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

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

if addr, err := c.Geocoder.ReverseGeocode(lat, lng); err != nil {
return "", err
return nil, err
} else {
c.Cache.Set(locKey, addr, 0)
return addr, nil
Expand Down
68 changes: 41 additions & 27 deletions cached/geocoder_test.go
Original file line number Diff line number Diff line change
@@ -1,71 +1,85 @@
package cached_test

import (
"strings"
"testing"
"time"

"github.com/codingsince1985/geo-golang"
"github.com/codingsince1985/geo-golang/cached"
"github.com/codingsince1985/geo-golang/data"
"github.com/patrickmn/go-cache"
"github.com/stretchr/testify/assert"

"strings"
"testing"
"time"
)

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

// geocoder is chained with one data geocoder with address -> location data
// the other has location -> address data
// this will exercise the chained fallback handling
var geocoder = cached.Geocoder(
data.Geocoder(
data.AddressToLocation{
"Melbourne VIC": geo.Location{Lat: -37.814107, Lng: 144.96328},
},
data.LocationToAddress{
geo.Location{Lat: -37.816742, Lng: 144.964463}: "Melbourne VIC 3000, Australia",
},
),
geoCache,
var (
addressFixture = geo.Address{
FormattedAddress: "64 Elizabeth Street, Melbourne, Victoria 3000, Australia",
}
locationFixture = geo.Location{
Lat: -37.814107,
Lng: 144.96328,
}
geocoder = cached.Geocoder(
data.Geocoder(
data.AddressToLocation{
addressFixture: locationFixture,
},
data.LocationToAddress{
locationFixture: addressFixture,
},
),
geoCache,
)
)

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

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

func TestReverseGeocodeWithNoResult(t *testing.T) {
_, err := geocoder.ReverseGeocode(-37.816742, 164.964463)
assert.Equal(t, err, geo.ErrNoResult)
addr, err := geocoder.ReverseGeocode(1, 2)
assert.Nil(t, err)
assert.Nil(t, addr)
}

func TestCachedGeocode(t *testing.T) {
mockAddr := geo.Address{
FormattedAddress: "42, Some Street, Austin, Texas",
}
mock1 := data.Geocoder(
data.AddressToLocation{
"Austin,TX": geo.Location{Lat: 1, Lng: 2},
mockAddr: geo.Location{Lat: 1, Lng: 2},
},
data.LocationToAddress{},
)

c := cached.Geocoder(mock1, geoCache)

l, err := c.Geocode("Austin,TX")
l, err := c.Geocode("42, Some Street, Austin, Texas")
assert.NoError(t, err)
assert.Equal(t, geo.Location{Lat: 1, Lng: 2}, l)
assert.Equal(t, geo.Location{Lat: 1, Lng: 2}, *l)

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

_, err = c.Geocode("NOWHERE,TX")
assert.Equal(t, geo.ErrNoResult, err)
addr, err := c.Geocode("NOWHERE,TX")
assert.Nil(t, err)
assert.Nil(t, addr)
}
Loading

0 comments on commit 2a8ea2b

Please sign in to comment.