-
Notifications
You must be signed in to change notification settings - Fork 125
/
mapquest_geocoder.go
195 lines (156 loc) · 4.71 KB
/
mapquest_geocoder.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
package geo
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strconv"
)
// This struct contains all the funcitonality
// of interacting with the MapQuest Geocoding Service
type MapQuestGeocoder struct{}
type mapQuestGeocodeResponse struct {
BoundingBox []string `json:"boundingbox"`
Lat string
Lng string `json:"lon"`
DisplayName string `json:"display_name"`
}
type mapQuestReverseGeocodeResponse struct {
Address struct {
Road string
City string
State string
PostCode string `json:"postcode"`
CountryCode string `json:"country_code"`
}
}
// This is the error that consumers receive when there
// are no results from the geocoding request.
var mapquestZeroResultsError = errors.New("ZERO_RESULTS")
var MapquestAPIKey = ""
// This contains the base URL for the Mapquest Geocoder API.
var mapquestGeocodeURL = "http://open.mapquestapi.com/nominatim/v1"
func SetMapquestAPIKey(newAPIKey string) {
MapquestAPIKey = newAPIKey
}
// Note: In the next major revision (1.0.0), it is planned
// That Geocoders should adhere to the `geo.Geocoder`
// interface and provide versioning of APIs accordingly.
// Sets the base URL for the MapQuest Geocoding API.
func SetMapquestGeocodeURL(newGeocodeURL string) {
mapquestGeocodeURL = newGeocodeURL
}
// Request Issues a request to the open mapquest api geocoding services using the passed in url query.
// Returns an array of bytes as the result of the api call or an error if one occurs during the process.
// Note: Since this is an arbitrary request, you are responsible for passing in your API key if you want one.
func (g *MapQuestGeocoder) Request(url string) ([]byte, error) {
client := &http.Client{}
fullUrl := fmt.Sprintf("%s/%s", mapquestGeocodeURL, url)
// TODO Refactor into an api driver of some sort
// It seems odd that golang-geo should be responsible of versioning of APIs, etc.
req, _ := http.NewRequest("GET", fullUrl, nil)
resp, requestErr := client.Do(req)
if requestErr != nil {
return nil, requestErr
}
// TODO figure out a better typing for response
data, dataReadErr := ioutil.ReadAll(resp.Body)
if dataReadErr != nil {
return nil, dataReadErr
}
return data, nil
}
// Geocode returns the first point returned by MapQuest's geocoding service or an error
// if one occurs during the geocoding request.
func (g *MapQuestGeocoder) Geocode(address string) (*Point, error) {
queryStr, err := mapquestGeocodeQueryStr(address)
if err != nil {
return nil, err
}
data, err := g.Request(queryStr)
if err != nil {
return nil, err
}
res := []*mapQuestGeocodeResponse{}
json.Unmarshal(data, &res)
if len(res) == 0 {
return &Point{}, mapquestZeroResultsError
}
lat, err := strconv.ParseFloat(res[0].Lat, 64)
if err != nil {
return nil, err
}
lng, err := strconv.ParseFloat(res[0].Lng, 64)
if err != nil {
return nil, err
}
p := &Point{
lat: lat,
lng: lng,
}
return p, nil
}
func mapquestGeocodeQueryStr(address string) (string, error) {
url_safe_query := url.QueryEscape(address)
var queryBuf = bytes.NewBufferString("search.php?")
_, err := queryBuf.WriteString(fmt.Sprintf("q=%s", url_safe_query))
if err != nil {
return "", err
}
if MapquestAPIKey != "" {
_, err := queryBuf.WriteString(fmt.Sprintf("&key=%s", MapquestAPIKey))
if err != nil {
return "", err
}
}
_, err = queryBuf.WriteString("&format=json")
if err != nil {
return "", err
}
return queryBuf.String(), err
}
// ReverseGeocode returns the first most available address that corresponds to the passed in point.
// It may also return an error if one occurs during execution.
func (g *MapQuestGeocoder) ReverseGeocode(p *Point) (string, error) {
queryStr, err := mapquestReverseGeocodeQueryStr(p)
if err != nil {
return "", err
}
data, err := g.Request(queryStr)
if err != nil {
return "", err
}
res := []*mapQuestReverseGeocodeResponse{}
err = json.Unmarshal(data, &res)
if err != nil {
return "", err
}
road := res[0].Address.Road
city := res[0].Address.City
state := res[0].Address.State
postcode := res[0].Address.PostCode
countryCode := res[0].Address.CountryCode
resStr := fmt.Sprintf("%s %s %s %s %s", road, city, state, postcode, countryCode)
return resStr, nil
}
func mapquestReverseGeocodeQueryStr(p *Point) (string, error) {
var queryBuf = bytes.NewBufferString("reverse.php?")
_, err := queryBuf.WriteString(fmt.Sprintf("lat=%f&lng=%f", p.lat, p.lng))
if err != nil {
return "", err
}
if MapquestAPIKey != "" {
_, err := queryBuf.WriteString(fmt.Sprintf("&key=%s", MapquestAPIKey))
if err != nil {
return "", err
}
}
_, err = queryBuf.WriteString("&format=json")
if err != nil {
return "", err
}
return queryBuf.String(), err
}