forked from kellydunn/golang-geo
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmapquest_geocoder.go
119 lines (94 loc) · 3.59 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
package geo
import (
"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{}
// This is the error that consumers receive when there
// are no results from the geocoding request.
var mapquestZeroResultsError = errors.New("ZERO_RESULTS")
// This contains the base URL for the Mapquest Geocoder API.
var mapquestGeocodeURL = "http://open.mapquestapi.com/nominatim/v1"
// 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
}
// 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.
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 {
panic(requestErr)
}
// TODO figure out a better typing for response
data, dataReadErr := ioutil.ReadAll(resp.Body)
if dataReadErr != nil {
return nil, dataReadErr
}
return data, nil
}
// Returns the first point returned by MapQuest's geocoding service or an error
// if one occurs during the geocoding request.
func (g *MapQuestGeocoder) Geocode(query string) (*Point, error) {
url_safe_query := url.QueryEscape(query)
data, err := g.Request(fmt.Sprintf("search.php?q=%s&format=json", url_safe_query))
if err != nil {
return nil, err
}
lat, lng, extractErr := g.extractLatLngFromResponse(data)
if extractErr != nil {
return nil, extractErr
}
p := &Point{lat: lat, lng: lng}
return p, nil
}
// Extracts the first lat and lng values from a MapQuest response body.
func (g *MapQuestGeocoder) extractLatLngFromResponse(data []byte) (float64, float64, error) {
res := make([]map[string]interface{}, 0)
json.Unmarshal(data, &res)
if len(res) == 0 {
return 0, 0, mapquestZeroResultsError
}
lat, _ := strconv.ParseFloat(res[0]["lat"].(string), 64)
lng, _ := strconv.ParseFloat(res[0]["lon"].(string), 64)
return lat, lng, nil
}
// 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) {
data, err := g.Request(fmt.Sprintf("reverse.php?lat=%f&lon=%f&format=json", p.lat, p.lng))
if err != nil {
return "", err
}
resStr := g.extractAddressFromResponse(data)
return resStr, nil
}
// Return sthe first address in the passed in byte array.
func (g *MapQuestGeocoder) extractAddressFromResponse(data []byte) string {
res := make(map[string]map[string]string)
json.Unmarshal(data, &res)
// TODO determine if it's better to have channels to receive this data on
// Provides for concurrency during HTTP requests, etc ~
road, _ := res["address"]["road"]
city, _ := res["address"]["city"]
state, _ := res["address"]["state"]
postcode, _ := res["address"]["postcode"]
country_code, _ := res["address"]["country_code"]
resStr := fmt.Sprintf("%s %s %s %s %s", road, city, state, postcode, country_code)
return resStr
}