-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathgoogle.py
More file actions
192 lines (148 loc) · 6.81 KB
/
google.py
File metadata and controls
192 lines (148 loc) · 6.81 KB
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
from urllib import urlencode
from urllib2 import urlopen
import urllib2
import logging
logger = logging.getLogger("ggeocoder")
try:
import json
except ImportError:
try:
import simplejson as json
except ImportError:
from django.utils import simplejson as json
class Google(object):
"""Geocoder using the Google Maps API."""
def __init__(self, api_key=None,
domain='maps.googleapis.com',
proxy=None,
resource=None,
format_string='%s',
output_format='json',
sensor=False
):
"""
Google geocoder uses API v3 and supports reverse geo-coding
Initialize a customized Google geocoder with location-specific
address information and your Google Maps API key.
``api_key`` should be a valid Google Maps API key. Required as per Google Geocoding API
V2 docs, but the API works without a key in practice.
``domain`` should be the localized Google Maps domain to connect to. The default
is 'maps.google.com', but if you're geocoding address in the UK (for
example), you may want to set it to 'maps.google.co.uk' to properly bias results.
``resource`` is DEPRECATED and ignored -- the parameter remains for compatibility
purposes. The supported 'maps/geo' API is used regardless of this parameter.
``format_string`` is a string containing '%s' where the string to
geocode should be interpolated before querying the geocoder.
For example: '%s, Mountain View, CA'. The default is just '%s'.
``output_format`` (DEPRECATED) can be 'json', 'xml', or 'kml' and will
control the output format of Google's response. The default is 'json'. 'kml' is
an alias for 'xml'.
``sensor`` (required) indicates whether or not the geocoding request comes from a
device with a location sensor. This value must be either true or false.
"""
if resource != None:
from warnings import warn
warn('geopy.geocoders.google.GoogleGeocoder: The `resource` parameter is deprecated '+
'and now ignored. The Google-supported "maps/geo" API will be used.', DeprecationWarning)
self.api_key = api_key
proxy = {'http':proxy} if proxy is not None else {}
proxy_support = urllib2.ProxyHandler(proxy)
self.opener = urllib2.build_opener(proxy_support)
self.domain = domain
self.format_string = format_string
self.sensor = str(sensor).lower()
if output_format:
supported_formats = ('json',)
if output_format not in supported_formats:
raise ValueError('if defined, `output_format` must be one of: %s' % supported_formats)
self.output_format = output_format
else:
self.output_format = "json"
@property
def url(self):
domain = self.domain.strip('/')
return "http://%s/maps/api/geocode/%s?%%s" % (domain, self.output_format)
def geocode(self, address, exactly_one=True):
"""
Geocode an address string with Google Maps API.
``address`` (required) address string to be gecoded.
Return:
A tuple of (formatted_address, (lat, lng), results_dict) if exactly_one is True; a list of these tuples otherwise.
Raise an exception with original google.maps.GeocoderStatus code if it is not "OK".
"""
params = {'address': self.format_string % address,
'sensor': self.sensor,
}
if self.api_key:
params['key'] = self.api_key
url = self.url % urlencode(params)
return self.geocode_url(url, exactly_one)
def reverse(self, coord):
"""
Reverse geocode a (lat, lng) coordinate with Google Maps API.
``coord`` (required) coordinate to be reverse geocoded in the format of (lat, lng).
**Return**
A tuple of (formatted_address, (lat, lng), results_dict).
Raise an exception with original google.maps.GeocoderStatus code if it is not "OK".
"""
(lat,lng) = coord
params = {'latlng': self.format_string % lat+','+self.format_string % lng,
'sensor': self.sensor,
}
url = self.url % urlencode(params)
return self.geocode_url(url, exactly_one=True)
def geocode_url(self, url, exactly_one=True):
## preserve "," in the query url
url = url.replace("%2C", ",")
logger.debug("Fetching %s..." % url)
req=urllib2.Request(url)
page=self.opener.open(req)
dispatch = getattr(self, 'parse_' + self.output_format)
status, results = dispatch(page, exactly_one)
logger.debug("...%s" % status)
return results
def parse_json(self, page, exactly_one=True):
##TODO: decode page?
doc = json.loads(page.read())
results = doc.get('results', [])
status_code = doc.get("status", 'Empty status')
if len(results) == 0:
# Got empty result. Parse out the status code and raise an error if necessary.
self.check_status_code(status_code)
return None
def parse_place(place):
formatted_address = place.get('formatted_address')
lat = place['geometry']['location']['lat']
lng = place['geometry']['location']['lng']
return (formatted_address, (lat, lng), place)
if exactly_one:
return status_code, parse_place(results[0])
else:
return status_code, [parse_place(place) for place in results]
def check_status_code(self, status_code):
if status_code != 'OK':
raise status_code
import unittest
from random import sample
import numpy as np
class TestGoogleGeocoder(unittest.TestCase):
def setUp(self):
GOOGLE_KEY = None
self.geocoder = Google(GOOGLE_KEY, output_format='json')
def test_geocoding(self):
address_str = "1600 Pennsylvania Ave, Washington DC"
coord = (38.8987149, -77.0376555)
address = u"1600 Pennsylvania Ave NW, Washington, DC 20500, USA"
results = self.geocoder.geocode(address_str)
assert len(results) == 3
assert results[0] == address
assert np.allclose( np.array(results[1]), np.array(coord), atol=1e-3 )
def test_reverse_geocoding(self):
coord = (38.8987149, -77.0376555)
address = u"1600 Pennsylvania Ave NW, Washington, DC 20500, USA"
results = self.geocoder.reverse(coord)
assert len(results) == 3
assert results[0] == address
assert np.allclose( np.array(results[1]), np.array(coord), atol=1e-3 )
if __name__ == '__main__':
unittest.main()