Skip to content

Commit

Permalink
Added new French API Gouv geo provider (codingsince1985#56)
Browse files Browse the repository at this point in the history
  • Loading branch information
yudao authored and codingsince1985 committed May 28, 2019
1 parent ee4a4e6 commit d62ccc6
Show file tree
Hide file tree
Showing 4 changed files with 320 additions and 4 deletions.
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 42,7 @@ import (
"github.com/codingsince1985/geo-golang/arcgis"
"github.com/codingsince1985/geo-golang/bing"
"github.com/codingsince1985/geo-golang/chained"
"github.com/codingsince1985/geo-golang/frenchapigouv"
"github.com/codingsince1985/geo-golang/geocod"
"github.com/codingsince1985/geo-golang/google"
"github.com/codingsince1985/geo-golang/here"
Expand All @@ -62,6 63,8 @@ const (
lat, lng = -37.813611, 144.963056
radius = 50
zoom = 18
addrFR = "Champs de Mars Paris"
latFR, lngFR = 48.854395, 2.304770
)

func main() {
Expand Down Expand Up @@ -115,6 118,11 @@ func ExampleGeocoder() {
fmt.Println("Yandex")
try(yandex.Geocoder(os.Getenv("YANDEX_API_KEY")))

// To use only with french locations or addresses,
// or values ​​could be estimated and will be false
fmt.Println("FrenchAPIGouv")
tryOnlyFRData(frenchapigouv.Geocoder())

// Chained geocoder will fallback to subsequent geocoders
fmt.Println("ChainedAPI[OpenStreetmap -> Google]")
try(chained.Geocoder(
Expand All @@ -139,6 147,24 @@ func try(geocoder geo.Geocoder) {
}
fmt.Print("\n")
}

func tryOnlyFRData(geocoder geo.Geocoder) {
location, _ := geocoder.Geocode(addrFR)
if location != nil {
fmt.Printf("%s location is (%.6f, %.6f)\n", addrFR, location.Lat, location.Lng)
} else {
fmt.Println("got <nil> location")
}
address, _ := geocoder.ReverseGeocode(latFR, lngFR)
if address != nil {
fmt.Printf("Address of (%.6f,%.6f) is %s\n", latFR, lngFR, address.FormattedAddress)
fmt.Printf("Detailed address: %#v\n", address)
} else {
fmt.Println("got <nil> address")
}
fmt.Print("\n")
}

```
### Result
```
Expand Down Expand Up @@ -216,6 242,11 @@ Melbourne VIC location is (41.926823, 2.254232)
Address of (-37.813611,144.963056) is Victoria, City of Melbourne, Elizabeth Street
Detailed address: &geo.Address{FormattedAddress:"Victoria, City of Melbourne, Elizabeth Street", Street:"Elizabeth Street", HouseNumber:"", Suburb:"", Postcode:"", State:"Victoria", StateDistrict:"", County:"", Country:"Australia", CountryCode:"AU", City:"City of Melbourne"}
FrenchAPIGouv
Champs de Mars Paris location is (2.304770, 48.854395)
Address of (48.854395,2.304770) is 9001, Parc du Champs de Mars, 75007, Paris, Paris, Île-de-France, France
Detailed address: &geo.Address{FormattedAddress:"9001, Parc du Champs de Mars, 75007, Paris, Paris, Île-de-France, France", Street:"Parc du Champs de Mars", HouseNumber:"9001", Suburb:"", Postcode:"75007", State:" Île-de-France", StateDistrict:"", County:" Paris", Country:"France", CountryCode:"FRA", City:"Paris"}
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
Expand Down
40 changes: 36 additions & 4 deletions examples/geocoder_example.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 8,7 @@ import (
"github.com/codingsince1985/geo-golang/arcgis"
"github.com/codingsince1985/geo-golang/bing"
"github.com/codingsince1985/geo-golang/chained"
"github.com/codingsince1985/geo-golang/frenchapigouv"
"github.com/codingsince1985/geo-golang/geocod"
"github.com/codingsince1985/geo-golang/google"
"github.com/codingsince1985/geo-golang/here"
Expand All @@ -24,10 25,12 @@ import (
)

const (
addr = "Melbourne VIC"
lat, lng = -37.813611, 144.963056
radius = 50
zoom = 18
addr = "Melbourne VIC"
lat, lng = -37.813611, 144.963056
radius = 50
zoom = 18
addrFR = "Champs de Mars Paris"
latFR, lngFR = 48.854395, 2.304770
)

func main() {
Expand Down Expand Up @@ -81,6 84,11 @@ func ExampleGeocoder() {
fmt.Println("Yandex")
try(yandex.Geocoder(os.Getenv("YANDEX_API_KEY")))

// To use only with french locations or addresses,
// or values ​​could be estimated and will be false
fmt.Println("FrenchAPIGouv")
tryOnlyFRData(frenchapigouv.Geocoder())

// Chained geocoder will fallback to subsequent geocoders
fmt.Println("ChainedAPI[OpenStreetmap -> Google]")
try(chained.Geocoder(
Expand Down Expand Up @@ -182,6 190,13 @@ func ExampleGeocoder() {
// HouseNumber:"", Suburb:"", Postcode:"", State:"Victoria", StateDistrict:"", County:"", Country:"Australia", CountryCode:"AU",
// City:"City of Melbourne"}
//
// FrenchAPIGouv
// Champs de Mars Paris location is (2.304770, 48.854395)
// Address of (48.854395,2.304770) is 9001, Parc du Champs de Mars, 75007, Paris, Paris, Île-de-France, France
// Detailed address: &geo.Address{FormattedAddress:"9001, Parc du Champs de Mars, 75007, Paris, Paris, Île-de-France, France",
// Street:"Parc du Champs de Mars", HouseNumber:"9001", Suburb:"", Postcode:"75007", State:" Île-de-France", StateDistrict:"",
// County:" Paris", Country:"France", CountryCode:"FRA", City:"Paris"}
//
// 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
Expand All @@ -206,3 221,20 @@ func try(geocoder geo.Geocoder) {
}
fmt.Print("\n")
}

func tryOnlyFRData(geocoder geo.Geocoder) {
location, _ := geocoder.Geocode(addrFR)
if location != nil {
fmt.Printf("%s location is (%.6f, %.6f)\n", addrFR, location.Lat, location.Lng)
} else {
fmt.Println("got <nil> location")
}
address, _ := geocoder.ReverseGeocode(latFR, lngFR)
if address != nil {
fmt.Printf("Address of (%.6f,%.6f) is %s\n", latFR, lngFR, address.FormattedAddress)
fmt.Printf("Detailed address: %#v\n", address)
} else {
fmt.Println("got <nil> address")
}
fmt.Print("\n")
}
117 changes: 117 additions & 0 deletions frenchapigouv/geocoder.go
Original file line number Diff line number Diff line change
@@ -0,0 1,117 @@
// Package frenchapigouv is a geo-golang based French API Gouv geocode/reverse geocode client
package frenchapigouv

import (
"fmt"
"strings"

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

type (
baseURL string
geocodeResponse struct {
Type string
Version string
Features []struct {
Type string
Geometry struct {
Type string
Coordinates []float64
}
Properties struct {
Label string
Score float64
Housenumber string
Citycode string
Context string
Postcode string
Name string
ID string
Y float64
Importance float64
Type string
City string
X float64
Street string
}
}
Attribution string
Licence string
Query string
Limit int
}
context struct {
state string
county string
countyCode string
}
)

// Geocoder constructs FrenchApiGouv geocoder
func Geocoder() geo.Geocoder { return GeocoderWithURL("https://api-adresse.data.gouv.fr/") }

// GeocoderWithURL constructs French API Gouv geocoder using a custom installation of Nominatim
func GeocoderWithURL(url string) geo.Geocoder {
return geo.HTTPGeocoder{
EndpointBuilder: baseURL(url),
ResponseParserFactory: func() geo.ResponseParser { return &geocodeResponse{} },
}
}

func (b baseURL) GeocodeURL(address string) string {
return string(b) "search?limit=1&q=" address
}

func (b baseURL) ReverseGeocodeURL(l geo.Location) string {
return string(b) "reverse?" fmt.Sprintf("lat=%f&lon=%f", l.Lat, l.Lng)
}

func (r *geocodeResponse) Location() (*geo.Location, error) {
if len(r.Features) == 0 || len(r.Features[0].Geometry.Coordinates) < 2 {
return nil, nil
}
p := r.Features[0].Geometry.Coordinates
return &geo.Location{
Lat: p[1],
Lng: p[0],
}, nil
}

func (r *geocodeResponse) Address() (*geo.Address, error) {
if len(r.Features) == 0 || r.Features[0].Properties.Label == "baninfo" {
return nil, nil
}
p := r.Features[0].Properties
c := r.parseContext()
return &geo.Address{
FormattedAddress: strings.Join(strings.Fields(strings.TrimSpace(fmt.Sprintf("%s, %s, %s, %s, %s, %s, %s", p.Housenumber, p.Street, p.Postcode, p.City, c.county, c.state, "France"))), " "),
HouseNumber: p.Housenumber,
Street: p.Street,
Postcode: p.Postcode,
City: p.City,
State: c.state,
County: c.county,
Country: "France",
CountryCode: "FRA",
}, nil
}

func (r *geocodeResponse) parseContext() *context {
var c context
if len(r.Features) > 0 {
p := r.Features[0].Properties
f := strings.Split(p.Context, ",")
for i := range f {
switch i {
case 0:
c.countyCode = f[i]
case 1:
c.county = f[i]
case 2:
c.state = f[i]
}
}
}
return &c
}
136 changes: 136 additions & 0 deletions frenchapigouv/geocoder_test.go
Original file line number Diff line number Diff line change
@@ -0,0 1,136 @@
package frenchapigouv_test

import (
"strings"
"testing"

"net/http"
"net/http/httptest"

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

func TestGeocode(t *testing.T) {
ts := testServer(response1)
defer ts.Close()

geocoder := frenchapigouv.GeocoderWithURL(ts.URL "/")
location, err := geocoder.Geocode("Champ de Mars, 5 Avenue Anatole France, 75007 Paris")
assert.Nil(t, err)
assert.Equal(t, geo.Location{Lat: 48.859831, Lng: 2.328123}, *location)
}

func TestGeocodeWithNoResult(t *testing.T) {
ts := testServer(response2)
defer ts.Close()

geocoder := frenchapigouv.GeocoderWithURL(ts.URL "/")
location, err := geocoder.Geocode("nowhere")
assert.Nil(t, err)
assert.Nil(t, location)
}

func TestReverseGeocode(t *testing.T) {
ts := testServer(response1)
defer ts.Close()

geocoder := frenchapigouv.GeocoderWithURL(ts.URL "/")
address, err := geocoder.ReverseGeocode(48.859831, 2.328123)
assert.Nil(t, err)
assert.True(t, strings.HasPrefix(address.FormattedAddress, "5, Quai Anatole France,"))
}

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

geocoder := frenchapigouv.GeocoderWithURL(ts.URL "/")
addr, err := geocoder.ReverseGeocode(0, 0.34)
assert.Nil(t, addr)
assert.Nil(t, err)
}

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

geocoder := frenchapigouv.GeocoderWithURL(ts.URL "/")
addr, err := geocoder.ReverseGeocode(0, 0)
assert.Nil(t, addr)
assert.Nil(t, err)
}

func testServer(response string) *httptest.Server {
return httptest.NewServer(http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
resp.Write([]byte(response))
}))
}

const (
response1 = `[
{
"type": "FeatureCollection",
"version": "draft",
"features": [{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [2.328123, 48.859831]
},
"properties": {
"label": "5 Quai Anatole France 75007 Paris",
"score": 0.4882581475128644,
"housenumber": "5",
"citycode": "75107",
"context": "75, Paris, Île-de-France",
"postcode": "75007",
"name": "5 Quai Anatole France",
"id": "ADRNIVX_0000000270768224",
"y": 6862409.3,
"importance": 0.2765,
"type": "housenumber",
"city": "Paris",
"x": 650705.5,
"street": "Quai Anatole France"
}
}],
"attribution": "BAN",
"licence": "ODbL 1.0",
"query": "Champ de Mars, 5 Avenue Anatole France, 75007 Paris",
"limit": 10
}
]`
response2 = `{
"type": "FeatureCollection",
"version": "draft",
"features": [],
"attribution": "BAN",
"licence": "ODbL 1.0",
"limit": 1
}`
response3 = `{
"type": "FeatureCollection",
"version": "draft",
"features": [{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [0.0, 0.0]
},
"properties": {
"label": "baninfo",
"score": 1.0,
"name": "baninfo",
"id": "db",
"type": "info",
"context": "20181125T223127",
"distance": 0
}
}],
"attribution": "BAN",
"licence": "ODbL 1.0",
"limit": 1
}`
)

0 comments on commit d62ccc6

Please sign in to comment.