forked from kellydunn/golang-geo
-
Notifications
You must be signed in to change notification settings - Fork 0
/
google_geocoder.go
133 lines (105 loc) · 3.49 KB
/
google_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
package geo
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/url"
)
// This struct contains all the funcitonality
// of interacting with the Google Maps Geocoding Service
type GoogleGeocoder struct{
HttpClient *http.Client
}
// This struct contains selected fields from Google's Geocoding Service response
type googleGeocodeResponse struct {
Results []struct {
FormattedAddress string `json:"formatted_address"`
Geometry struct {
Location struct {
Lat float64
Lng float64
}
}
}
}
// This is the error that consumers receive when there
// are no results from the geocoding request.
var googleZeroResultsError = errors.New("ZERO_RESULTS")
// This contains the base URL for the Google Geocoder API.
var googleGeocodeURL = "http://maps.googleapis.com/maps/api/geocode/json"
// 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 Google Geocoding API.
func SetGoogleGeocodeURL(newGeocodeURL string) {
googleGeocodeURL = newGeocodeURL
}
// Issues a request to the google geocoding service and forwards the passed in params string
// as a URL-encoded entity. Returns an array of byes as a result, or an error if one occurs during the process.
func (g *GoogleGeocoder) Request(params string) ([]byte, error) {
if g.HttpClient == nil {
g.HttpClient = &http.Client{}
}
client := g.HttpClient
fullUrl := fmt.Sprintf("%s?sensor=false&%s", googleGeocodeURL, params)
// TODO Potentially refactor out from MapQuestGeocoder as well
req, _ := http.NewRequest("GET", fullUrl, nil)
resp, requestErr := client.Do(req)
if requestErr != nil {
return nil, requestErr
}
data, dataReadErr := ioutil.ReadAll(resp.Body)
if dataReadErr != nil {
return nil, dataReadErr
}
return data, nil
}
// Geocodes the passed in query string and returns a pointer to a new Point struct.
// Returns an error if the underlying request cannot complete.
func (g *GoogleGeocoder) Geocode(query string) (*Point, error) {
url_safe_query := url.QueryEscape(query)
data, err := g.Request(fmt.Sprintf("address=%s", url_safe_query))
if err != nil {
return nil, err
}
point, err := g.extractLatLngFromResponse(data)
if err != nil {
return nil, err
}
return &point, nil
}
// Extracts the first location from a Google Geocoder Response body.
func (g *GoogleGeocoder) extractLatLngFromResponse(data []byte) (Point, error) {
res := &googleGeocodeResponse{}
json.Unmarshal(data, &res)
if len(res.Results) == 0 {
return Point{}, googleZeroResultsError
}
lat := res.Results[0].Geometry.Location.Lat
lng := res.Results[0].Geometry.Location.Lng
return Point{lat, lng}, nil
}
// Reverse geocodes the pointer to a Point struct and returns the first address that matches
// or returns an error if the underlying request cannot complete.
func (g *GoogleGeocoder) ReverseGeocode(p *Point) (string, error) {
data, err := g.Request(fmt.Sprintf("latlng=%f,%f", p.lat, p.lng))
if err != nil {
return "", err
}
resStr, err := g.extractAddressFromResponse(data)
return resStr, err
}
// Returns an Address from a Google Geocoder Response body.
func (g *GoogleGeocoder) extractAddressFromResponse(data []byte) (string, error) {
res := &googleGeocodeResponse{}
err := json.Unmarshal(data, &res)
if err != nil {
return "", err
}
if len(res.Results) == 0 {
return "", errors.New("ZERO_RESULTS")
}
return res.Results[0].FormattedAddress, nil
}