-
-
Notifications
You must be signed in to change notification settings - Fork 139
/
geolocation.py
298 lines (243 loc) · 9.2 KB
/
geolocation.py
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
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
"""Look up geolocation information for media objects."""
from __future__ import print_function
from __future__ import division
from future import standard_library
from past.utils import old_div
standard_library.install_aliases() # noqa
from os import path
import requests
import urllib.request
import urllib.parse
import urllib.error
from elodie.config import load_config
from elodie import constants
from elodie import log
from elodie.localstorage import Db
__KEY__ = None
__DEFAULT_LOCATION__ = 'Unknown Location'
__PREFER_ENGLISH_NAMES__ = None
def coordinates_by_name(name):
# Try to get cached location first
db = Db()
cached_coordinates = db.get_location_coordinates(name)
if(cached_coordinates is not None):
return {
'latitude': cached_coordinates[0],
'longitude': cached_coordinates[1]
}
# If the name is not cached then we go ahead with an API lookup
geolocation_info = lookup(location=name)
if(geolocation_info is not None):
if(
'results' in geolocation_info and
len(geolocation_info['results']) != 0 and
'locations' in geolocation_info['results'][0] and
len(geolocation_info['results'][0]['locations']) != 0
):
# By default we use the first entry unless we find one with
# geocodeQuality=city.
geolocation_result = geolocation_info['results'][0]
use_location = geolocation_result['locations'][0]['latLng']
# Loop over the locations to see if we come accross a
# geocodeQuality=city.
# If we find a city we set that to the use_location and break
for location in geolocation_result['locations']:
if(
'latLng' in location and
'lat' in location['latLng'] and
'lng' in location['latLng'] and
location['geocodeQuality'].lower() == 'city'
):
use_location = location['latLng']
break
return {
'latitude': use_location['lat'],
'longitude': use_location['lng']
}
return None
def decimal_to_dms(decimal):
decimal = float(decimal)
decimal_abs = abs(decimal)
minutes, seconds = divmod(decimal_abs*3600, 60)
degrees, minutes = divmod(minutes, 60)
degrees = degrees
sign = 1 if decimal >= 0 else -1
return (degrees, minutes, seconds, sign)
def dms_to_decimal(degrees, minutes, seconds, direction=' '):
sign = 1
if(direction[0] in 'WSws'):
sign = -1
return (
float(degrees) + old_div(float(minutes), 60) +
old_div(float(seconds), 3600)
) * sign
def dms_string(decimal, type='latitude'):
# Example string -> 38 deg 14' 27.82" S
dms = decimal_to_dms(decimal)
if type == 'latitude':
direction = 'N' if decimal >= 0 else 'S'
elif type == 'longitude':
direction = 'E' if decimal >= 0 else 'W'
return '{} deg {}\' {}" {}'.format(dms[0], dms[1], dms[2], direction)
def get_key():
global __KEY__
if __KEY__ is not None:
return __KEY__
if constants.mapquest_key is not None:
__KEY__ = constants.mapquest_key
return __KEY__
config = load_config()
if('MapQuest' not in config):
return None
__KEY__ = config['MapQuest']['key']
return __KEY__
def get_prefer_english_names():
global __PREFER_ENGLISH_NAMES__
if __PREFER_ENGLISH_NAMES__ is not None:
return __PREFER_ENGLISH_NAMES__
config_file = '%s/config.ini' % constants.application_directory
if not path.exists(config_file):
return False
config = load_config()
if('MapQuest' not in config):
return False
if('prefer_english_names' not in config['MapQuest']):
return False
__PREFER_ENGLISH_NAMES__ = bool(config['MapQuest']['prefer_english_names'])
return __PREFER_ENGLISH_NAMES__
def place_name(lat, lon):
lookup_place_name_default = {'default': __DEFAULT_LOCATION__}
if(lat is None or lon is None):
return lookup_place_name_default
# Convert lat/lon to floats
if(not isinstance(lat, float)):
lat = float(lat)
if(not isinstance(lon, float)):
lon = float(lon)
# Try to get cached location first
db = Db()
# 3km distace radious for a match
cached_place_name = db.get_location_name(lat, lon, 3000)
# We check that it's a dict to coerce an upgrade of the location
# db from a string location to a dictionary. See gh-160.
if(isinstance(cached_place_name, dict)):
return cached_place_name
lookup_place_name = {}
geolocation_info = lookup(lat=lat, lon=lon)
if(geolocation_info is not None and 'address' in geolocation_info):
address = geolocation_info['address']
# gh-386 adds support for town
# taking precedence after city for backwards compatability
for loc in ['city', 'town', 'state', 'country']:
if(loc in address):
lookup_place_name[loc] = address[loc]
# In many cases the desired key is not available so we
# set the most specific as the default.
if('default' not in lookup_place_name):
lookup_place_name['default'] = address[loc]
if(lookup_place_name):
db.add_location(lat, lon, lookup_place_name)
# TODO: Maybe this should only be done on exit and not for every write.
db.update_location_db()
if('default' not in lookup_place_name):
lookup_place_name = lookup_place_name_default
return lookup_place_name
def lookup(**kwargs):
if(
'location' not in kwargs and
'lat' not in kwargs and
'lon' not in kwargs
):
return None
if('lat' in kwargs and 'lon' in kwargs):
kwargs['location'] = '{},{}'.format(kwargs['lat'], kwargs['lon'])
key = get_key()
prefer_english_names = get_prefer_english_names()
if(key is None):
return None
try:
headers = {}
params = {'format': 'json', 'key': key}
if(prefer_english_names):
headers = {'Accept-Language':'en-EN,en;q=0.8'}
params['locale'] = 'en_US'
params.update(kwargs)
path = '/geocoding/v1/address'
if('lat' in kwargs and 'lon' in kwargs):
path = '/geocoding/v1/reverse'
url = '%s%s?%s' % (
constants.mapquest_base_url,
path,
urllib.parse.urlencode(params)
)
# log the MapQuest url gh-446
log.info('MapQuest url: %s' % (url))
r = requests.get(url, headers=headers)
return parse_result(r.json())
except requests.exceptions.RequestException as e:
log.error(e)
return None
except ValueError as e:
log.error(r.text)
log.error(e)
return None
def parse_result(result):
# gh-421
# Return None if statusCode is not 0
# https://developer.mapquest.com/documentation/geocoding-api/status-codes/
if( 'info' not in result or
'statuscode' not in result['info'] or
result['info']['statuscode'] != 0
):
return None
address = parse_result_address(result)
if(address is None):
return None
result['address'] = address
result['latLng'] = parse_result_latlon(result)
return result
def parse_result_address(result):
# We want to store the city, state and country
# The only way determined to identify an unfound address is
# that none of the indicies were found
if( 'results' not in result or
len(result['results']) == 0 or
'locations' not in result['results'][0] or
len(result['results'][0]['locations']) == 0
):
return None
index_found = False
addresses = {'city': None, 'state': None, 'country': None}
result_compat = {}
result_compat['address'] = {}
locations = result['results'][0]['locations'][0]
# We are looping over locations to find the adminAreaNType key which
# has a value of City, State or Country.
# Once we find it then we obtain the value from the key adminAreaN
# where N is a numeric index.
# For example
# * adminArea1Type = 'City'
# * adminArea1 = 'Sunnyvale'
for key in locations:
# Check if the key is of the form adminArea1Type
if(key[-4:] == 'Type'):
# If it's a type then check if it corresponds to one we are intereated in
# and store the index by parsing the key
key_prefix = key[:-4]
key_index = key[-5:-4]
if(locations[key].lower() in addresses):
addresses[locations[key].lower()] = locations[key_prefix]
index_found = True
if(index_found is False):
return None
return addresses
def parse_result_latlon(result):
if( 'results' not in result or
len(result['results']) == 0 or
'locations' not in result['results'][0] or
len(result['results'][0]['locations']) == 0 or
'latLng' not in result['results'][0]['locations'][0]
):
return None
latLng = result['results'][0]['locations'][0]['latLng'];
return {'lat': latLng['lat'], 'lon': latLng['lng']}