From 77b30cf8214bfbb6e98398016335d69d9d1c295c Mon Sep 17 00:00:00 2001 From: Lyuben Manolov Date: Sun, 18 Dec 2016 23:42:09 +0100 Subject: [PATCH 01/38] Use structured data for address isntead of string --- http_geocoder.go | 22 +++++++++++++++++++++- locationiq/geocoder.go | 17 ++++++++++++++++- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/http_geocoder.go b/http_geocoder.go index 8af3944..3d46a2d 100644 --- a/http_geocoder.go +++ b/http_geocoder.go @@ -24,6 +24,21 @@ type Location struct { Lat, Lng float64 } +// Address is the structured representation of an address, including its flat representation +type Address struct { + FormattedAddress string + Street string + HouseNumber string + Suburb string + Postcode string + State string + StateDistrict string + County string + Country string + CountryCode string + City string +} + // EndpointBuilder defines functions that build urls for geocode/reverse geocode type EndpointBuilder interface { GeocodeURL(string) string @@ -36,7 +51,12 @@ type ResponseParserFactory func() ResponseParser // ResponseParser defines functions that parse response of geocode/reverse geocode type ResponseParser interface { Location() Location - Address() string + Address() Address +} + +// AddressFormatter returns the flat uniform representation of the address (varies based on service provider) +type AddressFormatter interface { + FormattedAddress() string } // HTTPGeocoder has EndpointBuilder and ResponseParser diff --git a/locationiq/geocoder.go b/locationiq/geocoder.go index 2ff5fa4..6ec138a 100644 --- a/locationiq/geocoder.go +++ b/locationiq/geocoder.go @@ -80,7 +80,22 @@ func (r *geocodeResponse) Location() geo.Location { return l } -func (r *geocodeResponse) Address() string { +func (r *geocodeResponse) Address() geo.Address { + if r.Error != "" { + return geo.Address{} + } + return geo.Address{ + FormattedAddress: r.DisplayName, + Street: r.Addr.Road, + HouseNumber: r.Addr.HouseNumber, + City: r.Addr.City, + Postcode: r.Addr.Postcode, + Country: r.Addr.CountryCode, + CountryCode: r.Addr.CountryCode, + } +} + +func (r *geocodeResponse) FormattedAddress() string { if r.Error != "" { return "" } From b9c57ebd148439d5f1e5b1df370e1efabbe0e81c Mon Sep 17 00:00:00 2001 From: Lyuben Manolov Date: Mon, 19 Dec 2016 12:46:19 +0100 Subject: [PATCH 02/38] Replase string return value with Address --- geocoder.go | 2 +- http_geocoder.go | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/geocoder.go b/geocoder.go index 6dfc52d..2b05ff9 100644 --- a/geocoder.go +++ b/geocoder.go @@ -4,5 +4,5 @@ package geo // Geocoder can look up (lat, long) by address and address by (lat, long) type Geocoder interface { Geocode(address string) (Location, error) - ReverseGeocode(lat, lng float64) (string, error) + ReverseGeocode(lat, lng float64) (Address, error) } diff --git a/http_geocoder.go b/http_geocoder.go index 3d46a2d..71e2c13 100644 --- a/http_geocoder.go +++ b/http_geocoder.go @@ -83,8 +83,8 @@ func (g HTTPGeocoder) Geocode(address string) (Location, error) { } // ReverseGeocode returns address for location -func (g HTTPGeocoder) ReverseGeocode(lat, lng float64) (string, error) { - ch := make(chan string, 1) +func (g HTTPGeocoder) ReverseGeocode(lat, lng float64) (Address, error) { + ch := make(chan Address, 1) go func() { responseParser := g.ResponseParserFactory() response(g.ReverseGeocodeURL(Location{lat, lng}), responseParser) @@ -95,7 +95,7 @@ func (g HTTPGeocoder) ReverseGeocode(lat, lng float64) (string, error) { case address := <-ch: return address, anyError(address) case <-time.After(timeout): - return "", ErrTimeout + return Address{}, ErrTimeout } } @@ -125,6 +125,10 @@ func anyError(v interface{}) error { if v == "" { return ErrNoResult } + case Address: + if v.Postcode == "" { + return ErrNoResult + } } return nil } From e502238066955d8f9a487a5dc98126b665512165 Mon Sep 17 00:00:00 2001 From: Lyuben Manolov Date: Mon, 19 Dec 2016 12:52:59 +0100 Subject: [PATCH 03/38] Fix typo --- locationiq/geocoder.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locationiq/geocoder.go b/locationiq/geocoder.go index 6ec138a..f803b9a 100644 --- a/locationiq/geocoder.go +++ b/locationiq/geocoder.go @@ -90,7 +90,7 @@ func (r *geocodeResponse) Address() geo.Address { HouseNumber: r.Addr.HouseNumber, City: r.Addr.City, Postcode: r.Addr.Postcode, - Country: r.Addr.CountryCode, + Country: r.Addr.Country, CountryCode: r.Addr.CountryCode, } } From b5ee39106d1f91d02c9400ed2938b4d432b54278 Mon Sep 17 00:00:00 2001 From: Lyuben Manolov Date: Mon, 19 Dec 2016 16:15:02 +0100 Subject: [PATCH 04/38] Return pointer instead of value; also return error --- geocoder.go | 4 ++-- http_geocoder.go | 28 ++++++++++++++-------------- locationiq/geocoder.go | 36 +++++++++++++++++++++++++++++++----- 3 files changed, 47 insertions(+), 21 deletions(-) diff --git a/geocoder.go b/geocoder.go index 2b05ff9..f358b79 100644 --- a/geocoder.go +++ b/geocoder.go @@ -3,6 +3,6 @@ package geo // Geocoder can look up (lat, long) by address and address by (lat, long) type Geocoder interface { - Geocode(address string) (Location, error) - ReverseGeocode(lat, lng float64) (Address, error) + Geocode(address string) (*Location, error) + ReverseGeocode(lat, lng float64) (*Address, error) } diff --git a/http_geocoder.go b/http_geocoder.go index 71e2c13..61f5d4f 100644 --- a/http_geocoder.go +++ b/http_geocoder.go @@ -50,8 +50,8 @@ type ResponseParserFactory func() ResponseParser // ResponseParser defines functions that parse response of geocode/reverse geocode type ResponseParser interface { - Location() Location - Address() Address + Location() (*Location, error) + Address() (*Address, error) } // AddressFormatter returns the flat uniform representation of the address (varies based on service provider) @@ -66,36 +66,40 @@ type HTTPGeocoder struct { } // Geocode returns location for address -func (g HTTPGeocoder) Geocode(address string) (Location, error) { - ch := make(chan Location, 1) +func (g HTTPGeocoder) Geocode(address string) (*Location, error) { + ch := make(chan *Location, 1) go func() { responseParser := g.ResponseParserFactory() response(g.GeocodeURL(url.QueryEscape(address)), responseParser) - ch <- responseParser.Location() + // TODO error handling + loc, _ := responseParser.Location() + ch <- loc }() select { case location := <-ch: return location, anyError(location) case <-time.After(timeout): - return Location{}, ErrTimeout + return nil, ErrTimeout } } // ReverseGeocode returns address for location -func (g HTTPGeocoder) ReverseGeocode(lat, lng float64) (Address, error) { - ch := make(chan Address, 1) +func (g HTTPGeocoder) ReverseGeocode(lat, lng float64) (*Address, error) { + ch := make(chan *Address, 1) go func() { responseParser := g.ResponseParserFactory() response(g.ReverseGeocodeURL(Location{lat, lng}), responseParser) - ch <- responseParser.Address() + // TODO error handling + addr, _ := responseParser.Address() + ch <- addr }() select { case address := <-ch: return address, anyError(address) case <-time.After(timeout): - return Address{}, ErrTimeout + return nil, ErrTimeout } } @@ -121,10 +125,6 @@ func anyError(v interface{}) error { if v.Lat == 0 && v.Lng == 0 { return ErrNoResult } - case string: - if v == "" { - return ErrNoResult - } case Address: if v.Postcode == "" { return ErrNoResult diff --git a/locationiq/geocoder.go b/locationiq/geocoder.go index f803b9a..29c07fd 100644 --- a/locationiq/geocoder.go +++ b/locationiq/geocoder.go @@ -70,21 +70,40 @@ func (b baseURL) ReverseGeocodeURL(l geo.Location) string { return string(b) + "reverse.php?key=" + key + fmt.Sprintf("&format=json&lat=%f&lon=%f&zoom=%d", l.Lat, l.Lng, zoom) } +<<<<<<< HEAD func (r *geocodeResponse) Location() geo.Location { l := geo.Location{} // In case of empty response from LocationIQ or any other error we get zero values if r.Lat != "" && r.Lon != "" { l.Lat = geo.ParseFloat(r.Lat) l.Lng = geo.ParseFloat(r.Lon) +======= +func (r *geocodeResponse) Location() (*geo.Location, error) { + if r.Lat == "" || r.Lon == "" { + return nil, fmt.Errorf("empty lat/lon value: %s", r.Error) +>>>>>>> Return pointer instead of value; also return error } - return l + + lat, err := parseFloat(r.Lat) + if err != nil { + return nil, fmt.Errorf("error converting lat to float: %s", err) + } + lon, err := parseFloat(r.Lon) + if err != nil { + return nil, fmt.Errorf("error converting lon to float: %s", err) + } + + return &geo.Location{ + Lat: lat, + Lng: lon, + }, nil } -func (r *geocodeResponse) Address() geo.Address { +func (r *geocodeResponse) Address() (*geo.Address, error) { if r.Error != "" { - return geo.Address{} + return nil, fmt.Errorf("error reverse geocoding: %s", r.Error) } - return geo.Address{ + return &geo.Address{ FormattedAddress: r.DisplayName, Street: r.Addr.Road, HouseNumber: r.Addr.HouseNumber, @@ -92,7 +111,7 @@ func (r *geocodeResponse) Address() geo.Address { Postcode: r.Addr.Postcode, Country: r.Addr.Country, CountryCode: r.Addr.CountryCode, - } + }, nil } func (r *geocodeResponse) FormattedAddress() string { @@ -101,3 +120,10 @@ func (r *geocodeResponse) FormattedAddress() string { } return r.DisplayName } +<<<<<<< HEAD +======= + +func parseFloat(value string) (float64, error) { + return strconv.ParseFloat(value, 64) +} +>>>>>>> Return pointer instead of value; also return error From 615f705b2e2fbfd29da434f3158a1f95ae492bfa Mon Sep 17 00:00:00 2001 From: Lyuben Manolov Date: Tue, 20 Dec 2016 23:07:06 +0100 Subject: [PATCH 05/38] Fix bing to the new interface --- bing/geocoder.go | 35 +++++++++++++++++++++++++++-------- bing/geocoder_test.go | 22 ++++++++++++---------- http_geocoder.go | 5 ----- 3 files changed, 39 insertions(+), 23 deletions(-) diff --git a/bing/geocoder.go b/bing/geocoder.go index 3037c9b..a13851d 100644 --- a/bing/geocoder.go +++ b/bing/geocoder.go @@ -2,6 +2,7 @@ package bing import ( + "errors" "fmt" "github.com/codingsince1985/geo-golang" "strings" @@ -17,9 +18,16 @@ type ( } Address struct { FormattedAddress string + AddressLine string + AdminDistrict string + AdminDistrict2 string + CountryRegion string + Locality string + PostalCode string } } } + ErrorDetails []string } ) @@ -46,17 +54,28 @@ 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, fmt.Errorf("empty result resolving location") } c := r.ResourceSets[0].Resources[0].Point.Coordinates - return geo.Location{c[0], c[1]} + return &geo.Location{c[0], 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 + } + //fmt.Printf("%+v\n\n", r.ResourceSets[0].Resources[0].Address) + 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 } diff --git a/bing/geocoder_test.go b/bing/geocoder_test.go index 119ec34..de081c7 100644 --- a/bing/geocoder_test.go +++ b/bing/geocoder_test.go @@ -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") @@ -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) { @@ -31,9 +31,9 @@ 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) { @@ -41,8 +41,10 @@ func TestReverseGeocodeWithNoResult(t *testing.T) { 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 { diff --git a/http_geocoder.go b/http_geocoder.go index 61f5d4f..f9c9557 100644 --- a/http_geocoder.go +++ b/http_geocoder.go @@ -54,11 +54,6 @@ type ResponseParser interface { Address() (*Address, error) } -// AddressFormatter returns the flat uniform representation of the address (varies based on service provider) -type AddressFormatter interface { - FormattedAddress() string -} - // HTTPGeocoder has EndpointBuilder and ResponseParser type HTTPGeocoder struct { EndpointBuilder From 8045af40d0d38c4442b8ec5b5183f69eff8e529c Mon Sep 17 00:00:00 2001 From: Lyuben Manolov Date: Wed, 21 Dec 2016 16:18:03 +0100 Subject: [PATCH 06/38] Adjust the here client and tests --- here/geocoder.go | 57 +++++++++++++++++++++++++++++++++++-------- here/geocoder_test.go | 8 +++--- 2 files changed, 51 insertions(+), 14 deletions(-) diff --git a/here/geocoder.go b/here/geocoder.go index 952f4bd..a1cfe49 100644 --- a/here/geocoder.go +++ b/here/geocoder.go @@ -17,7 +17,19 @@ type ( Latitude, Longitude float64 } Address struct { - Label string + Label string + Country string + State string + County string + City string + District string + Street string + HouseNumber string + PostalCode string + AdditionalData []struct { + Key string + Value string + } } } } @@ -26,6 +38,12 @@ type ( } ) +const ( + KeyCountryName = "CountryName" + KeyStateName = "StateName" + KeyCountyName = "CountyName" +) + var r = 100 // Geocoder constructs HERE geocoder @@ -62,17 +80,36 @@ func (b baseURL) ReverseGeocodeURL(l geo.Location) string { return b.forReverseGeocode + fmt.Sprintf("&prox=%f,%f,%d", l.Lat, l.Lng, r) } -func (r *geocodeResponse) Location() geo.Location { - if len(r.Response.View) > 0 { - p := r.Response.View[0].Result[0].Location.DisplayPosition - return geo.Location{p.Latitude, p.Longitude} +func (r *geocodeResponse) Location() (*geo.Location, error) { + if len(r.Response.View) == 0 { + return nil, nil } - return geo.Location{} + p := r.Response.View[0].Result[0].Location.DisplayPosition + return &geo.Location{p.Latitude, p.Longitude}, nil } -func (r *geocodeResponse) Address() string { - if len(r.Response.View) > 0 { - return r.Response.View[0].Result[0].Location.Address.Label +func (r *geocodeResponse) Address() (*geo.Address, error) { + if len(r.Response.View) == 0 || len(r.Response.View[0].Result) == 0 { + return nil, nil + } + + res := r.Response.View[0].Result[0].Location.Address + + addr := &geo.Address{ + FormattedAddress: res.Label, + Street: res.Street, + HouseNumber: res.HouseNumber, + City: res.City, + Postcode: res.PostalCode, + CountryCode: res.Country, + } + for _, v := range res.AdditionalData { + switch v.Key { + case KeyCountryName: + addr.Country = v.Value + case KeyStateName: + addr.State = v.Value + } } - return "" + return addr, nil } diff --git a/here/geocoder_test.go b/here/geocoder_test.go index 3a5e5c6..7ab6d6f 100644 --- a/here/geocoder_test.go +++ b/here/geocoder_test.go @@ -21,7 +21,7 @@ func TestGeocode(t *testing.T) { geocoder := here.Geocoder(appID, appCode, 100, ts.URL+"/") location, err := geocoder.Geocode("60 Collins St, Melbourne VIC 3000") assert.NoError(t, err) - 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) { @@ -31,7 +31,7 @@ func TestReverseGeocode(t *testing.T) { geocoder := here.Geocoder(appID, appCode, 100, ts.URL+"/") address, err := geocoder.ReverseGeocode(-37.81375, 144.97176) assert.NoError(t, err) - assert.True(t, strings.HasPrefix(address, "56-64 Collins St")) + assert.True(t, strings.HasPrefix(address.FormattedAddress, "56-64 Collins St")) } func TestReverseGeocodeWithNoResult(t *testing.T) { @@ -39,8 +39,8 @@ func TestReverseGeocodeWithNoResult(t *testing.T) { defer ts.Close() geocoder := here.Geocoder(appID, appCode, 100, ts.URL+"/") - _, err := geocoder.ReverseGeocode(-37.81375, 164.97176) - assert.Equal(t, err, geo.ErrNoResult) + addr, _ := geocoder.ReverseGeocode(-37.81375, 164.97176) + assert.Nil(t, addr) } func testServer(response string) *httptest.Server { From 2a7fa90f9fd431713c4c35061f216ee11f9d3452 Mon Sep 17 00:00:00 2001 From: Lyuben Manolov Date: Thu, 22 Dec 2016 07:47:39 +0100 Subject: [PATCH 07/38] Minor improvement when parsing LocationIQ data --- locationiq/geocoder.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/locationiq/geocoder.go b/locationiq/geocoder.go index 29c07fd..c96209f 100644 --- a/locationiq/geocoder.go +++ b/locationiq/geocoder.go @@ -4,6 +4,7 @@ package locationiq import ( "fmt" "github.com/codingsince1985/geo-golang" + "strings" ) type baseURL string @@ -19,6 +20,7 @@ type locationiqAddress struct { Suburb string `json:"suburb"` City string `json:"city"` County string `json:"county"` + Village string `json:"village"` Country string `json:"country"` CountryCode string `json:"country_code"` Road string `json:"road"` @@ -103,14 +105,22 @@ func (r *geocodeResponse) Address() (*geo.Address, error) { if r.Error != "" { return nil, fmt.Errorf("error reverse geocoding: %s", r.Error) } + var locality string + if r.Addr.City != "" { + locality = r.Addr.City + } else { + locality = r.Addr.Village + } return &geo.Address{ FormattedAddress: r.DisplayName, Street: r.Addr.Road, HouseNumber: r.Addr.HouseNumber, - City: r.Addr.City, + City: locality, Postcode: r.Addr.Postcode, + Suburb: r.Addr.Suburb, + State: r.Addr.State, Country: r.Addr.Country, - CountryCode: r.Addr.CountryCode, + CountryCode: strings.ToUpper(r.Addr.CountryCode), }, nil } From 5558322e0a8e00969f6fe6e9ff566b2c515c2dfc Mon Sep 17 00:00:00 2001 From: Lyuben Manolov Date: Thu, 22 Dec 2016 07:54:18 +0100 Subject: [PATCH 08/38] goimports --- locationiq/geocoder.go | 33 +++++---------------------------- 1 file changed, 5 insertions(+), 28 deletions(-) diff --git a/locationiq/geocoder.go b/locationiq/geocoder.go index c96209f..8c28767 100644 --- a/locationiq/geocoder.go +++ b/locationiq/geocoder.go @@ -3,8 +3,10 @@ package locationiq import ( "fmt" - "github.com/codingsince1985/geo-golang" + "strconv" "strings" + + "github.com/codingsince1985/geo-golang" ) type baseURL string @@ -72,32 +74,14 @@ func (b baseURL) ReverseGeocodeURL(l geo.Location) string { return string(b) + "reverse.php?key=" + key + fmt.Sprintf("&format=json&lat=%f&lon=%f&zoom=%d", l.Lat, l.Lng, zoom) } -<<<<<<< HEAD -func (r *geocodeResponse) Location() geo.Location { - l := geo.Location{} - // In case of empty response from LocationIQ or any other error we get zero values - if r.Lat != "" && r.Lon != "" { - l.Lat = geo.ParseFloat(r.Lat) - l.Lng = geo.ParseFloat(r.Lon) -======= func (r *geocodeResponse) Location() (*geo.Location, error) { if r.Lat == "" || r.Lon == "" { return nil, fmt.Errorf("empty lat/lon value: %s", r.Error) ->>>>>>> Return pointer instead of value; also return error - } - - lat, err := parseFloat(r.Lat) - if err != nil { - return nil, fmt.Errorf("error converting lat to float: %s", err) - } - lon, err := parseFloat(r.Lon) - if err != nil { - return nil, fmt.Errorf("error converting lon to float: %s", err) } return &geo.Location{ - Lat: lat, - Lng: lon, + Lat: geo.ParseFloat(r.Lat), + Lng: geo.ParseFloat(r.Lon), }, nil } @@ -130,10 +114,3 @@ func (r *geocodeResponse) FormattedAddress() string { } return r.DisplayName } -<<<<<<< HEAD -======= - -func parseFloat(value string) (float64, error) { - return strconv.ParseFloat(value, 64) -} ->>>>>>> Return pointer instead of value; also return error From 152ade12e8a62aa429a424aaca04b2db3e732f8e Mon Sep 17 00:00:00 2001 From: Lyuben Manolov Date: Thu, 22 Dec 2016 07:58:49 +0100 Subject: [PATCH 09/38] Adjust the openstreetmap client and tests --- openstreetmap/geocoder.go | 63 ++++++++++++++++++++++++++++------ openstreetmap/geocoder_test.go | 14 ++++---- 2 files changed, 60 insertions(+), 17 deletions(-) diff --git a/openstreetmap/geocoder.go b/openstreetmap/geocoder.go index 4e7fdca..5bca1a2 100644 --- a/openstreetmap/geocoder.go +++ b/openstreetmap/geocoder.go @@ -3,14 +3,32 @@ package openstreetmap import ( "fmt" + "strings" "github.com/codingsince1985/geo-golang" ) type ( - baseURL string + baseURL string geocodeResponse struct { - DisplayName string `json:"display_name"` - Lat, Lon, Error string + DisplayName string `json:"display_name"` + Lat string + Lon string + Error string + Addr osmAddress `json:"address"` + } + + osmAddress struct { + HouseNumber string `json:"house_number"` + Suburb string `json:"suburb"` + City string `json:"city"` + Village string `json:"village"` + County string `json:"county"` + Country string `json:"country"` + CountryCode string `json:"country_code"` + Road string `json:"road"` + State string `json:"state"` + StateDistrict string `json:"state_district"` + Postcode string `json:"postcode"` } ) @@ -33,16 +51,39 @@ func (b baseURL) ReverseGeocodeURL(l geo.Location) string { return string(b) + "reverse?" + fmt.Sprintf("format=json&lat=%f&lon=%f", l.Lat, l.Lng) } -func (r *geocodeResponse) Location() geo.Location { - if r.Error == "" { - return geo.Location{geo.ParseFloat(r.Lat), geo.ParseFloat(r.Lon)} +func (r *geocodeResponse) Location() (*geo.Location, error) { + if r.Error != "" { + return nil, fmt.Errorf("geocoding error: %s", r.Error) + } + if r.Lat == "" && r.Lon == "" { + return nil, nil } - return geo.Location{} + + return &geo.Location{ + Lat: geo.ParseFloat(r.Lat), + Lng: geo.ParseFloat(r.Lon), + }, nil } -func (r *geocodeResponse) Address() string { - if r.Error == "" { - return r.DisplayName +func (r *geocodeResponse) Address() (*geo.Address, error) { + if r.Error != "" { + return nil, fmt.Errorf("reverse geocoding error: %s", r.Error) + } + var locality string + if r.Addr.City != "" { + locality = r.Addr.City + } else { + locality = r.Addr.Village } - return "" + return &geo.Address{ + FormattedAddress: r.DisplayName, + HouseNumber: r.Addr.HouseNumber, + Street: r.Addr.Road, + Postcode: r.Addr.Postcode, + City: locality, + Suburb: r.Addr.Suburb, + State: r.Addr.State, + Country: r.Addr.Country, + CountryCode: strings.ToUpper(r.Addr.CountryCode), + }, nil } diff --git a/openstreetmap/geocoder_test.go b/openstreetmap/geocoder_test.go index d7e00dc..e4dd04d 100644 --- a/openstreetmap/geocoder_test.go +++ b/openstreetmap/geocoder_test.go @@ -17,8 +17,8 @@ func TestGeocode(t *testing.T) { geocoder := openstreetmap.GeocoderWithURL(ts.URL + "/") location, err := geocoder.Geocode("60 Collins St, Melbourne VIC 3000") - assert.NoError(t, err) - assert.Equal(t, geo.Location{Lat: -37.8157915, Lng: 144.9656171}, location) + assert.Nil(t, err) + assert.Equal(t, geo.Location{Lat: -37.8157915, Lng: 144.9656171}, *location) } func TestReverseGeocode(t *testing.T) { @@ -27,8 +27,8 @@ func TestReverseGeocode(t *testing.T) { geocoder := openstreetmap.GeocoderWithURL(ts.URL + "/") address, err := geocoder.ReverseGeocode(-37.8157915, 144.9656171) - assert.NoError(t, err) - assert.True(t, strings.Index(address, "Collins St") > 0) + assert.Nil(t, err) + assert.True(t, strings.Index(address.FormattedAddress, "Collins St") > 0) } func TestReverseGeocodeWithNoResult(t *testing.T) { @@ -36,8 +36,10 @@ func TestReverseGeocodeWithNoResult(t *testing.T) { defer ts.Close() geocoder := openstreetmap.GeocoderWithURL(ts.URL + "/") - _, err := geocoder.ReverseGeocode(-37.8157915, 164.9656171) - assert.Equal(t, err, geo.ErrNoResult) + //geocoder := openstreetmap.Geocoder() + addr, err := geocoder.ReverseGeocode(-37.8157915, 164.9656171) + assert.Nil(t, err) + assert.Nil(t, addr) } func testServer(response string) *httptest.Server { From dc0f98219bf25aa9cb7b16c2f4a6b998370fb9fb Mon Sep 17 00:00:00 2001 From: Lyuben Manolov Date: Thu, 22 Dec 2016 11:50:14 +0100 Subject: [PATCH 10/38] Remove getter func, since the field is exported; improve error handling --- locationiq/geocoder.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/locationiq/geocoder.go b/locationiq/geocoder.go index 8c28767..6790085 100644 --- a/locationiq/geocoder.go +++ b/locationiq/geocoder.go @@ -75,6 +75,9 @@ func (b baseURL) ReverseGeocodeURL(l geo.Location) string { } func (r *geocodeResponse) Location() (*geo.Location, error) { + if r.Error != "" { + return nil, fmt.Errorf("geocoding error: %s", r.Error) + } if r.Lat == "" || r.Lon == "" { return nil, fmt.Errorf("empty lat/lon value: %s", r.Error) } @@ -87,7 +90,7 @@ func (r *geocodeResponse) Location() (*geo.Location, error) { func (r *geocodeResponse) Address() (*geo.Address, error) { if r.Error != "" { - return nil, fmt.Errorf("error reverse geocoding: %s", r.Error) + return nil, fmt.Errorf("reverse geocoding error: %s", r.Error) } var locality string if r.Addr.City != "" { From 063cfcf6109ad5bd96839c965b6a976f10a553be Mon Sep 17 00:00:00 2001 From: Lyuben Manolov Date: Thu, 22 Dec 2016 12:55:44 +0100 Subject: [PATCH 11/38] Adjust test --- locationiq/geocoder_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/locationiq/geocoder_test.go b/locationiq/geocoder_test.go index 347017c..5ee394d 100644 --- a/locationiq/geocoder_test.go +++ b/locationiq/geocoder_test.go @@ -58,7 +58,7 @@ func TestReverseGeocodeYieldsResult(t *testing.T) { if err != nil { t.Errorf("Expected nil error, got %v", err) } - if !strings.HasPrefix(addr, "26, Seidlstraße") { + if !strings.HasPrefix(addr.FormattedAddress, "26, Seidlstraße") { t.Errorf("Expected address string starting with %s, got string: %s", "26, Seidlstraße", addr) } } @@ -73,8 +73,8 @@ func TestReverseGeocodeYieldsNoResult(t *testing.T) { if err == nil { t.Error("Expected error, got nil") } - if addr != "" { - t.Errorf("Expected empty string as address, got: %s", addr) + if addr != nil { + t.Errorf("Expected nil as address, got: %s", addr) } } From e8b7e4af2b4c46f1e6d2c1a6d9f820d7cc159fd6 Mon Sep 17 00:00:00 2001 From: Lyuben Manolov Date: Fri, 23 Dec 2016 00:14:39 +0100 Subject: [PATCH 12/38] Adjust opencage client and unit tests --- opencage/geocoder.go | 79 ++++++++++++++++++++++++++++++++------- opencage/geocoder_test.go | 16 ++++---- 2 files changed, 75 insertions(+), 20 deletions(-) diff --git a/opencage/geocoder.go b/opencage/geocoder.go index 0017b3f..406d7ad 100644 --- a/opencage/geocoder.go +++ b/opencage/geocoder.go @@ -3,16 +3,38 @@ package opencage import ( "fmt" + "strings" + "github.com/codingsince1985/geo-golang" ) type ( - baseURL string + baseURL string + geocodeResponse struct { - Results []struct { - Formatted string - Geometry geo.Location - } + Results []struct { + Formatted string + Geometry geo.Location + Components osmAddress + } + Status struct { + Code int + Message string + } + } + + osmAddress struct { + HouseNumber string `json:"house_number"` + Suburb string `json:"suburb"` + City string `json:"city"` + Village string `json:"village"` + County string `json:"county"` + Country string `json:"country"` + CountryCode string `json:"country_code"` + Road string `json:"road"` + State string `json:"state"` + StateDistrict string `json:"state_district"` + Postcode string `json:"postcode"` } ) @@ -37,16 +59,47 @@ func (b baseURL) ReverseGeocodeURL(l geo.Location) string { return string(b) + fmt.Sprintf("%+f,%+f", l.Lat, l.Lng) } -func (r *geocodeResponse) Location() geo.Location { - if len(r.Results) > 0 { - return r.Results[0].Geometry +func (r *geocodeResponse) Location() (*geo.Location, error) { + if r.Status.Code >= 400 { + return nil, fmt.Errorf("geocoding error: %s", r.Status.Message) } - return geo.Location{} + if len(r.Results) == 0 { + return nil, nil + } + + return &geo.Location{ + Lat: r.Results[0].Geometry.Lat, + Lng: r.Results[0].Geometry.Lng, + }, nil } -func (r *geocodeResponse) Address() string { - if len(r.Results) > 0 { - return r.Results[0].Formatted +func (r *geocodeResponse) Address() (*geo.Address, error) { + if r.Status.Code >= 400 { + return nil, fmt.Errorf("geocoding error: %s", r.Status.Message) + } + if len(r.Results) == 0 { + return nil, nil + } + + addr := r.Results[0].Components + var locality string + if addr.City != "" { + locality = addr.City + } else { + locality = addr.Village } - return "" + + return &geo.Address{ + FormattedAddress: r.Results[0].Formatted, + HouseNumber: addr.HouseNumber, + Street: addr.Road, + Suburb: addr.Suburb, + Postcode: addr.Postcode, + City: locality, + CountryCode: strings.ToUpper(addr.CountryCode), + Country: addr.Country, + County: addr.County, + State: addr.State, + StateDistrict: addr.StateDistrict, + }, nil } diff --git a/opencage/geocoder_test.go b/opencage/geocoder_test.go index 8377e54..41674fb 100644 --- a/opencage/geocoder_test.go +++ b/opencage/geocoder_test.go @@ -1,14 +1,14 @@ package opencage_test import ( - "github.com/codingsince1985/geo-golang" - "github.com/codingsince1985/geo-golang/opencage" - "github.com/stretchr/testify/assert" "net/http" "net/http/httptest" "os" "strings" "testing" + + "github.com/codingsince1985/geo-golang/opencage" + "github.com/stretchr/testify/assert" ) var key = os.Getenv("OPENCAGE_API_KEY") @@ -23,7 +23,7 @@ func TestGeocode(t *testing.T) { geocoder := opencage.Geocoder(key, ts.URL+"/") location, err := geocoder.Geocode("60 Collins St, Melbourne VIC 3000") - assert.NoError(t, err) + assert.Nil(t, err) assert.InDelta(t, -37.8154176, location.Lat, locDelta) assert.InDelta(t, 144.9665563, location.Lng, locDelta) } @@ -35,7 +35,7 @@ func TestReverseGeocode(t *testing.T) { geocoder := opencage.Geocoder(key, ts.URL+"/") address, err := geocoder.ReverseGeocode(-37.8154176, 144.9665563) assert.NoError(t, err) - assert.True(t, strings.Index(address, "Collins St") > 0) + assert.True(t, strings.Index(address.FormattedAddress, "Collins St") > 0) } func TestReverseGeocodeWithNoResult(t *testing.T) { @@ -43,8 +43,10 @@ func TestReverseGeocodeWithNoResult(t *testing.T) { defer ts.Close() geocoder := opencage.Geocoder(key, ts.URL+"/") - _, err := geocoder.ReverseGeocode(-37.8154176, 164.9665563) - assert.Equal(t, err, geo.ErrNoResult) + //geocoder := opencage.Geocoder(key) + address, err := geocoder.ReverseGeocode(-37.8154176, 164.9665563) + assert.Nil(t, err) + assert.Nil(t, address) } func testServer(response string) *httptest.Server { From 24c94b857b8c9eb84a030895bfc2d094a0132897 Mon Sep 17 00:00:00 2001 From: Lyuben Manolov Date: Mon, 9 Jan 2017 09:59:31 +0100 Subject: [PATCH 13/38] Adjust MapQuest Open to the new interface --- mapquest/open/geocoder.go | 51 ++++++++++++++++++++++++++++------ mapquest/open/geocoder_test.go | 10 ++++--- opencage/geocoder.go | 40 +++++++++++++------------- 3 files changed, 69 insertions(+), 32 deletions(-) diff --git a/mapquest/open/geocoder.go b/mapquest/open/geocoder.go index 566755d..14f4f42 100644 --- a/mapquest/open/geocoder.go +++ b/mapquest/open/geocoder.go @@ -8,12 +8,22 @@ import ( ) type ( - baseURL string + baseURL string + geocodeResponse struct { Results []struct { Locations []struct { - LatLng geo.Location - Street, AdminArea5, AdminArea3, AdminArea1 string + LatLng struct { + Lat float64 + Lng float64 + } + PostalCode string + Street string + AdminArea6 string // neighbourhood + AdminArea5 string // city + AdminArea4 string // county + AdminArea3 string // state + AdminArea1 string // country (ISO 3166-1 alpha-2 code) } } } @@ -43,12 +53,37 @@ func (b baseURL) ReverseGeocodeURL(l geo.Location) string { return strings.Replace(string(b), "*", "reverse", 1) + fmt.Sprintf("%f,%f", l.Lat, l.Lng) } -func (r *geocodeResponse) Location() geo.Location { return r.Results[0].Locations[0].LatLng } +func (r *geocodeResponse) Location() (*geo.Location, error) { + if len(r.Results) == 0 || len(r.Results[0].Locations) == 0 { + return nil, nil + } + + loc := r.Results[0].Locations[0].LatLng + return &geo.Location{ + Lat: loc.Lat, + Lng: loc.Lng, + }, nil +} + +func (r *geocodeResponse) Address() (*geo.Address, error) { + if len(r.Results) == 0 || len(r.Results[0].Locations) == 0 { + return nil, nil + } -func (r *geocodeResponse) Address() string { p := r.Results[0].Locations[0] - if p.AdminArea1 != "" { - return p.Street + ", " + p.AdminArea5 + ", " + p.AdminArea3 + ", " + p.AdminArea1 + if p.Street == "" || p.AdminArea5 == "" { + return nil, nil } - return "" + + formattedAddress := p.Street + ", " + p.PostalCode + ", " + p.AdminArea5 + ", " + p.AdminArea3 + ", " + p.AdminArea1 + return &geo.Address{ + FormattedAddress: formattedAddress, + Street: p.Street, + Suburb: p.AdminArea6, + Postcode: p.PostalCode, + City: p.AdminArea5, + County: p.AdminArea4, + State: p.AdminArea3, + CountryCode: p.AdminArea1, + }, nil } diff --git a/mapquest/open/geocoder_test.go b/mapquest/open/geocoder_test.go index ebd4a41..fa76702 100644 --- a/mapquest/open/geocoder_test.go +++ b/mapquest/open/geocoder_test.go @@ -20,7 +20,7 @@ func TestGeocode(t *testing.T) { geocoder := open.Geocoder(key, ts.URL+"/") location, err := geocoder.Geocode("60 Collins St, Melbourne VIC 3000") assert.NoError(t, err) - assert.Equal(t, geo.Location{Lat: -37.813743, Lng: 144.971745}, location) + assert.Equal(t, geo.Location{Lat: -37.813743, Lng: 144.971745}, *location) } func TestReverseGeocode(t *testing.T) { @@ -30,7 +30,7 @@ func TestReverseGeocode(t *testing.T) { geocoder := open.Geocoder(key, ts.URL+"/") address, err := geocoder.ReverseGeocode(-37.813743, 144.971745) assert.NoError(t, err) - assert.True(t, strings.HasPrefix(address, "Exhibition Street")) + assert.True(t, strings.HasPrefix(address.FormattedAddress, "Exhibition Street")) } func TestReverseGeocodeWithNoResult(t *testing.T) { @@ -38,8 +38,10 @@ func TestReverseGeocodeWithNoResult(t *testing.T) { defer ts.Close() geocoder := open.Geocoder(key, ts.URL+"/") - _, err := geocoder.ReverseGeocode(-37.813743, 164.971745) - assert.Equal(t, err, geo.ErrNoResult) + //geocoder := open.Geocoder(key) + addr, err := geocoder.ReverseGeocode(-37.813743, 164.971745) + assert.Equal(t, err, nil) + assert.Nil(t, addr) } func testServer(response string) *httptest.Server { diff --git a/opencage/geocoder.go b/opencage/geocoder.go index 406d7ad..8a41529 100644 --- a/opencage/geocoder.go +++ b/opencage/geocoder.go @@ -12,29 +12,29 @@ type ( baseURL string geocodeResponse struct { - Results []struct { - Formatted string - Geometry geo.Location - Components osmAddress - } - Status struct { - Code int - Message string - } + Results []struct { + Formatted string + Geometry geo.Location + Components osmAddress + } + Status struct { + Code int + Message string + } } osmAddress struct { - HouseNumber string `json:"house_number"` - Suburb string `json:"suburb"` - City string `json:"city"` - Village string `json:"village"` - County string `json:"county"` - Country string `json:"country"` - CountryCode string `json:"country_code"` - Road string `json:"road"` - State string `json:"state"` - StateDistrict string `json:"state_district"` - Postcode string `json:"postcode"` + HouseNumber string `json:"house_number"` + Suburb string `json:"suburb"` + City string `json:"city"` + Village string `json:"village"` + County string `json:"county"` + Country string `json:"country"` + CountryCode string `json:"country_code"` + Road string `json:"road"` + State string `json:"state"` + StateDistrict string `json:"state_district"` + Postcode string `json:"postcode"` } ) From 1b8a607b6d59e74fa3ae435932879b707153167b Mon Sep 17 00:00:00 2001 From: Lyuben Manolov Date: Mon, 9 Jan 2017 15:00:36 +0100 Subject: [PATCH 14/38] Fix for variable street/city naming in the response from LocationIQ --- locationiq/geocoder.go | 52 ++++++++++++++++++++++++++++++------- locationiq/geocoder_test.go | 8 +++--- 2 files changed, 47 insertions(+), 13 deletions(-) diff --git a/locationiq/geocoder.go b/locationiq/geocoder.go index 6790085..ba75df1 100644 --- a/locationiq/geocoder.go +++ b/locationiq/geocoder.go @@ -19,13 +19,18 @@ type geocodeResponse struct { type locationiqAddress struct { HouseNumber string `json:"house_number"` + Road string `json:"road"` + Pedestrian string `json:"pedestrian"` + Cycleway string `json:"cycleway"` + Highway string `json:"highway"` + Path string `json:"path"` Suburb string `json:"suburb"` City string `json:"city"` - County string `json:"county"` + Town string `json:"town"` Village string `json:"village"` + County string `json:"county"` Country string `json:"country"` CountryCode string `json:"country_code"` - Road string `json:"road"` State string `json:"state"` StateDistrict string `json:"state_district"` Postcode string `json:"postcode"` @@ -92,17 +97,12 @@ func (r *geocodeResponse) Address() (*geo.Address, error) { if r.Error != "" { return nil, fmt.Errorf("reverse geocoding error: %s", r.Error) } - var locality string - if r.Addr.City != "" { - locality = r.Addr.City - } else { - locality = r.Addr.Village - } + return &geo.Address{ FormattedAddress: r.DisplayName, - Street: r.Addr.Road, + Street: extractStreet(r.Addr), HouseNumber: r.Addr.HouseNumber, - City: locality, + City: extractLocality(r.Addr), Postcode: r.Addr.Postcode, Suburb: r.Addr.Suburb, State: r.Addr.State, @@ -117,3 +117,35 @@ func (r *geocodeResponse) FormattedAddress() string { } return r.DisplayName } + +func extractLocality(a locationiqAddress) string { + var locality string + + if a.City != "" { + locality = a.City + } else if a.Town != "" { + locality = a.Town + } else if a.Village != "" { + locality = a.Village + } + + return locality +} + +func extractStreet(a locationiqAddress) string { + var street string + + if a.Road != "" { + street = a.Road + } else if a.Pedestrian != "" { + street = a.Pedestrian + } else if a.Path != "" { + street = a.Path + } else if a.Cycleway != "" { + street = a.Cycleway + } else if a.Highway != "" { + street = a.Highway + } + + return street +} diff --git a/locationiq/geocoder_test.go b/locationiq/geocoder_test.go index 5ee394d..8c35634 100644 --- a/locationiq/geocoder_test.go +++ b/locationiq/geocoder_test.go @@ -30,6 +30,7 @@ func TestGeocodeYieldsResult(t *testing.T) { } } +/* TODO uncomment as soon as the propagated error from client is returned func TestGeocodeYieldsNoResult(t *testing.T) { ts := testServer("[]") defer ts.Close() @@ -38,7 +39,7 @@ func TestGeocodeYieldsNoResult(t *testing.T) { l, err := gc.Geocode("Seidlstraße 26, 80335 München") if err == nil { - t.Error("Got nil error") + t.Fatal("Got nil error") } if l.Lat != 0 { t.Errorf("Expected latitude: %d, got: %f", 0, l.Lat) @@ -47,7 +48,7 @@ func TestGeocodeYieldsNoResult(t *testing.T) { t.Errorf("Expected longitude: %d, got: %f", 0, l.Lat) } } - +*/ func TestReverseGeocodeYieldsResult(t *testing.T) { ts := testServer(responseForReverse) defer ts.Close() @@ -63,6 +64,7 @@ func TestReverseGeocodeYieldsResult(t *testing.T) { } } +/* TODO uncomment as soon as the propagated error from client is returned func TestReverseGeocodeYieldsNoResult(t *testing.T) { ts := testServer(errorResponse) defer ts.Close() @@ -77,7 +79,7 @@ func TestReverseGeocodeYieldsNoResult(t *testing.T) { t.Errorf("Expected nil as address, got: %s", addr) } } - +*/ func testServer(response string) *httptest.Server { return httptest.NewServer(http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { resp.Write([]byte(response)) From a1c6cf2ce682c5021aa0906102b2b67e49cd5d87 Mon Sep 17 00:00:00 2001 From: Lyuben Manolov Date: Tue, 10 Jan 2017 07:53:47 +0100 Subject: [PATCH 15/38] Minor refactoring of unit test --- mapquest/open/geocoder_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mapquest/open/geocoder_test.go b/mapquest/open/geocoder_test.go index fa76702..9459fe6 100644 --- a/mapquest/open/geocoder_test.go +++ b/mapquest/open/geocoder_test.go @@ -40,7 +40,7 @@ func TestReverseGeocodeWithNoResult(t *testing.T) { geocoder := open.Geocoder(key, ts.URL+"/") //geocoder := open.Geocoder(key) addr, err := geocoder.ReverseGeocode(-37.813743, 164.971745) - assert.Equal(t, err, nil) + assert.Nil(t, err) assert.Nil(t, addr) } From fb73898a7f7b3c9e7ce3c631e7f619f2cc767a09 Mon Sep 17 00:00:00 2001 From: Lyuben Manolov Date: Tue, 10 Jan 2017 09:19:50 +0100 Subject: [PATCH 16/38] Introduce package osm for common OpenStreetMap primitives --- osm/osm.go | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 osm/osm.go diff --git a/osm/osm.go b/osm/osm.go new file mode 100644 index 0000000..1235287 --- /dev/null +++ b/osm/osm.go @@ -0,0 +1,57 @@ +// Package osm provides common types for OpneStreetMap used by various providers +// and some helper functions to reduce code repetition across specific client implementations. +package osm + +// OSMAddress contains address fields specific to OpenStreetMap +type OSMAddress struct { + HouseNumber string `json:"house_number"` + Road string `json:"road"` + Pedestrian string `json:"pedestrian"` + Cycleway string `json:"cycleway"` + Highway string `json:"highway"` + Path string `json:"path"` + Suburb string `json:"suburb"` + City string `json:"city"` + Town string `json:"town"` + Village string `json:"village"` + County string `json:"county"` + Country string `json:"country"` + CountryCode string `json:"country_code"` + State string `json:"state"` + StateDistrict string `json:"state_district"` + Postcode string `json:"postcode"` +} + +// Locality checks different fields for the locality name +func (a OSMAddress) Locality() string { + var locality string + + if a.City != "" { + locality = a.City + } else if a.Town != "" { + locality = a.Town + } else if a.Village != "" { + locality = a.Village + } + + return locality +} + +// Street checks different fields for the street name +func (a OSMAddress) Street() string { + var street string + + if a.Road != "" { + street = a.Road + } else if a.Pedestrian != "" { + street = a.Pedestrian + } else if a.Path != "" { + street = a.Path + } else if a.Cycleway != "" { + street = a.Cycleway + } else if a.Highway != "" { + street = a.Highway + } + + return street +} From e49611daf13b8615b97ed66323e1ec243e2cb554 Mon Sep 17 00:00:00 2001 From: Lyuben Manolov Date: Tue, 10 Jan 2017 15:26:00 +0100 Subject: [PATCH 17/38] Refactor execution of HTTP request. Use context.WithTimeout instead. Add error handling on various levels. --- http_geocoder.go | 130 ++++++++++++++++++++++++++++++----------------- 1 file changed, 84 insertions(+), 46 deletions(-) diff --git a/http_geocoder.go b/http_geocoder.go index f9c9557..332fefd 100644 --- a/http_geocoder.go +++ b/http_geocoder.go @@ -1,6 +1,7 @@ package geo import ( + "context" "encoding/json" "errors" "io/ioutil" @@ -11,20 +12,20 @@ import ( "time" ) -var timeout = time.Second * 8 - -// ErrTimeout occurs when no response returned within timeoutInSeconds -var ErrTimeout = errors.New("TIMEOUT") +// Default timeout for the request execution +const DefaultTimeout = time.Second * 8 // ErrNoResult occurs when no result returned var ErrNoResult = errors.New("NO_RESULT") +var ErrTimeout = errors.New("TIMEOUT") // Location is the output of Geocode type Location struct { Lat, Lng float64 } -// Address is the structured representation of an address, including its flat representation +// Address is returned by ReverseGeocode. +// This is a structured representation of an address, including its flat representation type Address struct { FormattedAddress string Street string @@ -62,69 +63,106 @@ type HTTPGeocoder struct { // Geocode returns location for address func (g HTTPGeocoder) Geocode(address string) (*Location, error) { - ch := make(chan *Location, 1) + responseParser := g.ResponseParserFactory() + + ctx, cancel := context.WithTimeout(context.TODO(), DefaultTimeout) + defer cancel() + + ch := make(chan struct { + l *Location + e error + }, 1) go func() { - responseParser := g.ResponseParserFactory() - response(g.GeocodeURL(url.QueryEscape(address)), responseParser) - // TODO error handling - loc, _ := responseParser.Location() - ch <- loc + if err := response(ctx, g.GeocodeURL(url.QueryEscape(address)), responseParser); err != nil { + ch <- struct { + l *Location + e error + }{ + l: nil, + e: err, + } + } + + loc, err := responseParser.Location() + ch <- struct { + l *Location + e error + }{ + l: loc, + e: err, + } }() select { - case location := <-ch: - return location, anyError(location) - case <-time.After(timeout): + case <-ctx.Done(): return nil, ErrTimeout + case res := <-ch: + return res.l, res.e } } // ReverseGeocode returns address for location func (g HTTPGeocoder) ReverseGeocode(lat, lng float64) (*Address, error) { - ch := make(chan *Address, 1) + responseParser := g.ResponseParserFactory() + + ctx, cancel := context.WithTimeout(context.TODO(), DefaultTimeout) + defer cancel() + + ch := make(chan struct { + a *Address + e error + }, 1) go func() { - responseParser := g.ResponseParserFactory() - response(g.ReverseGeocodeURL(Location{lat, lng}), responseParser) - // TODO error handling - addr, _ := responseParser.Address() - ch <- addr + if err := response(ctx, g.ReverseGeocodeURL(Location{lat, lng}), responseParser); err != nil { + ch <- struct { + a *Address + e error + }{ + a: nil, + e: err, + } + } + + addr, err := responseParser.Address() + ch <- struct { + a *Address + e error + }{ + a: addr, + e: err, + } }() select { - case address := <-ch: - return address, anyError(address) - case <-time.After(timeout): + case <-ctx.Done(): return nil, ErrTimeout + case res := <-ch: + return res.a, res.e } } // Response gets response from url -func response(url string, obj ResponseParser) { - if req, err := http.NewRequest("GET", url, nil); err == nil { - if resp, err := (&http.Client{}).Do(req); err == nil { - defer resp.Body.Close() - if data, err := ioutil.ReadAll(resp.Body); err == nil { - // TODO: don't swallow json unmarshal errors - // currently it just treats an empty response as a ErrNoResult which - // is fine for now but we should have some logging or something to indicate - // failed json unmarshal - json.Unmarshal([]byte(strings.Trim(string(data), " []")), obj) - } - } +func response(ctx context.Context, url string, obj ResponseParser) error { + req, err := http.NewRequest(http.MethodGet, url, nil) + if err != nil { + return err } -} -func anyError(v interface{}) error { - switch v := v.(type) { - case Location: - if v.Lat == 0 && v.Lng == 0 { - return ErrNoResult - } - case Address: - if v.Postcode == "" { - return ErrNoResult - } + resp, err := http.DefaultClient.Do(req) + if err != nil { + return err + } + + defer resp.Body.Close() + data, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err } + + if err := json.Unmarshal([]byte(strings.Trim(string(data), " []")), obj); err != nil { + return err + } + return nil } From 9a22788731e19a678b3225956107a489cd49d0f9 Mon Sep 17 00:00:00 2001 From: Lyuben Manolov Date: Tue, 10 Jan 2017 16:32:45 +0100 Subject: [PATCH 18/38] Avoid empty response body leading to json unmarshaling error --- http_geocoder.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/http_geocoder.go b/http_geocoder.go index 332fefd..2692480 100644 --- a/http_geocoder.go +++ b/http_geocoder.go @@ -159,7 +159,11 @@ func response(ctx context.Context, url string, obj ResponseParser) error { return err } - if err := json.Unmarshal([]byte(strings.Trim(string(data), " []")), obj); err != nil { + body := strings.Trim(string(data), " []") + if body == "" { + return nil + } + if err := json.Unmarshal([]byte(body), obj); err != nil { return err } From 259943b2b1e2e020ca00742dc037dea1d64c199d Mon Sep 17 00:00:00 2001 From: Lyuben Manolov Date: Tue, 10 Jan 2017 17:22:05 +0100 Subject: [PATCH 19/38] Rename OSM address struct --- osm/osm.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osm/osm.go b/osm/osm.go index 1235287..d0c9b8b 100644 --- a/osm/osm.go +++ b/osm/osm.go @@ -2,8 +2,8 @@ // and some helper functions to reduce code repetition across specific client implementations. package osm -// OSMAddress contains address fields specific to OpenStreetMap -type OSMAddress struct { +// Address contains address fields specific to OpenStreetMap +type Address struct { HouseNumber string `json:"house_number"` Road string `json:"road"` Pedestrian string `json:"pedestrian"` @@ -23,7 +23,7 @@ type OSMAddress struct { } // Locality checks different fields for the locality name -func (a OSMAddress) Locality() string { +func (a Address) Locality() string { var locality string if a.City != "" { @@ -38,7 +38,7 @@ func (a OSMAddress) Locality() string { } // Street checks different fields for the street name -func (a OSMAddress) Street() string { +func (a Address) Street() string { var street string if a.Road != "" { From c3a5e86a60a1df5d2d42bf7e280dd41bade237bd Mon Sep 17 00:00:00 2001 From: Lyuben Manolov Date: Tue, 10 Jan 2017 17:22:49 +0100 Subject: [PATCH 20/38] Adjust MapQuest Nominatim to the new interface --- mapquest/nominatim/geocoder.go | 45 +++++++++++++++++++++++------ mapquest/nominatim/geocoder_test.go | 12 ++++---- 2 files changed, 43 insertions(+), 14 deletions(-) diff --git a/mapquest/nominatim/geocoder.go b/mapquest/nominatim/geocoder.go index 9bf062c..f76b51a 100644 --- a/mapquest/nominatim/geocoder.go +++ b/mapquest/nominatim/geocoder.go @@ -3,14 +3,20 @@ package nominatim import ( "fmt" + "strconv" + "strings" + "github.com/codingsince1985/geo-golang" + "github.com/codingsince1985/geo-golang/osm" ) type ( - baseURL string + baseURL string + geocodeResponse struct { DisplayName string `json:"display_name"` Lat, Lon, Error string + Addr osm.Address `json:"address"` } ) @@ -40,16 +46,37 @@ func (b baseURL) ReverseGeocodeURL(l geo.Location) string { return string(b) + "reverse.php?key=" + key + fmt.Sprintf("&format=json&lat=%f&lon=%f", l.Lat, l.Lng) } -func (r *geocodeResponse) Location() geo.Location { - if r.Error == "" { - return geo.Location{geo.ParseFloat(r.Lat), geo.ParseFloat(r.Lon)} +func (r *geocodeResponse) Location() (*geo.Location, error) { + if r.Error != "" { + return nil, fmt.Errorf("geocode error: %s", r.Error) } - return geo.Location{} + + return &geo.Location{ + Lat: parseFloat(r.Lat), + Lng: parseFloat(r.Lon), + }, nil } -func (r *geocodeResponse) Address() string { - if r.Error == "" { - return r.DisplayName +func (r *geocodeResponse) Address() (*geo.Address, error) { + if r.Error != "" { + return nil, fmt.Errorf("reverse geocode error: %s", r.Error) } - return "" + + return &geo.Address{ + FormattedAddress: r.DisplayName, + HouseNumber: r.Addr.HouseNumber, + Street: r.Addr.Street(), + Suburb: r.Addr.Suburb, + City: r.Addr.Locality(), + State: r.Addr.State, + County: r.Addr.County, + Postcode: r.Addr.Postcode, + Country: r.Addr.Country, + CountryCode: strings.ToUpper(r.Addr.CountryCode), + }, nil +} + +func parseFloat(value string) float64 { + f, _ := strconv.ParseFloat(value, 64) + return f } diff --git a/mapquest/nominatim/geocoder_test.go b/mapquest/nominatim/geocoder_test.go index bcde31c..372f9c5 100644 --- a/mapquest/nominatim/geocoder_test.go +++ b/mapquest/nominatim/geocoder_test.go @@ -20,7 +20,7 @@ func TestGeocode(t *testing.T) { geocoder := nominatim.Geocoder(key, ts.URL+"/") location, err := geocoder.Geocode("60 Collins St, Melbourne VIC 3000") assert.NoError(t, err) - assert.Equal(t, geo.Location{Lat: -37.8137433689794, Lng: 144.971745104488}, location) + assert.Equal(t, geo.Location{Lat: -37.8137433689794, Lng: 144.971745104488}, *location) } func TestReverseGeocode(t *testing.T) { @@ -28,9 +28,9 @@ func TestReverseGeocode(t *testing.T) { defer ts.Close() geocoder := nominatim.Geocoder(key, ts.URL+"/") - address, err := geocoder.ReverseGeocode(-37.8137433689794, 144.971745104488) + addr, err := geocoder.ReverseGeocode(-37.8137433689794, 144.971745104488) assert.NoError(t, err) - assert.True(t, strings.HasPrefix(address, "Reserve Bank of Australia")) + assert.True(t, strings.HasPrefix(addr.FormattedAddress, "Reserve Bank of Australia")) } func TestReverseGeocodeWithNoResult(t *testing.T) { @@ -38,8 +38,10 @@ func TestReverseGeocodeWithNoResult(t *testing.T) { defer ts.Close() geocoder := nominatim.Geocoder(key, ts.URL+"/") - _, err := geocoder.ReverseGeocode(-37.8137433689794, 164.971745104488) - assert.Equal(t, err, geo.ErrNoResult) + //geocoder := nominatim.Geocoder(key) + addr, err := geocoder.ReverseGeocode(-37.8137433689794, 164.971745104488) + assert.NotNil(t, err) + assert.Nil(t, addr) } func testServer(response string) *httptest.Server { From 22f00930a8f29c94d3798b4710ed14b7715f6a3b Mon Sep 17 00:00:00 2001 From: Lyuben Manolov Date: Tue, 10 Jan 2017 17:23:48 +0100 Subject: [PATCH 21/38] Use osm.Address. Fix tests --- locationiq/geocoder.go | 63 +++++-------------------------------- locationiq/geocoder_test.go | 18 +++++------ 2 files changed, 14 insertions(+), 67 deletions(-) diff --git a/locationiq/geocoder.go b/locationiq/geocoder.go index ba75df1..f9b29d1 100644 --- a/locationiq/geocoder.go +++ b/locationiq/geocoder.go @@ -3,10 +3,10 @@ package locationiq import ( "fmt" - "strconv" "strings" "github.com/codingsince1985/geo-golang" + "github.com/codingsince1985/geo-golang/osm" ) type baseURL string @@ -14,26 +14,7 @@ type baseURL string type geocodeResponse struct { DisplayName string `json:"display_name"` Lat, Lon, Error string - Addr locationiqAddress `json:"address"` -} - -type locationiqAddress struct { - HouseNumber string `json:"house_number"` - Road string `json:"road"` - Pedestrian string `json:"pedestrian"` - Cycleway string `json:"cycleway"` - Highway string `json:"highway"` - Path string `json:"path"` - Suburb string `json:"suburb"` - City string `json:"city"` - Town string `json:"town"` - Village string `json:"village"` - County string `json:"county"` - Country string `json:"country"` - CountryCode string `json:"country_code"` - State string `json:"state"` - StateDistrict string `json:"state_district"` - Postcode string `json:"postcode"` + Addr osm.Address `json:"address"` } const ( @@ -83,8 +64,10 @@ func (r *geocodeResponse) Location() (*geo.Location, error) { if r.Error != "" { return nil, fmt.Errorf("geocoding error: %s", r.Error) } + + // no result if r.Lat == "" || r.Lon == "" { - return nil, fmt.Errorf("empty lat/lon value: %s", r.Error) + return nil, nil } return &geo.Location{ @@ -100,9 +83,9 @@ func (r *geocodeResponse) Address() (*geo.Address, error) { return &geo.Address{ FormattedAddress: r.DisplayName, - Street: extractStreet(r.Addr), + Street: r.Addr.Street(), HouseNumber: r.Addr.HouseNumber, - City: extractLocality(r.Addr), + City: r.Addr.Locality(), Postcode: r.Addr.Postcode, Suburb: r.Addr.Suburb, State: r.Addr.State, @@ -117,35 +100,3 @@ func (r *geocodeResponse) FormattedAddress() string { } return r.DisplayName } - -func extractLocality(a locationiqAddress) string { - var locality string - - if a.City != "" { - locality = a.City - } else if a.Town != "" { - locality = a.Town - } else if a.Village != "" { - locality = a.Village - } - - return locality -} - -func extractStreet(a locationiqAddress) string { - var street string - - if a.Road != "" { - street = a.Road - } else if a.Pedestrian != "" { - street = a.Pedestrian - } else if a.Path != "" { - street = a.Path - } else if a.Cycleway != "" { - street = a.Cycleway - } else if a.Highway != "" { - street = a.Highway - } - - return street -} diff --git a/locationiq/geocoder_test.go b/locationiq/geocoder_test.go index 8c35634..7f5b04c 100644 --- a/locationiq/geocoder_test.go +++ b/locationiq/geocoder_test.go @@ -30,7 +30,6 @@ func TestGeocodeYieldsResult(t *testing.T) { } } -/* TODO uncomment as soon as the propagated error from client is returned func TestGeocodeYieldsNoResult(t *testing.T) { ts := testServer("[]") defer ts.Close() @@ -38,17 +37,15 @@ func TestGeocodeYieldsNoResult(t *testing.T) { gc := Geocoder("foobar", 18, ts.URL+"/") l, err := gc.Geocode("Seidlstraße 26, 80335 München") - if err == nil { - t.Fatal("Got nil error") - } - if l.Lat != 0 { - t.Errorf("Expected latitude: %d, got: %f", 0, l.Lat) + if l != nil { + t.Errorf("Expected nil, got %#v", l) } - if l.Lng != 0 { - t.Errorf("Expected longitude: %d, got: %f", 0, l.Lat) + + if err != nil { + t.Errorf("Expected nil error, got %v", err) } } -*/ + func TestReverseGeocodeYieldsResult(t *testing.T) { ts := testServer(responseForReverse) defer ts.Close() @@ -64,7 +61,6 @@ func TestReverseGeocodeYieldsResult(t *testing.T) { } } -/* TODO uncomment as soon as the propagated error from client is returned func TestReverseGeocodeYieldsNoResult(t *testing.T) { ts := testServer(errorResponse) defer ts.Close() @@ -79,7 +75,7 @@ func TestReverseGeocodeYieldsNoResult(t *testing.T) { t.Errorf("Expected nil as address, got: %s", addr) } } -*/ + func testServer(response string) *httptest.Server { return httptest.NewServer(http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { resp.Write([]byte(response)) From 2733a3ea133ff857566641ef81cff337538bdcf2 Mon Sep 17 00:00:00 2001 From: Lyuben Manolov Date: Fri, 13 Jan 2017 10:51:05 +0100 Subject: [PATCH 22/38] Add field osm.Address.Footway --- osm/osm.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osm/osm.go b/osm/osm.go index d0c9b8b..694d4db 100644 --- a/osm/osm.go +++ b/osm/osm.go @@ -7,6 +7,7 @@ type Address struct { HouseNumber string `json:"house_number"` Road string `json:"road"` Pedestrian string `json:"pedestrian"` + Footway string `json:"footway"` Cycleway string `json:"cycleway"` Highway string `json:"highway"` Path string `json:"path"` @@ -49,6 +50,8 @@ func (a Address) Street() string { street = a.Path } else if a.Cycleway != "" { street = a.Cycleway + } else if a.Footway != "" { + street = a.Footway } else if a.Highway != "" { street = a.Highway } From 2b906c104044f9bcf1bda34a0a2a6c3c55e89133 Mon Sep 17 00:00:00 2001 From: Lyuben Manolov Date: Fri, 13 Jan 2017 16:15:54 +0100 Subject: [PATCH 23/38] Clean up refactored HTTPGeocoder --- http_geocoder.go | 43 +++++++++++++++++++------------------------ 1 file changed, 19 insertions(+), 24 deletions(-) diff --git a/http_geocoder.go b/http_geocoder.go index 2692480..91bf2a0 100644 --- a/http_geocoder.go +++ b/http_geocoder.go @@ -17,6 +17,8 @@ const DefaultTimeout = time.Second * 8 // ErrNoResult occurs when no result returned var ErrNoResult = errors.New("NO_RESULT") + +// ErrTimeout occurs when no response returned within timeoutInSeconds var ErrTimeout = errors.New("TIMEOUT") // Location is the output of Geocode @@ -68,30 +70,26 @@ func (g HTTPGeocoder) Geocode(address string) (*Location, error) { ctx, cancel := context.WithTimeout(context.TODO(), DefaultTimeout) defer cancel() - ch := make(chan struct { + type geoResp struct { l *Location e error - }, 1) - go func() { + } + ch := make(chan geoResp, 1) + + go func(ch chan geoResp) { if err := response(ctx, g.GeocodeURL(url.QueryEscape(address)), responseParser); err != nil { - ch <- struct { - l *Location - e error - }{ + ch <- geoResp{ l: nil, e: err, } } loc, err := responseParser.Location() - ch <- struct { - l *Location - e error - }{ + ch <- geoResp{ l: loc, e: err, } - }() + }(ch) select { case <-ctx.Done(): @@ -108,30 +106,26 @@ func (g HTTPGeocoder) ReverseGeocode(lat, lng float64) (*Address, error) { ctx, cancel := context.WithTimeout(context.TODO(), DefaultTimeout) defer cancel() - ch := make(chan struct { + type revResp struct { a *Address e error - }, 1) - go func() { + } + ch := make(chan revResp, 1) + + go func(ch chan revResp) { if err := response(ctx, g.ReverseGeocodeURL(Location{lat, lng}), responseParser); err != nil { - ch <- struct { - a *Address - e error - }{ + ch <- revResp{ a: nil, e: err, } } addr, err := responseParser.Address() - ch <- struct { - a *Address - e error - }{ + ch <- revResp{ a: addr, e: err, } - }() + }(ch) select { case <-ctx.Done(): @@ -147,6 +141,7 @@ func response(ctx context.Context, url string, obj ResponseParser) error { if err != nil { return err } + req = req.WithContext(ctx) resp, err := http.DefaultClient.Do(req) if err != nil { From 8bc4627e7d0cb7c7ee1b9cc5f4218c5c76d00004 Mon Sep 17 00:00:00 2001 From: Lyuben Manolov Date: Tue, 24 Jan 2017 14:59:55 +0100 Subject: [PATCH 24/38] Cleanup after rebasing --- bing/geocoder.go | 9 ++++++--- here/geocoder.go | 5 ++++- locationiq/geocoder.go | 7 ------- mapquest/open/geocoder.go | 1 - opencage/geocoder.go | 21 ++++---------------- openstreetmap/geocoder.go | 40 +++++++++++---------------------------- 6 files changed, 25 insertions(+), 58 deletions(-) diff --git a/bing/geocoder.go b/bing/geocoder.go index a13851d..c28a27f 100644 --- a/bing/geocoder.go +++ b/bing/geocoder.go @@ -56,10 +56,13 @@ func (b baseURL) ReverseGeocodeURL(l geo.Location) string { func (r *geocodeResponse) Location() (*geo.Location, error) { if len(r.ResourceSets) <= 0 || len(r.ResourceSets[0].Resources) <= 0 { - return nil, fmt.Errorf("empty result resolving location") + return nil, nil } c := r.ResourceSets[0].Resources[0].Point.Coordinates - return &geo.Location{c[0], c[1]}, nil + return &geo.Location{ + Lat: c[0], + Lng: c[1], + }, nil } func (r *geocodeResponse) Address() (*geo.Address, error) { @@ -69,7 +72,7 @@ func (r *geocodeResponse) Address() (*geo.Address, error) { if len(r.ResourceSets) <= 0 || len(r.ResourceSets[0].Resources) <= 0 { return nil, nil } - //fmt.Printf("%+v\n\n", r.ResourceSets[0].Resources[0].Address) + a := r.ResourceSets[0].Resources[0].Address return &geo.Address{ FormattedAddress: a.FormattedAddress, diff --git a/here/geocoder.go b/here/geocoder.go index a1cfe49..c7fcdb5 100644 --- a/here/geocoder.go +++ b/here/geocoder.go @@ -85,7 +85,10 @@ func (r *geocodeResponse) Location() (*geo.Location, error) { return nil, nil } p := r.Response.View[0].Result[0].Location.DisplayPosition - return &geo.Location{p.Latitude, p.Longitude}, nil + return &geo.Location{ + Lat: p.Latitude, + Lng: p.Longitude, + }, nil } func (r *geocodeResponse) Address() (*geo.Address, error) { diff --git a/locationiq/geocoder.go b/locationiq/geocoder.go index f9b29d1..8e351c4 100644 --- a/locationiq/geocoder.go +++ b/locationiq/geocoder.go @@ -93,10 +93,3 @@ func (r *geocodeResponse) Address() (*geo.Address, error) { CountryCode: strings.ToUpper(r.Addr.CountryCode), }, nil } - -func (r *geocodeResponse) FormattedAddress() string { - if r.Error != "" { - return "" - } - return r.DisplayName -} diff --git a/mapquest/open/geocoder.go b/mapquest/open/geocoder.go index 14f4f42..7e895a0 100644 --- a/mapquest/open/geocoder.go +++ b/mapquest/open/geocoder.go @@ -9,7 +9,6 @@ import ( type ( baseURL string - geocodeResponse struct { Results []struct { Locations []struct { diff --git a/opencage/geocoder.go b/opencage/geocoder.go index 8a41529..ab587fa 100644 --- a/opencage/geocoder.go +++ b/opencage/geocoder.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/codingsince1985/geo-golang" + "github.com/codingsince1985/geo-golang/osm" ) type ( @@ -15,27 +16,13 @@ type ( Results []struct { Formatted string Geometry geo.Location - Components osmAddress + Components osm.Address } Status struct { Code int Message string } } - - osmAddress struct { - HouseNumber string `json:"house_number"` - Suburb string `json:"suburb"` - City string `json:"city"` - Village string `json:"village"` - County string `json:"county"` - Country string `json:"country"` - CountryCode string `json:"country_code"` - Road string `json:"road"` - State string `json:"state"` - StateDistrict string `json:"state_district"` - Postcode string `json:"postcode"` - } ) // Geocoder constructs OpenCage geocoder @@ -92,10 +79,10 @@ func (r *geocodeResponse) Address() (*geo.Address, error) { return &geo.Address{ FormattedAddress: r.Results[0].Formatted, HouseNumber: addr.HouseNumber, - Street: addr.Road, + Street: addr.Street(), Suburb: addr.Suburb, Postcode: addr.Postcode, - City: locality, + City: addr.Locality(), CountryCode: strings.ToUpper(addr.CountryCode), Country: addr.Country, County: addr.County, diff --git a/openstreetmap/geocoder.go b/openstreetmap/geocoder.go index 5bca1a2..f888e17 100644 --- a/openstreetmap/geocoder.go +++ b/openstreetmap/geocoder.go @@ -3,32 +3,19 @@ package openstreetmap import ( "fmt" - "strings" "github.com/codingsince1985/geo-golang" + "github.com/codingsince1985/geo-golang/osm" + "strings" ) type ( - baseURL string + baseURL string geocodeResponse struct { - DisplayName string `json:"display_name"` - Lat string - Lon string - Error string - Addr osmAddress `json:"address"` - } - - osmAddress struct { - HouseNumber string `json:"house_number"` - Suburb string `json:"suburb"` - City string `json:"city"` - Village string `json:"village"` - County string `json:"county"` - Country string `json:"country"` - CountryCode string `json:"country_code"` - Road string `json:"road"` - State string `json:"state"` - StateDistrict string `json:"state_district"` - Postcode string `json:"postcode"` + DisplayName string `json:"display_name"` + Lat string + Lon string + Error string + Addr osm.Address `json:"address"` } ) @@ -69,18 +56,13 @@ func (r *geocodeResponse) Address() (*geo.Address, error) { if r.Error != "" { return nil, fmt.Errorf("reverse geocoding error: %s", r.Error) } - var locality string - if r.Addr.City != "" { - locality = r.Addr.City - } else { - locality = r.Addr.Village - } + return &geo.Address{ FormattedAddress: r.DisplayName, HouseNumber: r.Addr.HouseNumber, - Street: r.Addr.Road, + Street: r.Addr.Street(), Postcode: r.Addr.Postcode, - City: locality, + City: r.Addr.Locality(), Suburb: r.Addr.Suburb, State: r.Addr.State, Country: r.Addr.Country, From 5011dcdae3830ff3db66a123df143798ce3c2780 Mon Sep 17 00:00:00 2001 From: Lyuben Manolov Date: Thu, 26 Jan 2017 15:11:08 +0100 Subject: [PATCH 25/38] Adjust google to the new interface --- google/geocoder.go | 94 ++++++++++++++++++++++++++++++++++++----- google/geocoder_test.go | 10 +++-- 2 files changed, 89 insertions(+), 15 deletions(-) diff --git a/google/geocoder.go b/google/geocoder.go index e5b83fd..11ce8e9 100644 --- a/google/geocoder.go +++ b/google/geocoder.go @@ -11,12 +11,32 @@ type ( baseURL string geocodeResponse struct { Results []struct { - FormattedAddress string `json:"formatted_address"` - Geometry struct { + FormattedAddress string `json:"formatted_address"` + AddressComponents []googleAddressComponent `json:"address_components"` + Geometry struct { Location geo.Location } } + Status string `json: "status"` } + googleAddressComponent struct { + LongName string `json:"long_name"` + ShortName string `json:"short_name"` + Types []string `json:"types"` + } +) + +const ( + statusOK = "OK" + statusNoResults = "ZERO_RESULTS" + componentTypeHouseNumber = "street_number" + componentTypeStreetName = "route" + componentTypeSuburb = "sublocality" + componentTypeLocality = "locality" + componentTypeStateDistrict = "administrative_area_level_2" + componentTypeState = "administrative_area_level_1" + componentTypeCountry = "country" + componentTypePostcode = "postal_code" ) // Geocoder constructs Google geocoder @@ -37,19 +57,71 @@ func getUrl(apiKey string, baseURLs ...string) string { func (b baseURL) GeocodeURL(address string) string { return string(b) + "address=" + address } func (b baseURL) ReverseGeocodeURL(l geo.Location) string { - return string(b) + fmt.Sprintf("latlng=%f,%f", l.Lat, l.Lng) + return string(b) + fmt.Sprintf("result_type=street_address&latlng=%f,%f", l.Lat, l.Lng) +} + +func (r *geocodeResponse) Location() (*geo.Location, error) { + if r.Status == statusNoResults { + return nil, nil + } else if r.Status != statusOK { + return nil, fmt.Errorf("geocoding error: %s", r.Status) + } + + return &r.Results[0].Geometry.Location, nil } -func (r *geocodeResponse) Location() geo.Location { - if len(r.Results) > 0 { - return r.Results[0].Geometry.Location +func (r *geocodeResponse) Address() (*geo.Address, error) { + if r.Status == statusNoResults { + return nil, nil + } else if r.Status != statusOK { + return nil, fmt.Errorf("reverse geocoding error: %s", r.Status) } - return geo.Location{} + + if len(r.Results) == 0 || len(r.Results[0].AddressComponents) == 0 { + return nil, nil + } + + addr := parseGoogleResult(r) + + return addr, nil } -func (r *geocodeResponse) Address() string { - if len(r.Results) > 0 { - return r.Results[0].FormattedAddress +func parseGoogleResult(r *geocodeResponse) *geo.Address { + addr := &geo.Address{} + res := r.Results[0] + addr.FormattedAddress = res.FormattedAddress +OuterLoop: + for _, comp := range res.AddressComponents { + for _, typ := range comp.Types { + switch typ { + case componentTypeHouseNumber: + addr.HouseNumber = comp.LongName + continue OuterLoop + case componentTypeStreetName: + addr.Street = comp.LongName + continue OuterLoop + case componentTypeSuburb: + addr.Suburb = comp.LongName + continue OuterLoop + case componentTypeLocality: + addr.City = comp.LongName + continue OuterLoop + case componentTypeStateDistrict: + addr.StateDistrict = comp.LongName + continue OuterLoop + case componentTypeState: + addr.State = comp.LongName + continue OuterLoop + case componentTypeCountry: + addr.Country = comp.LongName + addr.CountryCode = comp.ShortName + continue OuterLoop + case componentTypePostcode: + addr.Postcode = comp.LongName + continue OuterLoop + } + } } - return "" + + return addr } diff --git a/google/geocoder_test.go b/google/geocoder_test.go index e6d6aa2..06338b3 100644 --- a/google/geocoder_test.go +++ b/google/geocoder_test.go @@ -20,7 +20,7 @@ func TestGeocode(t *testing.T) { geocoder := google.Geocoder(token, ts.URL+"/") location, err := geocoder.Geocode("60 Collins St, Melbourne VIC 3000") assert.NoError(t, err) - assert.Equal(t, geo.Location{Lat: -37.8137683, Lng: 144.9718448}, location) + assert.Equal(t, geo.Location{Lat: -37.8137683, Lng: 144.9718448}, *location) } func TestReverseGeocode(t *testing.T) { @@ -30,7 +30,8 @@ func TestReverseGeocode(t *testing.T) { geocoder := google.Geocoder(token, ts.URL+"/") address, err := geocoder.ReverseGeocode(-37.8137683, 144.9718448) assert.NoError(t, err) - assert.True(t, strings.HasPrefix(address, "60 Collins St")) + assert.True(t, strings.HasPrefix(address.FormattedAddress, "60 Collins St")) + assert.True(t, strings.HasPrefix(address.Street, "Collins St")) } func TestReverseGeocodeWithNoResult(t *testing.T) { @@ -38,8 +39,9 @@ func TestReverseGeocodeWithNoResult(t *testing.T) { defer ts.Close() geocoder := google.Geocoder(token, ts.URL+"/") - _, err := geocoder.ReverseGeocode(-37.8137683, 164.9718448) - assert.Equal(t, err, geo.ErrNoResult) + addr, err := geocoder.ReverseGeocode(-37.8137683, 164.9718448) + assert.Nil(t, err) + assert.Nil(t, addr) } func testServer(response string) *httptest.Server { From 84f752b4f43af325f60ba53b103328ec6e0e8065 Mon Sep 17 00:00:00 2001 From: Lyuben Manolov Date: Mon, 6 Feb 2017 09:58:50 +0100 Subject: [PATCH 26/38] Adjust mapbox to the new interface --- mapbox/geocoder.go | 73 +++++++++++++++++++++++++++++++++++------ mapbox/geocoder_test.go | 16 +++++---- 2 files changed, 72 insertions(+), 17 deletions(-) diff --git a/mapbox/geocoder.go b/mapbox/geocoder.go index 2a4f02b..8339238 100644 --- a/mapbox/geocoder.go +++ b/mapbox/geocoder.go @@ -13,10 +13,26 @@ type ( Features []struct { PlaceName string `json:"place_name"` Center [2]float64 + Text string `json:"text"` // usually street name + Address string `json:"address"` // potentially house number + Context []struct { + Text string `json:"text"` + Id string `json:"id"` + ShortCode string `json:"short_code"` + Wikidata string `json:"wikidata"` + } } + Message string `json:"message"` } ) +const ( + mapboxPrefixLocality = "place" + mapboxPrefixPostcode = "postcode" + mapboxPrefixState = "region" + mapboxPrefixCountry = "country" +) + // Geocoder constructs Mapbox geocoder func Geocoder(token string, baseURLs ...string) geo.Geocoder { return geo.HTTPGeocoder{ @@ -29,7 +45,7 @@ func getUrl(token string, baseURLs ...string) string { if len(baseURLs) > 0 { return baseURLs[0] } - return "https://api.mapbox.com/geocoding/v5/mapbox.places/*.json?access_token=" + token + return "https://api.mapbox.com/geocoding/v5/mapbox.places/*.json?limit=1&access_token=" + token } func (b baseURL) GeocodeURL(address string) string { return strings.Replace(string(b), "*", address, 1) } @@ -38,17 +54,54 @@ func (b baseURL) ReverseGeocodeURL(l geo.Location) string { return strings.Replace(string(b), "*", fmt.Sprintf("%+f,%+f", l.Lng, l.Lat), 1) } -func (r *geocodeResponse) Location() geo.Location { - if len(r.Features) > 0 { - g := r.Features[0] - return geo.Location{g.Center[1], g.Center[0]} +func (r *geocodeResponse) Location() (*geo.Location, error) { + if len(r.Features) == 0 { + // error in response + if r.Message != "" { + return nil, fmt.Errorf("reverse geocoding error: %s", r.Message) + } else { // no results + return nil, nil + } } - return geo.Location{} + + g := r.Features[0] + return &geo.Location{ + Lat: g.Center[1], + Lng: g.Center[0], + }, nil } -func (r *geocodeResponse) Address() string { - if len(r.Features) > 0 { - return r.Features[0].PlaceName +func (r *geocodeResponse) Address() (*geo.Address, error) { + if len(r.Features) == 0 { + // error in response + if r.Message != "" { + return nil, fmt.Errorf("reverse geocoding error: %s", r.Message) + } else { // no results + return nil, nil + } } - return "" + + return parseMapboxResponse(r), nil +} + +func parseMapboxResponse(r *geocodeResponse) *geo.Address { + addr := &geo.Address{} + f := r.Features[0] + addr.FormattedAddress = f.PlaceName + addr.Street = f.Text + addr.HouseNumber = f.Address + for _, c := range f.Context { + if strings.HasPrefix(c.Id, mapboxPrefixLocality) { + addr.City = c.Text + } else if strings.HasPrefix(c.Id, mapboxPrefixPostcode) { + addr.Postcode = c.Text + } else if strings.HasPrefix(c.Id, mapboxPrefixState) { + addr.State = c.Text + } else if strings.HasPrefix(c.Id, mapboxPrefixCountry) { + addr.Country = c.Text + addr.CountryCode = strings.ToUpper(c.ShortCode) + } + } + + return addr } diff --git a/mapbox/geocoder_test.go b/mapbox/geocoder_test.go index 1eeba23..59f406c 100644 --- a/mapbox/geocoder_test.go +++ b/mapbox/geocoder_test.go @@ -2,14 +2,15 @@ package mapbox_test import ( "fmt" - "github.com/codingsince1985/geo-golang" - "github.com/codingsince1985/geo-golang/mapbox" - "github.com/stretchr/testify/assert" "net/http" "net/http/httptest" "os" "strings" "testing" + + "github.com/codingsince1985/geo-golang" + "github.com/codingsince1985/geo-golang/mapbox" + "github.com/stretchr/testify/assert" ) var token = os.Getenv("MAPBOX_API_KEY") @@ -21,7 +22,7 @@ func TestGeocode(t *testing.T) { geocoder := mapbox.Geocoder(token, ts.URL+"/") location, err := geocoder.Geocode("60 Collins St, Melbourne VIC 3000") assert.NoError(t, err) - assert.Equal(t, geo.Location{Lat: -37.813754, Lng: 144.971756}, location) + assert.Equal(t, geo.Location{Lat: -37.813754, Lng: 144.971756}, *location) } func TestReverseGeocode(t *testing.T) { @@ -32,7 +33,7 @@ func TestReverseGeocode(t *testing.T) { address, err := geocoder.ReverseGeocode(-37.813754, 144.971756) assert.NoError(t, err) fmt.Println(address) - assert.True(t, strings.Index(address, "60 Collins St") >= 0) + assert.True(t, strings.Index(address.FormattedAddress, "60 Collins St") >= 0) } func TestReverseGeocodeWithNoResult(t *testing.T) { @@ -40,8 +41,9 @@ func TestReverseGeocodeWithNoResult(t *testing.T) { defer ts.Close() geocoder := mapbox.Geocoder(token, ts.URL+"/") - _, err := geocoder.ReverseGeocode(-37.813754, 164.971756) - assert.Equal(t, err, geo.ErrNoResult) + addr, err := geocoder.ReverseGeocode(-37.813754, 164.971756) + assert.Nil(t, err) + assert.Nil(t, addr) } func testServer(response string) *httptest.Server { From 110e91ea3be1ec9c6d8e3c532181f22c97b8433a Mon Sep 17 00:00:00 2001 From: Lyuben Manolov Date: Mon, 6 Feb 2017 12:00:25 +0100 Subject: [PATCH 27/38] Fix test; gofmt and goimports; --- bing/geocoder.go | 3 ++- google/geocoder.go | 1 + here/geocoder.go | 1 + mapbox/geocoder.go | 3 ++- mapquest/open/geocoder.go | 5 +++-- opencage/geocoder.go | 6 ------ openstreetmap/geocoder.go | 3 ++- openstreetmap/geocoder_test.go | 2 +- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/bing/geocoder.go b/bing/geocoder.go index c28a27f..2676398 100644 --- a/bing/geocoder.go +++ b/bing/geocoder.go @@ -4,8 +4,9 @@ package bing import ( "errors" "fmt" - "github.com/codingsince1985/geo-golang" "strings" + + "github.com/codingsince1985/geo-golang" ) type ( diff --git a/google/geocoder.go b/google/geocoder.go index 11ce8e9..c628af2 100644 --- a/google/geocoder.go +++ b/google/geocoder.go @@ -4,6 +4,7 @@ package google import ( "fmt" + geo "github.com/codingsince1985/geo-golang" ) diff --git a/here/geocoder.go b/here/geocoder.go index c7fcdb5..77cc450 100644 --- a/here/geocoder.go +++ b/here/geocoder.go @@ -3,6 +3,7 @@ package here import ( "fmt" + "github.com/codingsince1985/geo-golang" ) diff --git a/mapbox/geocoder.go b/mapbox/geocoder.go index 8339238..62d22c7 100644 --- a/mapbox/geocoder.go +++ b/mapbox/geocoder.go @@ -3,8 +3,9 @@ package mapbox import ( "fmt" - "github.com/codingsince1985/geo-golang" "strings" + + "github.com/codingsince1985/geo-golang" ) type ( diff --git a/mapquest/open/geocoder.go b/mapquest/open/geocoder.go index 7e895a0..621a08f 100644 --- a/mapquest/open/geocoder.go +++ b/mapquest/open/geocoder.go @@ -3,12 +3,13 @@ package open import ( "fmt" - "github.com/codingsince1985/geo-golang" "strings" + + "github.com/codingsince1985/geo-golang" ) type ( - baseURL string + baseURL string geocodeResponse struct { Results []struct { Locations []struct { diff --git a/opencage/geocoder.go b/opencage/geocoder.go index ab587fa..eea5d32 100644 --- a/opencage/geocoder.go +++ b/opencage/geocoder.go @@ -69,12 +69,6 @@ func (r *geocodeResponse) Address() (*geo.Address, error) { } addr := r.Results[0].Components - var locality string - if addr.City != "" { - locality = addr.City - } else { - locality = addr.Village - } return &geo.Address{ FormattedAddress: r.Results[0].Formatted, diff --git a/openstreetmap/geocoder.go b/openstreetmap/geocoder.go index f888e17..47c443c 100644 --- a/openstreetmap/geocoder.go +++ b/openstreetmap/geocoder.go @@ -3,9 +3,10 @@ package openstreetmap import ( "fmt" + "strings" + "github.com/codingsince1985/geo-golang" "github.com/codingsince1985/geo-golang/osm" - "strings" ) type ( diff --git a/openstreetmap/geocoder_test.go b/openstreetmap/geocoder_test.go index e4dd04d..42b420a 100644 --- a/openstreetmap/geocoder_test.go +++ b/openstreetmap/geocoder_test.go @@ -38,8 +38,8 @@ func TestReverseGeocodeWithNoResult(t *testing.T) { geocoder := openstreetmap.GeocoderWithURL(ts.URL + "/") //geocoder := openstreetmap.Geocoder() addr, err := geocoder.ReverseGeocode(-37.8157915, 164.9656171) - assert.Nil(t, err) assert.Nil(t, addr) + assert.NotNil(t, err) } func testServer(response string) *httptest.Server { From bb3e4db166869141c65221ee2d7cc212e8d927ed Mon Sep 17 00:00:00 2001 From: Lyuben Manolov Date: Mon, 6 Feb 2017 17:48:57 +0100 Subject: [PATCH 28/38] Fix tests --- cached/geocoder.go | 10 +++--- cached/geocoder_test.go | 68 +++++++++++++++++++++++++---------------- data/geocoder.go | 22 +++++++------ data/geocoder_test.go | 50 +++++++++++++++++++++--------- 4 files changed, 94 insertions(+), 56 deletions(-) diff --git a/cached/geocoder.go b/cached/geocoder.go index 2559625..6309e57 100644 --- a/cached/geocoder.go +++ b/cached/geocoder.go @@ -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 { @@ -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 diff --git a/cached/geocoder_test.go b/cached/geocoder_test.go index a6343e3..b5e5118 100644 --- a/cached/geocoder_test.go +++ b/cached/geocoder_test.go @@ -1,15 +1,15 @@ 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) @@ -17,55 +17,69 @@ 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) } diff --git a/data/geocoder.go b/data/geocoder.go index 95f3c2a..0e398e7 100644 --- a/data/geocoder.go +++ b/data/geocoder.go @@ -5,10 +5,10 @@ import ( ) // AddressToLocation maps address string to location (lat, long) -type AddressToLocation map[string]geo.Location +type AddressToLocation map[geo.Address]geo.Location // LocationToAddress maps location(lat,lng) to address -type LocationToAddress map[geo.Location]string +type LocationToAddress map[geo.Location]geo.Address // dataGeocoder represents geo data in memory type dataGeocoder struct { @@ -25,17 +25,21 @@ func Geocoder(addressToLocation AddressToLocation, LocationToAddress LocationToA } // Geocode returns location for address -func (d dataGeocoder) Geocode(address string) (geo.Location, error) { - if l, ok := d.AddressToLocation[address]; ok { - return l, nil +func (d dataGeocoder) Geocode(address string) (*geo.Location, error) { + addr := geo.Address{ + FormattedAddress: address, } - return geo.Location{}, geo.ErrNoResult + if l, ok := d.AddressToLocation[addr]; ok { + return &l, nil + } + + return nil, nil } // ReverseGeocode returns address for location -func (d dataGeocoder) ReverseGeocode(lat, lng float64) (string, error) { +func (d dataGeocoder) ReverseGeocode(lat, lng float64) (*geo.Address, error) { if address, ok := d.LocationToAddress[geo.Location{Lat: lat, Lng: lng}]; ok { - return address, nil + return &address, nil } - return "", geo.ErrNoResult + return nil, nil } diff --git a/data/geocoder_test.go b/data/geocoder_test.go index 1ca199d..4ba5698 100644 --- a/data/geocoder_test.go +++ b/data/geocoder_test.go @@ -1,34 +1,54 @@ package data_test import ( + "strings" + "testing" + "github.com/codingsince1985/geo-golang" "github.com/codingsince1985/geo-golang/data" "github.com/stretchr/testify/assert" - "testing" ) -var 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", - }, +var ( + addressFixture = geo.Address{ + FormattedAddress: "64 Elizabeth Street, Melbourne, Victoria 3000, Australia", + //Street: "Elizabeth Street", + //HouseNumber: "64", + //City: "Melbourne", + //Postcode: "3000", + //State: "Victoria", + //Country: "Australia", + //CountryCode: "AU", + } + locationFixture = geo.Location{ + Lat: -37.814107, + Lng: 144.96328, + } + geocoder = data.Geocoder( + data.AddressToLocation{ + addressFixture: locationFixture, + }, + data.LocationToAddress{ + locationFixture: addressFixture, + }, + ) ) func TestGeocode(t *testing.T) { - location, err := geocoder.Geocode("Melbourne VIC") + location, err := geocoder.Geocode(addressFixture.FormattedAddress) assert.NoError(t, err) - assert.Equal(t, geo.Location{Lat: -37.814107, Lng: 144.96328}, location) + assert.Equal(t, geo.Location{Lat: -37.814107, Lng: 144.96328}, *location) } func TestReverseGeocode(t *testing.T) { - address, err := geocoder.ReverseGeocode(-37.816742, 144.964463) - assert.NoError(t, err) - assert.Equal(t, "Melbourne VIC 3000, Australia", address) + address, err := geocoder.ReverseGeocode(locationFixture.Lat, locationFixture.Lng) + assert.Nil(t, err) + assert.NotNil(t, address) + assert.True(t, strings.Contains(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) } From 2db9245b2b710a9ea560da8940200f90d6914ef1 Mon Sep 17 00:00:00 2001 From: Lyuben Manolov Date: Mon, 6 Feb 2017 18:09:12 +0100 Subject: [PATCH 29/38] Adjust tests --- chained/geocoder.go | 12 ++++---- chained/geocoder_test.go | 61 ++++++++++++++++++++++++---------------- data/geocoder_test.go | 7 ----- 3 files changed, 42 insertions(+), 38 deletions(-) diff --git a/chained/geocoder.go b/chained/geocoder.go index abb2419..35e14a6 100644 --- a/chained/geocoder.go +++ b/chained/geocoder.go @@ -10,29 +10,29 @@ type chainedGeocoder struct{ Geocoders []geo.Geocoder } func Geocoder(geocoders ...geo.Geocoder) geo.Geocoder { return chainedGeocoder{Geocoders: geocoders} } // Geocode returns location for address -func (c chainedGeocoder) Geocode(address string) (geo.Location, error) { +func (c chainedGeocoder) Geocode(address string) (*geo.Location, error) { // Geocode address by each geocoder until we get a real location response for i := range c.Geocoders { - if l, err := c.Geocoders[i].Geocode(address); err == nil { + if l, err := c.Geocoders[i].Geocode(address); err == nil && l != nil { return l, nil } // skip error and try the next geocoder continue } // No geocoders found a result - return geo.Location{}, geo.ErrNoResult + return nil, nil } // ReverseGeocode returns address for location -func (c chainedGeocoder) ReverseGeocode(lat, lng float64) (string, error) { +func (c chainedGeocoder) ReverseGeocode(lat, lng float64) (*geo.Address, error) { // Geocode address by each geocoder until we get a real location response for i := range c.Geocoders { - if addr, err := c.Geocoders[i].ReverseGeocode(lat, lng); err == nil { + if addr, err := c.Geocoders[i].ReverseGeocode(lat, lng); err == nil && addr != nil { return addr, nil } // skip error and try the next geocoder continue } // No geocoders found a result - return "", geo.ErrNoResult + return nil, nil } diff --git a/chained/geocoder_test.go b/chained/geocoder_test.go index 606f87d..1349f22 100644 --- a/chained/geocoder_test.go +++ b/chained/geocoder_test.go @@ -13,50 +13,60 @@ import ( // 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 = chained.Geocoder( - data.Geocoder( - data.AddressToLocation{ - "Melbourne VIC": geo.Location{Lat: -37.814107, Lng: 144.96328}, - }, - data.LocationToAddress{}, - ), +var ( + addressFixture = geo.Address{ + FormattedAddress: "64 Elizabeth Street, Melbourne, Victoria 3000, Australia", + } + locationFixture = geo.Location{ + Lat: -37.814107, + Lng: 144.96328, + } + geocoder = chained.Geocoder( + data.Geocoder( + data.AddressToLocation{ + addressFixture: locationFixture, + }, + data.LocationToAddress{}, + ), - data.Geocoder( - data.AddressToLocation{}, - data.LocationToAddress{ - geo.Location{Lat: -37.816742, Lng: 144.964463}: "Melbourne VIC 3000, Australia", - }, - ), + data.Geocoder( + data.AddressToLocation{}, + data.LocationToAddress{ + locationFixture: addressFixture, + }, + ), + ) ) func TestGeocode(t *testing.T) { - location, err := geocoder.Geocode("Melbourne VIC") + location, err := geocoder.Geocode(addressFixture.FormattedAddress) assert.NoError(t, err) - assert.Equal(t, geo.Location{Lat: -37.814107, Lng: 144.96328}, location) + assert.Equal(t, geo.Location{locationFixture.Lat, locationFixture.Lng}, *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(0, 0) + assert.Nil(t, err) + assert.Nil(t, addr) } func TestChainedGeocode(t *testing.T) { mock1 := data.Geocoder( data.AddressToLocation{ - "Austin,TX": geo.Location{Lat: 1, Lng: 2}, + geo.Address{FormattedAddress: "Austin,TX"}: geo.Location{Lat: 1, Lng: 2}, }, data.LocationToAddress{}, ) mock2 := data.Geocoder( data.AddressToLocation{ - "Dallas,TX": geo.Location{Lat: 3, Lng: 4}, + geo.Address{FormattedAddress: "Dallas,TX"}: geo.Location{Lat: 3, Lng: 4}, }, data.LocationToAddress{}, ) @@ -65,12 +75,13 @@ func TestChainedGeocode(t *testing.T) { l, err := c.Geocode("Austin,TX") assert.NoError(t, err) - assert.Equal(t, geo.Location{Lat: 1, Lng: 2}, l) + assert.Equal(t, geo.Location{Lat: 1, Lng: 2}, *l) l, err = c.Geocode("Dallas,TX") assert.NoError(t, err) - assert.Equal(t, geo.Location{Lat: 3, Lng: 4}, l) + assert.Equal(t, geo.Location{Lat: 3, Lng: 4}, *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) } diff --git a/data/geocoder_test.go b/data/geocoder_test.go index 4ba5698..c359f0a 100644 --- a/data/geocoder_test.go +++ b/data/geocoder_test.go @@ -12,13 +12,6 @@ import ( var ( addressFixture = geo.Address{ FormattedAddress: "64 Elizabeth Street, Melbourne, Victoria 3000, Australia", - //Street: "Elizabeth Street", - //HouseNumber: "64", - //City: "Melbourne", - //Postcode: "3000", - //State: "Victoria", - //Country: "Australia", - //CountryCode: "AU", } locationFixture = geo.Location{ Lat: -37.814107, From 59428a20ecab9d2e12ef3dbeead4e94673bc612b Mon Sep 17 00:00:00 2001 From: Lyuben Manolov Date: Wed, 8 Feb 2017 15:40:19 +0100 Subject: [PATCH 30/38] Remove debug output from test --- mapbox/geocoder_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/mapbox/geocoder_test.go b/mapbox/geocoder_test.go index 59f406c..17a065a 100644 --- a/mapbox/geocoder_test.go +++ b/mapbox/geocoder_test.go @@ -1,7 +1,6 @@ package mapbox_test import ( - "fmt" "net/http" "net/http/httptest" "os" @@ -32,7 +31,6 @@ func TestReverseGeocode(t *testing.T) { geocoder := mapbox.Geocoder(token, ts.URL+"/") address, err := geocoder.ReverseGeocode(-37.813754, 144.971756) assert.NoError(t, err) - fmt.Println(address) assert.True(t, strings.Index(address.FormattedAddress, "60 Collins St") >= 0) } From 0c4cc7bea851e51054ef6d8dc79f408a2d50c754 Mon Sep 17 00:00:00 2001 From: Lyuben Manolov Date: Thu, 9 Feb 2017 09:50:03 +0100 Subject: [PATCH 31/38] Extend output in the examples --- geocoder_test.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/geocoder_test.go b/geocoder_test.go index a72ee7d..cffa6da 100644 --- a/geocoder_test.go +++ b/geocoder_test.go @@ -101,7 +101,17 @@ func ExampleGeocoder() { func try(geocoder geo.Geocoder) { location, _ := geocoder.Geocode(addr) - fmt.Printf("%s location is (%.6f, %.6f)\n", addr, location.Lat, location.Lng) + if location != nil { + fmt.Printf("%s location is (%.6f, %.6f)\n", addr, location.Lat, location.Lng) + } else { + fmt.Println("got location") + } address, _ := geocoder.ReverseGeocode(lat, lng) - fmt.Printf("Address of (%.6f,%.6f) 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 address") + } + fmt.Println("\n") } From 3e6ac0bb9d4444f58212cd47f38881a501b7c356 Mon Sep 17 00:00:00 2001 From: Lyuben Manolov Date: Thu, 9 Feb 2017 10:55:55 +0100 Subject: [PATCH 32/38] Refactor examples Rename geocoder_test.go to examples/geocoder_example.go. Move the file to its own package to avoid import cycles. --- geocoder_test.go => examples/geocoder_example.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) rename geocoder_test.go => examples/geocoder_example.go (97%) diff --git a/geocoder_test.go b/examples/geocoder_example.go similarity index 97% rename from geocoder_test.go rename to examples/geocoder_example.go index cffa6da..90ae8d4 100644 --- a/geocoder_test.go +++ b/examples/geocoder_example.go @@ -1,4 +1,4 @@ -package geo_test +package main import ( "fmt" @@ -24,12 +24,16 @@ const ( ZOOM = 18 ) +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"))) From bd9fd407940baecab078d4b2b10e221abbc10e3f Mon Sep 17 00:00:00 2001 From: Lyuben Manolov Date: Fri, 10 Feb 2017 08:06:26 +0100 Subject: [PATCH 33/38] gofmt; goimports; comments --- chained/geocoder_test.go | 6 +++--- examples/geocoder_example.go | 33 +++++++++++++++++++++++++++-- google/geocoder.go | 2 +- google/geocoder_test.go | 7 +++--- here/geocoder_test.go | 7 +++--- mapquest/nominatim/geocoder_test.go | 7 +++--- mapquest/open/geocoder_test.go | 7 +++--- 7 files changed, 51 insertions(+), 18 deletions(-) diff --git a/chained/geocoder_test.go b/chained/geocoder_test.go index 1349f22..4726513 100644 --- a/chained/geocoder_test.go +++ b/chained/geocoder_test.go @@ -1,13 +1,13 @@ package chained_test import ( + "strings" + "testing" + "github.com/codingsince1985/geo-golang" "github.com/codingsince1985/geo-golang/chained" "github.com/codingsince1985/geo-golang/data" "github.com/stretchr/testify/assert" - - "strings" - "testing" ) // geocoder is chained with one data geocoder with address -> location data diff --git a/examples/geocoder_example.go b/examples/geocoder_example.go index 90ae8d4..220d842 100644 --- a/examples/geocoder_example.go +++ b/examples/geocoder_example.go @@ -63,44 +63,73 @@ func ExampleGeocoder() { google.Geocoder(os.Getenv("GOOGLE_API_KEY")), )) // Output: Google Geocoding API - // Melbourne VIC location is (-37.813628, 144.963058) - // Address of (-37.813611,144.963056) is 350 Bourke St, Melbourne VIC 3004, Australia + // Melbourne VIC location is (-37.813611, 144.963056) + // 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, 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 Elizabeth Street, 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, 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"} } func try(geocoder geo.Geocoder) { diff --git a/google/geocoder.go b/google/geocoder.go index c628af2..0b82d3b 100644 --- a/google/geocoder.go +++ b/google/geocoder.go @@ -5,7 +5,7 @@ package google import ( "fmt" - geo "github.com/codingsince1985/geo-golang" + "github.com/codingsince1985/geo-golang" ) type ( diff --git a/google/geocoder_test.go b/google/geocoder_test.go index 06338b3..7a4a9b3 100644 --- a/google/geocoder_test.go +++ b/google/geocoder_test.go @@ -1,14 +1,15 @@ package google_test import ( - "github.com/codingsince1985/geo-golang" - "github.com/codingsince1985/geo-golang/google" - "github.com/stretchr/testify/assert" "net/http" "net/http/httptest" "os" "strings" "testing" + + "github.com/codingsince1985/geo-golang" + "github.com/codingsince1985/geo-golang/google" + "github.com/stretchr/testify/assert" ) var token = os.Getenv("GOOGLE_API_KEY") diff --git a/here/geocoder_test.go b/here/geocoder_test.go index 7ab6d6f..6d0a8e7 100644 --- a/here/geocoder_test.go +++ b/here/geocoder_test.go @@ -1,14 +1,15 @@ package here_test import ( - "github.com/codingsince1985/geo-golang" - "github.com/codingsince1985/geo-golang/here" - "github.com/stretchr/testify/assert" "net/http" "net/http/httptest" "os" "strings" "testing" + + "github.com/codingsince1985/geo-golang" + "github.com/codingsince1985/geo-golang/here" + "github.com/stretchr/testify/assert" ) var appID = os.Getenv("HERE_APP_ID") diff --git a/mapquest/nominatim/geocoder_test.go b/mapquest/nominatim/geocoder_test.go index 372f9c5..66e4f53 100644 --- a/mapquest/nominatim/geocoder_test.go +++ b/mapquest/nominatim/geocoder_test.go @@ -1,14 +1,15 @@ package nominatim_test import ( - "github.com/codingsince1985/geo-golang" - "github.com/codingsince1985/geo-golang/mapquest/nominatim" - "github.com/stretchr/testify/assert" "net/http" "net/http/httptest" "os" "strings" "testing" + + "github.com/codingsince1985/geo-golang" + "github.com/codingsince1985/geo-golang/mapquest/nominatim" + "github.com/stretchr/testify/assert" ) var key = os.Getenv("MAPQUEST_NOMINATUM_KEY") diff --git a/mapquest/open/geocoder_test.go b/mapquest/open/geocoder_test.go index 9459fe6..efddea1 100644 --- a/mapquest/open/geocoder_test.go +++ b/mapquest/open/geocoder_test.go @@ -1,14 +1,15 @@ package open_test import ( - "github.com/codingsince1985/geo-golang" - "github.com/codingsince1985/geo-golang/mapquest/open" - "github.com/stretchr/testify/assert" "net/http" "net/http/httptest" "os" "strings" "testing" + + "github.com/codingsince1985/geo-golang" + "github.com/codingsince1985/geo-golang/mapquest/open" + "github.com/stretchr/testify/assert" ) var key = os.Getenv("MAPQUEST_OPEN_KEY") From dbf78970bd473aa362ab3da9904f4669912e3f0b Mon Sep 17 00:00:00 2001 From: Lyuben Manolov Date: Fri, 10 Feb 2017 08:51:14 +0100 Subject: [PATCH 34/38] Cleanup Delete ErrNoResults, since no longer used. Move the Location and Address structures to geocoder.go, since these are part of the interface's contract. --- geocoder.go | 21 +++++++++++++++++++++ http_geocoder.go | 24 ------------------------ 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/geocoder.go b/geocoder.go index f358b79..7dbe031 100644 --- a/geocoder.go +++ b/geocoder.go @@ -6,3 +6,24 @@ type Geocoder interface { Geocode(address string) (*Location, error) ReverseGeocode(lat, lng float64) (*Address, error) } + +// Location is the output of Geocode +type Location struct { + Lat, Lng float64 +} + +// Address is returned by ReverseGeocode. +// This is a structured representation of an address, including its flat representation +type Address struct { + FormattedAddress string + Street string + HouseNumber string + Suburb string + Postcode string + State string + StateDistrict string + County string + Country string + CountryCode string + City string +} diff --git a/http_geocoder.go b/http_geocoder.go index 91bf2a0..65450d3 100644 --- a/http_geocoder.go +++ b/http_geocoder.go @@ -15,33 +15,9 @@ import ( // Default timeout for the request execution const DefaultTimeout = time.Second * 8 -// ErrNoResult occurs when no result returned -var ErrNoResult = errors.New("NO_RESULT") - // ErrTimeout occurs when no response returned within timeoutInSeconds var ErrTimeout = errors.New("TIMEOUT") -// Location is the output of Geocode -type Location struct { - Lat, Lng float64 -} - -// Address is returned by ReverseGeocode. -// This is a structured representation of an address, including its flat representation -type Address struct { - FormattedAddress string - Street string - HouseNumber string - Suburb string - Postcode string - State string - StateDistrict string - County string - Country string - CountryCode string - City string -} - // EndpointBuilder defines functions that build urls for geocode/reverse geocode type EndpointBuilder interface { GeocodeURL(string) string From ba3c751c158c8d4a7a99f0d4fb8ca91b1519a834 Mon Sep 17 00:00:00 2001 From: Lyuben Manolov Date: Fri, 10 Feb 2017 09:22:16 +0100 Subject: [PATCH 35/38] Update README --- README.md | 42 ++++++++++++++++++++++++++++-------- examples/geocoder_example.go | 2 +- 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index a0ea6cb..f4cb29a 100644 --- a/README.md +++ b/README.md @@ -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) @@ -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"))) @@ -74,7 +78,7 @@ func main() { fmt.Println("OpenStreetMap") try(openstreetmap.Geocoder()) - + fmt.Println("LocationIQ") try(locationiq.Geocoder(os.Getenv("LOCATIONIQ_API_KEY"), ZOOM)) @@ -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 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 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 == diff --git a/examples/geocoder_example.go b/examples/geocoder_example.go index 220d842..d4d9509 100644 --- a/examples/geocoder_example.go +++ b/examples/geocoder_example.go @@ -146,5 +146,5 @@ func try(geocoder geo.Geocoder) { } else { fmt.Println("got address") } - fmt.Println("\n") + fmt.Print("\n") } From a6c1fa23a1b35675099d003a89a3939ab1212d4d Mon Sep 17 00:00:00 2001 From: Lyuben Manolov Date: Fri, 10 Feb 2017 14:14:57 +0100 Subject: [PATCH 36/38] Update Go version in travis config --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 0a51e89..9ee4571 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: go sudo: false go: -- '1.5' +- '1.7' - tip go_import_path: github.com/codingsince1985/geo-golang before_install: From dc9ef841a41f62d75e6990e572d75af539bfb87a Mon Sep 17 00:00:00 2001 From: Lyuben Manolov Date: Sat, 11 Feb 2017 17:47:25 +0100 Subject: [PATCH 37/38] Minor fixes to PR#34 Use geo.ParseFloat in mapquest/nominatim. Fix typo in osm/osm.go --- mapquest/nominatim/geocoder.go | 9 ++------- osm/osm.go | 2 +- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/mapquest/nominatim/geocoder.go b/mapquest/nominatim/geocoder.go index f76b51a..28f3a72 100644 --- a/mapquest/nominatim/geocoder.go +++ b/mapquest/nominatim/geocoder.go @@ -52,8 +52,8 @@ func (r *geocodeResponse) Location() (*geo.Location, error) { } return &geo.Location{ - Lat: parseFloat(r.Lat), - Lng: parseFloat(r.Lon), + Lat: geo.ParseFloat(r.Lat), + Lng: geo.ParseFloat(r.Lon), }, nil } @@ -75,8 +75,3 @@ func (r *geocodeResponse) Address() (*geo.Address, error) { CountryCode: strings.ToUpper(r.Addr.CountryCode), }, nil } - -func parseFloat(value string) float64 { - f, _ := strconv.ParseFloat(value, 64) - return f -} diff --git a/osm/osm.go b/osm/osm.go index 694d4db..aba8533 100644 --- a/osm/osm.go +++ b/osm/osm.go @@ -1,4 +1,4 @@ -// Package osm provides common types for OpneStreetMap used by various providers +// Package osm provides common types for OpenStreetMap used by various providers // and some helper functions to reduce code repetition across specific client implementations. package osm From 22149d59761b17bf6dea7075b2c56d3314730820 Mon Sep 17 00:00:00 2001 From: Lyuben Manolov Date: Sat, 11 Feb 2017 17:51:19 +0100 Subject: [PATCH 38/38] Remove unused import --- mapquest/nominatim/geocoder.go | 1 - 1 file changed, 1 deletion(-) diff --git a/mapquest/nominatim/geocoder.go b/mapquest/nominatim/geocoder.go index 28f3a72..2060376 100644 --- a/mapquest/nominatim/geocoder.go +++ b/mapquest/nominatim/geocoder.go @@ -3,7 +3,6 @@ package nominatim import ( "fmt" - "strconv" "strings" "github.com/codingsince1985/geo-golang"