• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

jmathai / elodie / c79dc659-6554-4465-a250-2125d49d4e20

pending completion
c79dc659-6554-4465-a250-2125d49d4e20

push

circleci

GitHub
Add debug log message for Mapquest URL (#448)

1 of 1 new or added line in 1 file covered. (100.0%)

1293 of 1427 relevant lines covered (90.61%)

0.91 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.35
/elodie/geolocation.py
1
"""Look up geolocation information for media objects."""
2
from __future__ import print_function
1✔
3
from __future__ import division
1✔
4
from future import standard_library
1✔
5
from past.utils import old_div
1✔
6

7
standard_library.install_aliases()  # noqa
1✔
8

9
from os import path
1✔
10

11
import requests
1✔
12
import urllib.request
1✔
13
import urllib.parse
1✔
14
import urllib.error
1✔
15

16
from elodie.config import load_config
1✔
17
from elodie import constants
1✔
18
from elodie import log
1✔
19
from elodie.localstorage import Db
1✔
20

21
__KEY__ = None
1✔
22
__DEFAULT_LOCATION__ = 'Unknown Location'
1✔
23
__PREFER_ENGLISH_NAMES__ = None
1✔
24

25

26
def coordinates_by_name(name):
1✔
27
    # Try to get cached location first
28
    db = Db()
1✔
29
    cached_coordinates = db.get_location_coordinates(name)
1✔
30
    if(cached_coordinates is not None):
1✔
31
        return {
×
32
            'latitude': cached_coordinates[0],
33
            'longitude': cached_coordinates[1]
34
        }
35

36
    # If the name is not cached then we go ahead with an API lookup
37
    geolocation_info = lookup(location=name)
1✔
38

39
    if(geolocation_info is not None):
1✔
40
        if(
1✔
41
            'results' in geolocation_info and
42
            len(geolocation_info['results']) != 0 and
43
            'locations' in geolocation_info['results'][0] and
44
            len(geolocation_info['results'][0]['locations']) != 0
45
        ):
46

47
            # By default we use the first entry unless we find one with
48
            #   geocodeQuality=city.
49
            geolocation_result = geolocation_info['results'][0]
1✔
50
            use_location = geolocation_result['locations'][0]['latLng']
1✔
51
            # Loop over the locations to see if we come accross a
52
            #   geocodeQuality=city.
53
            # If we find a city we set that to the use_location and break
54
            for location in geolocation_result['locations']:
1✔
55
                if(
1✔
56
                    'latLng' in location and
57
                    'lat' in location['latLng'] and
58
                    'lng' in location['latLng'] and
59
                    location['geocodeQuality'].lower() == 'city'
60
                ):
61
                    use_location = location['latLng']
1✔
62
                    break
1✔
63

64
            return {
1✔
65
                'latitude': use_location['lat'],
66
                'longitude': use_location['lng']
67
            }
68

69
    return None
×
70

71

72
def decimal_to_dms(decimal):
1✔
73
    decimal = float(decimal)
1✔
74
    decimal_abs = abs(decimal)
1✔
75
    minutes, seconds = divmod(decimal_abs*3600, 60)
1✔
76
    degrees, minutes = divmod(minutes, 60)
1✔
77
    degrees = degrees
1✔
78
    sign = 1 if decimal >= 0 else -1
1✔
79
    return (degrees, minutes, seconds, sign)
1✔
80

81

82
def dms_to_decimal(degrees, minutes, seconds, direction=' '):
1✔
83
    sign = 1
1✔
84
    if(direction[0] in 'WSws'):
1✔
85
        sign = -1
1✔
86
    return (
1✔
87
        float(degrees) + old_div(float(minutes), 60) +
88
        old_div(float(seconds), 3600)
89
    ) * sign
90

91

92
def dms_string(decimal, type='latitude'):
1✔
93
    # Example string -> 38 deg 14' 27.82" S
94
    dms = decimal_to_dms(decimal)
1✔
95
    if type == 'latitude':
1✔
96
        direction = 'N' if decimal >= 0 else 'S'
1✔
97
    elif type == 'longitude':
1✔
98
        direction = 'E' if decimal >= 0 else 'W'
1✔
99
    return '{} deg {}\' {}" {}'.format(dms[0], dms[1], dms[2], direction)
1✔
100

101

102
def get_key():
1✔
103
    global __KEY__
104
    if __KEY__ is not None:
1✔
105
        return __KEY__
1✔
106

107
    if constants.mapquest_key is not None:
1✔
108
        __KEY__ = constants.mapquest_key
×
109
        return __KEY__
×
110

111
    config = load_config()
1✔
112
    if('MapQuest' not in config):
1✔
113
        return None
×
114

115
    __KEY__ = config['MapQuest']['key']
1✔
116
    return __KEY__
1✔
117

118
def get_prefer_english_names():
1✔
119
    global __PREFER_ENGLISH_NAMES__
120
    if __PREFER_ENGLISH_NAMES__ is not None:
1✔
121
        return __PREFER_ENGLISH_NAMES__
×
122

123
    config_file = '%s/config.ini' % constants.application_directory
1✔
124
    if not path.exists(config_file):
1✔
125
        return False
1✔
126

127
    config = load_config()
×
128
    if('MapQuest' not in config):
×
129
        return False
×
130

131
    if('prefer_english_names' not in config['MapQuest']):
×
132
        return False
×
133

134
    __PREFER_ENGLISH_NAMES__ = bool(config['MapQuest']['prefer_english_names'])
×
135
    return __PREFER_ENGLISH_NAMES__
×
136

137
def place_name(lat, lon):
1✔
138
    lookup_place_name_default = {'default': __DEFAULT_LOCATION__}
1✔
139
    if(lat is None or lon is None):
1✔
140
        return lookup_place_name_default
1✔
141

142
    # Convert lat/lon to floats
143
    if(not isinstance(lat, float)):
1✔
144
        lat = float(lat)
1✔
145
    if(not isinstance(lon, float)):
1✔
146
        lon = float(lon)
1✔
147

148
    # Try to get cached location first
149
    db = Db()
1✔
150
    # 3km distace radious for a match
151
    cached_place_name = db.get_location_name(lat, lon, 3000)
1✔
152
    # We check that it's a dict to coerce an upgrade of the location
153
    #  db from a string location to a dictionary. See gh-160.
154
    if(isinstance(cached_place_name, dict)):
1✔
155
        return cached_place_name
1✔
156

157
    lookup_place_name = {}
1✔
158
    geolocation_info = lookup(lat=lat, lon=lon)
1✔
159
    if(geolocation_info is not None and 'address' in geolocation_info):
1✔
160
        address = geolocation_info['address']
1✔
161
        # gh-386 adds support for town
162
        # taking precedence after city for backwards compatability
163
        for loc in ['city', 'town', 'state', 'country']:
1✔
164
            if(loc in address):
1✔
165
                lookup_place_name[loc] = address[loc]
1✔
166
                # In many cases the desired key is not available so we
167
                #  set the most specific as the default.
168
                if('default' not in lookup_place_name):
1✔
169
                    lookup_place_name['default'] = address[loc]
1✔
170

171
    if(lookup_place_name):
1✔
172
        db.add_location(lat, lon, lookup_place_name)
1✔
173
        # TODO: Maybe this should only be done on exit and not for every write.
174
        db.update_location_db()
1✔
175

176
    if('default' not in lookup_place_name):
1✔
177
        lookup_place_name = lookup_place_name_default
1✔
178

179
    return lookup_place_name
1✔
180

181

182
def lookup(**kwargs):
1✔
183
    if(
1✔
184
        'location' not in kwargs and
185
        'lat' not in kwargs and
186
        'lon' not in kwargs
187
    ):
188
        return None
×
189

190
    if('lat' in kwargs and 'lon' in kwargs):
1✔
191
        kwargs['location'] = '{},{}'.format(kwargs['lat'], kwargs['lon'])
1✔
192

193
    key = get_key()
1✔
194
    prefer_english_names = get_prefer_english_names()
1✔
195

196
    if(key is None):
1✔
197
        return None
×
198

199
    try:
1✔
200
        headers = {}
1✔
201
        params = {'format': 'json', 'key': key}
1✔
202
        if(prefer_english_names):
1✔
203
            headers = {'Accept-Language':'en-EN,en;q=0.8'}
×
204
            params['locale'] = 'en_US'
×
205
        params.update(kwargs)
1✔
206
        path = '/geocoding/v1/address'
1✔
207
        if('lat' in kwargs and 'lon' in kwargs):
1✔
208
            path = '/geocoding/v1/reverse'
1✔
209
        url = '%s%s?%s' % (
1✔
210
                    constants.mapquest_base_url,
211
                    path,
212
                    urllib.parse.urlencode(params)
213
              )
214
        # log the MapQuest url gh-446
215
        log.info('MapQuest url: %s' % (url))
1✔
216
        r = requests.get(url, headers=headers)
1✔
217
        return parse_result(r.json())
1✔
218
    except requests.exceptions.RequestException as e:
1✔
219
        log.error(e)
×
220
        return None
×
221
    except ValueError as e:
1✔
222
        log.error(r.text)
1✔
223
        log.error(e)
1✔
224
        return None
1✔
225

226

227
def parse_result(result):
1✔
228
    # gh-421
229
    # Return None if statusCode is not 0
230
    #   https://developer.mapquest.com/documentation/geocoding-api/status-codes/
231
    if( 'info' not in result or
1✔
232
        'statuscode' not in result['info'] or
233
        result['info']['statuscode'] != 0
234
       ):
235
        return None
1✔
236

237
    address = parse_result_address(result)
1✔
238
    if(address is None):
1✔
239
        return None
1✔
240

241
    result['address'] = address
1✔
242
    result['latLng'] = parse_result_latlon(result)
1✔
243

244
    return result
1✔
245

246
def parse_result_address(result):
1✔
247
    # We want to store the city, state and country
248
    # The only way determined to identify an unfound address is 
249
    #   that none of the indicies were found
250
    if( 'results' not in result or
1✔
251
        len(result['results']) == 0 or
252
        'locations' not in result['results'][0] or
253
        len(result['results'][0]['locations']) == 0
254
        ):
255
        return None
×
256

257
    index_found = False
1✔
258
    addresses = {'city': None, 'state': None, 'country': None}
1✔
259
    result_compat = {}
1✔
260
    result_compat['address'] = {}
1✔
261

262

263
    locations = result['results'][0]['locations'][0]
1✔
264
    # We are looping over locations to find the adminAreaNType key which
265
    #   has a value of City, State or Country.
266
    # Once we find it then we obtain the value from the key adminAreaN
267
    #   where N is a numeric index.
268
    # For example
269
    #   * adminArea1Type = 'City'
270
    #   * adminArea1 = 'Sunnyvale'
271
    for key in locations:
1✔
272
        # Check if the key is of the form adminArea1Type
273
        if(key[-4:] == 'Type'):
1✔
274
            # If it's a type then check if it corresponds to one we are intereated in
275
            #   and store the index by parsing the key
276
            key_prefix = key[:-4]
1✔
277
            key_index = key[-5:-4]
1✔
278
            if(locations[key].lower() in addresses):
1✔
279
                addresses[locations[key].lower()] = locations[key_prefix]
1✔
280
                index_found = True
1✔
281

282
    if(index_found is False):
1✔
283
        return None
1✔
284

285
    return addresses
1✔
286

287
def parse_result_latlon(result):
1✔
288
    if( 'results' not in result or
1✔
289
        len(result['results']) == 0 or
290
        'locations' not in result['results'][0] or
291
        len(result['results'][0]['locations']) == 0 or
292
        'latLng' not in result['results'][0]['locations'][0]
293
        ):
294
        return None
×
295

296
    latLng = result['results'][0]['locations'][0]['latLng'];
1✔
297

298
    return {'lat': latLng['lat'], 'lon': latLng['lng']}
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2023 Coveralls, Inc