Skip to content

Commit b3c1f55

Browse files
add: amap api support (#82)
1 parent 14ca04a commit b3c1f55

File tree

5 files changed

+259
-16
lines changed

5 files changed

+259
-16
lines changed

README.md

+4-1
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,10 @@ func ExampleGeocoder() {
9898
try(bing.Geocoder(os.Getenv("BING_API_KEY")))
9999

100100
fmt.Println("Baidu Geocoding API")
101-
try(google.Geocoder(os.Getenv("BAIDU_API_KEY")))
101+
try(baidu.Geocoder(os.Getenv("BAIDU_API_KEY")))
102+
103+
fmt.Println("Amap Geocoding API")
104+
try(amap.Geocoder(os.Getenv("BAIDU_API_KEY")))
102105

103106
fmt.Println("Mapbox API")
104107
try(mapbox.Geocoder(os.Getenv("MAPBOX_API_KEY")))

amap/amap.go

+124
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
package amap
2+
3+
import (
4+
"encoding/xml"
5+
"fmt"
6+
"strings"
7+
8+
"github.com/codingsince1985/geo-golang"
9+
)
10+
11+
type (
12+
baseURL string
13+
geocodeResponse struct {
14+
XMLName xml.Name `xml:"response"`
15+
Status int `xml:"status"`
16+
Info string `xml:"info"`
17+
Infocode int `xml:"infocode"`
18+
Count int `xml:"count"`
19+
Geocodes []struct {
20+
FormattedAddress string `xml:"formatted_address"`
21+
Country string `xml:"country"`
22+
Province string `xml:"province"`
23+
Citycode string `xml:"citycode"`
24+
City string `xml:"city"`
25+
District string `xml:"district"`
26+
Adcode string `xml:"adcode"`
27+
Street string `xml:"street"`
28+
Number string `xml:"number"`
29+
Location string `xml:"location"`
30+
Level string `xml:"level"`
31+
} `xml:"geocodes>geocode"`
32+
Regeocode struct {
33+
FormattedAddress string `xml:"formatted_address"`
34+
AddressComponent struct {
35+
Country string `xml:"country"`
36+
Township string `xml:"township"`
37+
District string `xml:"district"`
38+
Adcode string `xml:"adcode"`
39+
Province string `xml:"province"`
40+
Citycode string `xml:"citycode"`
41+
StreetNumber struct {
42+
Number string `xml:"number"`
43+
Location string `xml:"location"`
44+
Direction string `xml:"direction"`
45+
Distance string `xml:"distance"`
46+
Street string `xml:"street"`
47+
} `xml:"streetNumber"`
48+
} `xml:"addressComponent"`
49+
} `xml:"regeocode"`
50+
}
51+
)
52+
53+
const (
54+
statusOK = 1
55+
)
56+
57+
var r = 1000
58+
59+
// Geocoder constructs AMAP geocoder
60+
func Geocoder(key string, radius int, baseURLs ...string) geo.Geocoder {
61+
if radius > 0 {
62+
r = radius
63+
}
64+
return geo.HTTPGeocoder{
65+
EndpointBuilder: baseURL(getURL(key, baseURLs...)),
66+
ResponseParserFactory: func() geo.ResponseParser { return &geocodeResponse{} },
67+
ResponseUnmarshaler: &geo.XMLUnmarshaler{},
68+
}
69+
}
70+
71+
func getURL(apiKey string, baseURLs ...string) string {
72+
if len(baseURLs) > 0 {
73+
return baseURLs[0]
74+
}
75+
return fmt.Sprintf("https://restapi.amap.com/v3/geocode/*?key=%s&", apiKey)
76+
}
77+
78+
// GeocodeURL https://restapi.amap.com/v3/geocode/geo?&output=XML&key=APPKEY&address=ADDRESS
79+
func (b baseURL) GeocodeURL(address string) string {
80+
return strings.Replace(string(b), "*", "geo", 1) + fmt.Sprintf("output=XML&address=%s", address)
81+
}
82+
83+
// ReverseGeocodeURL https://restapi.amap.com/v3/geocode/regeo?output=XML&key=APPKEY&radius=1000&extensions=all&location=31.225696563611,121.49884033194
84+
func (b baseURL) ReverseGeocodeURL(l geo.Location) string {
85+
return strings.Replace(string(b), "*", "regeo", 1) + fmt.Sprintf("output=XML&location=%f,%f&radius=%d&extensions=all", l.Lng, l.Lat, r)
86+
}
87+
88+
func (r *geocodeResponse) Location() (*geo.Location, error) {
89+
var location = &geo.Location{}
90+
if len(r.Geocodes) == 0 {
91+
return nil, nil
92+
}
93+
if r.Status != statusOK {
94+
return nil, fmt.Errorf("geocoding error: %v", r.Status)
95+
}
96+
fmt.Sscanf(string(r.Geocodes[0].Location), "%f,%f", &location.Lng, &location.Lat)
97+
return location, nil
98+
}
99+
100+
func (r *geocodeResponse) Address() (*geo.Address, error) {
101+
if r.Status != statusOK {
102+
return nil, fmt.Errorf("reverse geocoding error: %v", r.Status)
103+
}
104+
105+
addr := parseAmapResult(r)
106+
107+
return addr, nil
108+
}
109+
110+
func parseAmapResult(r *geocodeResponse) *geo.Address {
111+
addr := &geo.Address{}
112+
res := r.Regeocode
113+
addr.FormattedAddress = string(res.FormattedAddress)
114+
addr.HouseNumber = string(res.AddressComponent.StreetNumber.Number)
115+
addr.Street = string(res.AddressComponent.StreetNumber.Street)
116+
addr.Suburb = string(res.AddressComponent.District)
117+
addr.State = string(res.AddressComponent.Province)
118+
addr.Country = string(res.AddressComponent.Country)
119+
120+
if (*addr == geo.Address{}) {
121+
return nil
122+
}
123+
return addr
124+
}

amap/amap_test.go

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package amap_test
2+
3+
import (
4+
"net/http"
5+
"net/http/httptest"
6+
"os"
7+
"testing"
8+
9+
"github.com/codingsince1985/geo-golang"
10+
"github.com/codingsince1985/geo-golang/amap"
11+
"github.com/stretchr/testify/assert"
12+
)
13+
14+
var key = os.Getenv("AMAP_APP_KEY")
15+
16+
func TestGeocode(t *testing.T) {
17+
ts := testServer(response1)
18+
defer ts.Close()
19+
20+
geocoder := amap.Geocoder(key, 1000, ts.URL+"/")
21+
location, err := geocoder.Geocode("北京市海淀区清河街道西二旗西路领秀新硅谷")
22+
23+
assert.NoError(t, err)
24+
assert.NotNil(t, location)
25+
if location != nil {
26+
assert.Equal(t, geo.Location{Lat: 40.055106, Lng: 116.309866}, *location)
27+
}
28+
}
29+
30+
func TestReverseGeocode(t *testing.T) {
31+
ts := testServer(response2)
32+
defer ts.Close()
33+
34+
geocoder := amap.Geocoder(key, 1000, ts.URL+"/")
35+
address, err := geocoder.ReverseGeocode(116.3084202915042, 116.3084202915042)
36+
37+
assert.NoError(t, err)
38+
assert.Equal(t, address.FormattedAddress, "北京市海淀区上地街道树村郊野公园")
39+
}
40+
41+
func TestReverseGeocodeWithNoResult(t *testing.T) {
42+
ts := testServer(response3)
43+
defer ts.Close()
44+
45+
geocoder := amap.Geocoder(key, 1000, ts.URL+"/")
46+
addr, err := geocoder.ReverseGeocode(-37.81375, 164.97176)
47+
48+
assert.NoError(t, err)
49+
assert.Nil(t, addr)
50+
}
51+
52+
func testServer(response string) *httptest.Server {
53+
return httptest.NewServer(http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
54+
resp.Write([]byte(response))
55+
}))
56+
}
57+
58+
const (
59+
response1 = `<?xml version="1.0" encoding="UTF-8"?><response><status>1</status><info>OK</info><infocode>10000</infocode><count>1</count><geocodes type="list"><geocode><formatted_address>北京市海淀区清河街道西二旗西路领秀新硅谷</formatted_address><country>中国</country><province>北京市</province><citycode>010</citycode><city>北京市</city><district>海淀区</district><township></township><neighborhood><name></name><type></type></neighborhood><building><name></name><type></type></building><adcode>110108</adcode><street>西二旗西路</street><number></number><location>116.309866,40.055106</location><level>住宅区</level></geocode></geocodes></response>`
60+
response2 = `<?xml version="1.0" encoding="UTF-8"?><response><status>1</status><info>OK</info><infocode>10000</infocode><regeocode><formatted_address>北京市海淀区上地街道树村郊野公园</formatted_address><addressComponent><country>中国</country><province>北京市</province><city></city><citycode>010</citycode><district>海淀区</district><adcode>110108</adcode><township>上地街道</township><towncode>110108022000</towncode><neighborhood><name></name><type></type></neighborhood><building><name>树村郊野公园</name><type>风景名胜;公园广场;公园</type></building><streetNumber><street>马连洼北路</street><number>29号</number><location>116.299587,40.034620</location><direction>北</direction><distance>147.306</distance></streetNumber><businessAreas type="list"><businessArea><location>116.303276,40.035542</location><name>上地</name><id>110108</id></businessArea><businessArea><location>116.256057,40.054273</location><name>西北 </name><id>110108</id></businessArea><businessArea><location>116.281156,40.028654</location><name>马连洼</name><id>110108</id></businessArea></businessAreas></addressComponent></regeocode></response>`
61+
response3 = `<?xml version="1.0" encoding="UTF-8"?><response><status>1</status><info>OK</info><infocode>10000</infocode><regeocode><formatted_address></formatted_address><addressComponent><country></country><province></province><city></city><citycode></citycode><district></district><adcode></adcode><township></township><towncode></towncode></addressComponent><pois type="list"/><roads type="list"/><roadinters type="list"/><aois type="list"/></regeocode></response>`
62+
)

