coordinates_by_name()   F
last analyzed

Complexity

Conditions 12

Duplication

Lines 0
Ratio 0 %

Size

Total Lines 44

Importance

Changes 0
Metric Value
cc 12
dl 0
loc 44
rs 2.7855
c 0
b 0
f 0

How to fix   Complexity   

Complexity

Complex classes like coordinates_by_name() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
"""Look up geolocation information for media objects."""
2
from __future__ import print_function
3
from __future__ import division
4
from future import standard_library
5
from past.utils import old_div
6
7
standard_library.install_aliases()  # noqa
8
9
from os import path
10
11
import requests
12
import urllib.request
13
import urllib.parse
14
import urllib.error
15
16
from elodie.config import load_config
17
from elodie import constants
18
from elodie import log
19
from elodie.localstorage import Db
20
21
__KEY__ = None
22
__DEFAULT_LOCATION__ = 'Unknown Location'
23
24
25
def coordinates_by_name(name):
26
    # Try to get cached location first
27
    db = Db()
28
    cached_coordinates = db.get_location_coordinates(name)
29
    if(cached_coordinates is not None):
30
        return {
31
            'latitude': cached_coordinates[0],
32
            'longitude': cached_coordinates[1]
33
        }
34
35
    # If the name is not cached then we go ahead with an API lookup
36
    geolocation_info = lookup(location=name)
37
38
    if(geolocation_info is not None):
39
        if(
40
            'results' in geolocation_info and
41
            len(geolocation_info['results']) != 0 and
42
            'locations' in geolocation_info['results'][0] and
43
            len(geolocation_info['results'][0]['locations']) != 0
44
        ):
45
46
            # By default we use the first entry unless we find one with
47
            #   geocodeQuality=city.
48
            geolocation_result = geolocation_info['results'][0]
49
            use_location = geolocation_result['locations'][0]['latLng']
50
            # Loop over the locations to see if we come accross a
51
            #   geocodeQuality=city.
52
            # If we find a city we set that to the use_location and break
53
            for location in geolocation_result['locations']:
54
                if(
55
                    'latLng' in location and
56
                    'lat' in location['latLng'] and
57
                    'lng' in location['latLng'] and
58
                    location['geocodeQuality'].lower() == 'city'
59
                ):
60
                    use_location = location['latLng']
61
                    break
62
63
            return {
64
                'latitude': use_location['lat'],
65
                'longitude': use_location['lng']
66
            }
67
68
    return None
69
70
71
def decimal_to_dms(decimal):
72
    decimal = float(decimal)
73
    decimal_abs = abs(decimal)
74
    minutes, seconds = divmod(decimal_abs*3600, 60)
75
    degrees, minutes = divmod(minutes, 60)
76
    degrees = degrees
77
    sign = 1 if decimal >= 0 else -1
78
    return (degrees, minutes, seconds, sign)
79
80
81
def dms_to_decimal(degrees, minutes, seconds, direction=' '):
82
    sign = 1
83
    if(direction[0] in 'WSws'):
84
        sign = -1
85
    return (
86
        float(degrees) + old_div(float(minutes), 60) +
87
        old_div(float(seconds), 3600)
88
    ) * sign
89
90
91
def dms_string(decimal, type='latitude'):
92
    # Example string -> 38 deg 14' 27.82" S
93
    dms = decimal_to_dms(decimal)
94
    if type == 'latitude':
95
        direction = 'N' if decimal >= 0 else 'S'
96
    elif type == 'longitude':
97
        direction = 'E' if decimal >= 0 else 'W'
98
    return '{} deg {}\' {}" {}'.format(dms[0], dms[1], dms[2], direction)
99
100
101
def get_key():
102
    global __KEY__
103
    if __KEY__ is not None:
104
        return __KEY__
105
106
    config_file = '%s/config.ini' % constants.application_directory
107
    if not path.exists(config_file):
108
        return None
109
110
    config = load_config()
111
    if('MapQuest' not in config):
112
        return None
113
114
    __KEY__ = config['MapQuest']['key']
115
    return __KEY__
116
117
118
def place_name(lat, lon):
119
    lookup_place_name_default = {'default': __DEFAULT_LOCATION__}
120
    if(lat is None or lon is None):
121
        return lookup_place_name_default
122
123
    # Convert lat/lon to floats
124
    if(not isinstance(lat, float)):
125
        lat = float(lat)
126
    if(not isinstance(lon, float)):
127
        lon = float(lon)
128
129
    # Try to get cached location first
130
    db = Db()
131
    # 3km distace radious for a match
132
    cached_place_name = db.get_location_name(lat, lon, 3000)
133
    # We check that it's a dict to coerce an upgrade of the location
134
    #  db from a string location to a dictionary. See gh-160.
135
    if(isinstance(cached_place_name, dict)):
136
        return cached_place_name
137
138
    lookup_place_name = {}
139
    geolocation_info = lookup(lat=lat, lon=lon)
140
    if(geolocation_info is not None and 'address' in geolocation_info):
141
        address = geolocation_info['address']
142
        for loc in ['city', 'state', 'country']:
143
            if(loc in address):
144
                lookup_place_name[loc] = address[loc]
145
                # In many cases the desired key is not available so we
146
                #  set the most specific as the default.
147
                if('default' not in lookup_place_name):
148
                    lookup_place_name['default'] = address[loc]
149
150
    if(lookup_place_name is not {}):
151
        db.add_location(lat, lon, lookup_place_name)
152
        # TODO: Maybe this should only be done on exit and not for every write.
153
        db.update_location_db()
154
155
    if('default' not in lookup_place_name):
156
        lookup_place_name = lookup_place_name_default
157
158
    return lookup_place_name
159
160
161
def lookup(**kwargs):
162
    if(
163
        'location' not in kwargs and
164
        'lat' not in kwargs and
165
        'lon' not in kwargs
166
    ):
167
        return None
168
169
    key = get_key()
170
171
    if(key is None):
172
        return None
173
174
    try:
175
        params = {'format': 'json', 'key': key}
176
        params.update(kwargs)
177
        path = '/geocoding/v1/address'
178
        if('lat' in kwargs and 'lon' in kwargs):
179
            path = '/nominatim/v1/reverse.php'
180
        url = 'http://open.mapquestapi.com%s?%s' % (
181
                    path,
182
                    urllib.parse.urlencode(params)
183
              )
184
        r = requests.get(url)
185
        return parse_result(r.json())
186
    except requests.exceptions.RequestException as e:
187
        log.error(e)
188
        return None
189
    except ValueError as e:
190
        log.error(r.text)
191
        log.error(e)
192
        return None
193
194
195
def parse_result(result):
196
    if('error' in result):
197
        return None
198
199
    if(
200
        'results' in result and
201
        len(result['results']) > 0 and
202
        'locations' in result['results'][0]
203
        and len(result['results'][0]['locations']) > 0 and
204
        'latLng' in result['results'][0]['locations'][0]
205
    ):
206
        latLng = result['results'][0]['locations'][0]['latLng']
207
        if(latLng['lat'] == 39.78373 and latLng['lng'] == -100.445882):
208
            return None
209
210
    return result
211