-
Notifications
You must be signed in to change notification settings - Fork 125
/
opencage_geocoder.go
184 lines (146 loc) · 4.37 KB
/
opencage_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
package geo
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/url"
)
// This struct contains all the funcitonality
// of interacting with the OpenCage Geocoding Service
type OpenCageGeocoder struct{}
// This struct contains selected fields from OpenCage's Geocoding Service response
type opencageGeocodeResponse struct {
Results []struct {
Formatted string `json:"formatted"`
Geometry struct {
Lat float64
Lng float64
}
}
}
type opencageReverseGeocodeResponse struct {
Results []struct {
Formatted string `json:"formatted"`
}
}
// This is the error that consumers receive when there
// are no results from the geocoding request.
var opencageZeroResultsError = errors.New("ZERO_RESULTS")
// This contains the base URL for the Mapquest Geocoder API.
var opencageGeocodeURL = "http://api.opencagedata.com/geocode/v1/json"
var OpenCageAPIKey = ""
// 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 OpenCage Geocoding API.
func SetOpenCageGeocodeURL(newGeocodeURL string) {
opencageGeocodeURL = newGeocodeURL
}
func SetOpenCageAPIKey(newAPIKey string) {
OpenCageAPIKey = newAPIKey
}
// Request Issues a request to the open OpenCage 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 *OpenCageGeocoder) Request(url string) ([]byte, error) {
client := &http.Client{}
fullUrl := fmt.Sprintf("%s/%s", opencageGeocodeURL, 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 OpenCage's geocoding service or an error
// if one occurs during the geocoding request.
func (g *OpenCageGeocoder) Geocode(address string) (*Point, error) {
queryStr, err := opencageGeocodeQueryStr(address)
if err != nil {
return nil, err
}
data, err := g.Request(queryStr)
if err != nil {
return nil, err
}
res := &opencageGeocodeResponse{}
json.Unmarshal(data, res)
if len(res.Results) == 0 {
return nil, opencageZeroResultsError
}
lat := res.Results[0].Geometry.Lat
lng := res.Results[0].Geometry.Lng
point := &Point{
lat: lat,
lng: lng,
}
return point, nil
}
func opencageGeocodeQueryStr(address string) (string, error) {
url_safe_query := url.QueryEscape(address)
var queryStr = bytes.NewBufferString("?")
_, err := queryStr.WriteString(fmt.Sprintf("q=%s", url_safe_query))
if err != nil {
return "", err
}
if OpenCageAPIKey != "" {
_, err := queryStr.WriteString(fmt.Sprintf("&key=%s", OpenCageAPIKey))
if err != nil {
return "", err
}
}
_, err = queryStr.WriteString("&pretty=1")
if err != nil {
return "", err
}
return queryStr.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 *OpenCageGeocoder) ReverseGeocode(p *Point) (string, error) {
queryStr, err := opencageReverseGeocodeQueryStr(p)
if err != nil {
return "", err
}
data, err := g.Request(queryStr)
if err != nil {
return "", err
}
res := &opencageReverseGeocodeResponse{}
err = json.Unmarshal(data, res)
if err != nil {
return "", err
}
if len(res.Results) == 0 {
return "", opencageZeroResultsError
}
return res.Results[0].Formatted, nil
}
func opencageReverseGeocodeQueryStr(p *Point) (string, error) {
var queryStr = bytes.NewBufferString("?")
_, err := queryStr.WriteString(fmt.Sprintf("q=%f,%f", p.lat, p.lng))
if err != nil {
return "", err
}
if OpenCageAPIKey != "" {
_, err := queryStr.WriteString(fmt.Sprintf("&key=%s", OpenCageAPIKey))
if err != nil {
return "", err
}
}
_, err = queryStr.WriteString("&pretty=1")
if err != nil {
return "", err
}
return queryStr.String(), err
}