examples/geocoder_example.go

+34-6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"os"
66

77
"github.com/codingsince1985/geo-golang"
8+
"github.com/codingsince1985/geo-golang/amap"
89
"github.com/codingsince1985/geo-golang/arcgis"
910
"github.com/codingsince1985/geo-golang/baidu"
1011
"github.com/codingsince1985/geo-golang/bing"
@@ -26,12 +27,14 @@ import (
2627
)
2728

2829
const (
29-
addr = "Melbourne VIC"
30-
lat, lng = -37.813611, 144.963056
31-
radius = 50
32-
zoom = 18
33-
addrFR = "Champs de Mars Paris"
34-
latFR, lngFR = 48.854395, 2.304770
30+
addr = "Melbourne VIC"
31+
lat, lng = -37.813611, 144.963056
32+
radius = 50
33+
zoom = 18
34+
addrFR = "Champs de Mars Paris"
35+
addrAmap = "北京市海淀区清河街道西二旗西路领秀新硅谷" // China only
36+
latFR, lngFR = 48.854395, 2.304770
37+
latAmap, lngAmap = 40.05510, 116.309866
3538
)
3639

3740
func main() {
@@ -61,6 +64,9 @@ func ExampleGeocoder() {
6164
fmt.Println("Baidu Geocoding API")
6265
try(baidu.Geocoder(os.Getenv("BAIDU_API_KEY"), "en", "bd09ll"))
6366

67+
fmt.Println("Amap Geocoding API")
68+
tryOnlyForAmap(amap.Geocoder(os.Getenv("AMAP_API_KEY"), radius))
69+
6470
fmt.Println("Mapbox API")
6571
try(mapbox.Geocoder(os.Getenv("MAPBOX_API_KEY")))
6672

@@ -145,6 +151,11 @@ func ExampleGeocoder() {
145151
// Address of (-37.813611,144.963056) is 341 Little Bourke Street, Melbourne, Victoria, Australia
146152
// Detailed address: &geo.Address{FormattedAddress:"341 Little Bourke Street, Melbourne, Victoria, Australia", Street:"Little Bourke Street", HouseNumber:"341", Suburb:"", Postcode:"", State:"Victoria", StateCode:"", StateDistrict:"", County:"", Country:"Australia", CountryCode:"AUS", City:"Melbourne"}
147153
//
154+
// Amap Geocoding API
155+
// 北京市海淀区清河街道西二旗西路领秀新硅谷 location is (40.05510, 116.309866)
156+
// Address of (40.05510, 116.309866) is 北京市海淀区清河街道领秀新硅谷领秀新硅谷B区
157+
// Detailed address: &geo.Address{FormattedAddress:"北京市海淀区清河街道领秀新硅谷领秀新硅谷B区", Street:"西二旗西路", HouseNumber:"2号楼", Suburb:"海 淀区", Postcode:"", State:"北京市", StateCode:"", StateDistrict:"", County:"", Country:"中国", CountryCode:"", City:""}
158+
//
148159
// Mapbox API
149160
// Melbourne VIC location is (-37.814200, 144.963200)
150161
// Address of (-37.813611,144.963056) is Elwood Park Playground, Melbourne, Victoria 3000, Australia
@@ -231,6 +242,23 @@ func try(geocoder geo.Geocoder) {
231242
fmt.Print("\n")
232243
}
233244

245+
func tryOnlyForAmap(geocoder geo.Geocoder) {
246+
location, _ := geocoder.Geocode(addrAmap)
247+
if location != nil {
248+
fmt.Printf("%s location is (%.6f, %.6f)\n", addr, location.Lat, location.Lng)
249+
} else {
250+
fmt.Println("got <nil> location")
251+
}
252+
address, _ := geocoder.ReverseGeocode(latAmap, lngAmap)
253+
if address != nil {
254+
fmt.Printf("Address of (%.6f,%.6f) is %s\n", lat, lng, address.FormattedAddress)
255+
fmt.Printf("Detailed address: %#v\n", address)
256+
} else {
257+
fmt.Println("got <nil> address")
258+
}
259+
fmt.Print("\n")
260+
}
261+
234262
func tryOnlyFRData(geocoder geo.Geocoder) {
235263
location, _ := geocoder.Geocode(addrFR)
236264
if location != nil {

http_geocoder.go

+35-9
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package geo
33
import (
44
"context"
55
"encoding/json"
6+
"encoding/xml"
67
"errors"
78
"io"
89
"net/http"
@@ -37,10 +38,15 @@ type ResponseParser interface {
3738
type HTTPGeocoder struct {
3839
EndpointBuilder
3940
ResponseParserFactory
41+
ResponseUnmarshaler
4042
}
4143

4244
func (g HTTPGeocoder) GeocodeWithContext(ctx context.Context, address string) (*Location, error) {
4345
responseParser := g.ResponseParserFactory()
46+
var responseUnmarshaler ResponseUnmarshaler = &JSONUnmarshaler{}
47+
if g.ResponseUnmarshaler != nil {
48+
responseUnmarshaler = g.ResponseUnmarshaler
49+
}
4450

4551
type geoResp struct {
4652
l *Location
@@ -49,7 +55,7 @@ func (g HTTPGeocoder) GeocodeWithContext(ctx context.Context, address string) (*
4955
ch := make(chan geoResp, 1)
5056

5157
go func(ch chan geoResp) {
52-
if err := response(ctx, g.GeocodeURL(url.QueryEscape(address)), responseParser); err != nil {
58+
if err := response(ctx, g.GeocodeURL(url.QueryEscape(address)), responseUnmarshaler, responseParser); err != nil {
5359
ch <- geoResp{
5460
l: nil,
5561
e: err,
@@ -82,6 +88,10 @@ func (g HTTPGeocoder) Geocode(address string) (*Location, error) {
8288
// ReverseGeocode returns address for location
8389
func (g HTTPGeocoder) ReverseGeocode(lat, lng float64) (*Address, error) {
8490
responseParser := g.ResponseParserFactory()
91+
var responseUnmarshaler ResponseUnmarshaler = &JSONUnmarshaler{}
92+
if g.ResponseUnmarshaler != nil {
93+
responseUnmarshaler = g.ResponseUnmarshaler
94+
}
8595

8696
ctx, cancel := context.WithTimeout(context.TODO(), DefaultTimeout)
8797
defer cancel()
@@ -93,7 +103,7 @@ func (g HTTPGeocoder) ReverseGeocode(lat, lng float64) (*Address, error) {
93103
ch := make(chan revResp, 1)
94104

95105
go func(ch chan revResp) {
96-
if err := response(ctx, g.ReverseGeocodeURL(Location{lat, lng}), responseParser); err != nil {
106+
if err := response(ctx, g.ReverseGeocodeURL(Location{lat, lng}), responseUnmarshaler, responseParser); err != nil {
97107
ch <- revResp{
98108
a: nil,
99109
e: err,
@@ -115,8 +125,28 @@ func (g HTTPGeocoder) ReverseGeocode(lat, lng float64) (*Address, error) {
115125
}
116126
}
117127

128+
type ResponseUnmarshaler interface {
129+
Unmarshal(data []byte, v any) error
130+
}
131+
132+
type JSONUnmarshaler struct{}
133+
134+
func (*JSONUnmarshaler) Unmarshal(data []byte, v any) error {
135+
body := strings.Trim(string(data), " []")
136+
if body == "" {
137+
return nil
138+
}
139+
return json.Unmarshal([]byte(body), v)
140+
}
141+
142+
type XMLUnmarshaler struct{}
143+
144+
func (*XMLUnmarshaler) Unmarshal(data []byte, v any) error {
145+
return xml.Unmarshal(data, v)
146+
}
147+
118148
// Response gets response from url
119-
func response(ctx context.Context, url string, obj ResponseParser) error {
149+
func response(ctx context.Context, url string, unmarshaler ResponseUnmarshaler, obj ResponseParser) error {
120150
req, err := http.NewRequest(http.MethodGet, url, nil)
121151
if err != nil {
122152
return err
@@ -136,12 +166,8 @@ func response(ctx context.Context, url string, obj ResponseParser) error {
136166
return err
137167
}
138168

139-
body := strings.Trim(string(data), " []")
140-
DebugLogger.Printf("Received response: %s\n", body)
141-
if body == "" {
142-
return nil
143-
}
144-
if err := json.Unmarshal([]byte(body), obj); err != nil {
169+
DebugLogger.Printf("Received response: %s\n", string(data))
170+
if err := unmarshaler.Unmarshal(data, obj); err != nil {
145171
ErrLogger.Printf("Error unmarshalling response: %s\n", err.Error())
146172
return err
147173
}

0 commit comments

Comments
 (0)