2016-01-08 23:49:06 +01:00
|
|
|
"""Look up geolocation information for media objects."""
|
2016-03-12 20:09:28 +01:00
|
|
|
from past.utils import old_div
|
2016-01-08 23:49:06 +01:00
|
|
|
|
2016-11-08 05:34:25 +01:00
|
|
|
|
2015-10-08 11:21:31 +02:00
|
|
|
from os import path
|
2016-08-22 08:10:45 +02:00
|
|
|
|
2021-04-16 20:22:27 +02:00
|
|
|
import geopy
|
|
|
|
from geopy.geocoders import Nominatim
|
2021-07-31 20:30:30 +02:00
|
|
|
import logging
|
2015-10-08 11:21:31 +02:00
|
|
|
|
2021-07-30 07:41:02 +02:00
|
|
|
from dozo import constants
|
2021-07-31 20:11:39 +02:00
|
|
|
from dozo.config import load_config, get_geocoder
|
2015-10-28 08:19:21 +01:00
|
|
|
|
2016-09-14 05:10:29 +02:00
|
|
|
__KEY__ = None
|
2017-03-30 16:13:34 +02:00
|
|
|
__DEFAULT_LOCATION__ = 'Unknown Location'
|
2019-01-22 05:27:38 +01:00
|
|
|
__PREFER_ENGLISH_NAMES__ = None
|
2016-09-14 05:10:29 +02:00
|
|
|
|
2016-01-02 08:23:06 +01:00
|
|
|
|
2021-06-12 19:49:29 +02:00
|
|
|
def coordinates_by_name(name, db):
|
2015-12-29 09:07:50 +01:00
|
|
|
# Try to get cached location first
|
|
|
|
cached_coordinates = db.get_location_coordinates(name)
|
|
|
|
if(cached_coordinates is not None):
|
|
|
|
return {
|
2016-01-02 18:34:43 +01:00
|
|
|
'latitude': cached_coordinates[0],
|
|
|
|
'longitude': cached_coordinates[1]
|
2015-12-29 09:07:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
# If the name is not cached then we go ahead with an API lookup
|
2021-04-16 20:22:27 +02:00
|
|
|
geocoder = get_geocoder()
|
|
|
|
if geocoder == 'Nominatim':
|
|
|
|
locator = Nominatim(user_agent='myGeocoder')
|
|
|
|
geolocation_info = locator.geocode(name)
|
|
|
|
if geolocation_info is not None:
|
2015-10-14 05:26:55 +02:00
|
|
|
return {
|
2021-04-16 20:22:27 +02:00
|
|
|
'latitude': geolocation_info.latitude,
|
|
|
|
'longitude': geolocation_info.longitude
|
2015-10-14 05:26:55 +02:00
|
|
|
}
|
2021-08-13 19:12:42 +02:00
|
|
|
else:
|
|
|
|
raise NameError(geocoder)
|
2016-01-02 08:23:06 +01:00
|
|
|
|
2015-10-14 05:26:55 +02:00
|
|
|
return None
|
|
|
|
|
2016-01-02 08:23:06 +01:00
|
|
|
|
2016-06-21 20:19:40 +02:00
|
|
|
def decimal_to_dms(decimal):
|
|
|
|
decimal = float(decimal)
|
|
|
|
decimal_abs = abs(decimal)
|
2016-06-24 06:31:58 +02:00
|
|
|
minutes, seconds = divmod(decimal_abs*3600, 60)
|
|
|
|
degrees, minutes = divmod(minutes, 60)
|
2016-06-21 20:19:40 +02:00
|
|
|
degrees = degrees
|
|
|
|
sign = 1 if decimal >= 0 else -1
|
2016-06-24 06:31:58 +02:00
|
|
|
return (degrees, minutes, seconds, sign)
|
2015-10-14 05:26:55 +02:00
|
|
|
|
2016-01-02 08:23:06 +01:00
|
|
|
|
|
|
|
def dms_to_decimal(degrees, minutes, seconds, direction=' '):
|
|
|
|
sign = 1
|
2016-02-12 20:22:26 +01:00
|
|
|
if(direction[0] in 'WSws'):
|
2016-01-02 08:23:06 +01:00
|
|
|
sign = -1
|
|
|
|
return (
|
2016-08-22 08:10:45 +02:00
|
|
|
float(degrees) + old_div(float(minutes), 60) +
|
|
|
|
old_div(float(seconds), 3600)
|
2016-01-02 08:23:06 +01:00
|
|
|
) * sign
|
|
|
|
|
|
|
|
|
2016-06-21 20:19:40 +02:00
|
|
|
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)
|
|
|
|
|
|
|
|
|
2019-01-22 05:27:38 +01:00
|
|
|
def get_prefer_english_names():
|
|
|
|
global __PREFER_ENGLISH_NAMES__
|
|
|
|
if __PREFER_ENGLISH_NAMES__ is not None:
|
|
|
|
return __PREFER_ENGLISH_NAMES__
|
|
|
|
|
2021-04-16 20:02:14 +02:00
|
|
|
config = load_config(constants.CONFIG_FILE)
|
|
|
|
if('prefer_english_names' not in config['Geolocation']):
|
2019-01-22 05:27:38 +01:00
|
|
|
return False
|
|
|
|
|
2021-04-16 20:02:14 +02:00
|
|
|
__PREFER_ENGLISH_NAMES__ = bool(config['Geolocation']['prefer_english_names'])
|
2019-01-22 05:27:38 +01:00
|
|
|
return __PREFER_ENGLISH_NAMES__
|
2016-01-02 08:23:06 +01:00
|
|
|
|
2021-07-31 21:26:04 +02:00
|
|
|
|
2021-07-30 07:41:02 +02:00
|
|
|
def place_name(lat, lon, db, cache=True, logger=logging.getLogger()):
|
2017-03-30 16:13:34 +02:00
|
|
|
lookup_place_name_default = {'default': __DEFAULT_LOCATION__}
|
|
|
|
if(lat is None or lon is None):
|
|
|
|
return lookup_place_name_default
|
|
|
|
|
2016-04-21 07:23:57 +02:00
|
|
|
# Convert lat/lon to floats
|
2017-03-30 16:13:34 +02:00
|
|
|
if(not isinstance(lat, float)):
|
2016-04-21 07:23:57 +02:00
|
|
|
lat = float(lat)
|
2017-03-30 16:13:34 +02:00
|
|
|
if(not isinstance(lon, float)):
|
2016-04-21 07:23:57 +02:00
|
|
|
lon = float(lon)
|
2015-12-24 20:39:28 +01:00
|
|
|
|
|
|
|
# Try to get cached location first
|
|
|
|
# 3km distace radious for a match
|
2021-07-30 07:41:02 +02:00
|
|
|
cached_place_name = None
|
|
|
|
if cache:
|
|
|
|
cached_place_name = db.get_location_name(lat, lon, 3000)
|
2017-01-03 05:58:52 +01:00
|
|
|
# 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)):
|
2015-12-24 20:39:28 +01:00
|
|
|
return cached_place_name
|
|
|
|
|
2017-01-03 05:58:52 +01:00
|
|
|
lookup_place_name = {}
|
2021-04-16 20:22:27 +02:00
|
|
|
geocoder = get_geocoder()
|
|
|
|
if geocoder == 'Nominatim':
|
2021-07-31 20:30:30 +02:00
|
|
|
geolocation_info = lookup_osm(lat, lon, logger)
|
2021-04-16 20:22:27 +02:00
|
|
|
else:
|
2021-08-13 19:12:42 +02:00
|
|
|
raise NameError(geocoder)
|
2021-04-16 20:22:27 +02:00
|
|
|
|
2017-03-30 16:13:34 +02:00
|
|
|
if(geolocation_info is not None and 'address' in geolocation_info):
|
|
|
|
address = geolocation_info['address']
|
2020-10-23 09:48:19 +02:00
|
|
|
# gh-386 adds support for town
|
|
|
|
# taking precedence after city for backwards compatability
|
2021-04-16 20:22:27 +02:00
|
|
|
for loc in ['city', 'town', 'village', 'state', 'country']:
|
2017-03-30 16:13:34 +02:00
|
|
|
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]
|
2017-01-03 05:58:52 +01:00
|
|
|
|
2017-09-08 01:59:16 +02:00
|
|
|
if(lookup_place_name):
|
2015-12-24 20:39:28 +01:00
|
|
|
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()
|
2017-04-07 22:40:55 +02:00
|
|
|
|
|
|
|
if('default' not in lookup_place_name):
|
|
|
|
lookup_place_name = lookup_place_name_default
|
|
|
|
|
2015-12-24 20:39:28 +01:00
|
|
|
return lookup_place_name
|
2015-10-14 05:26:55 +02:00
|
|
|
|
2021-07-31 21:26:04 +02:00
|
|
|
|
2021-07-30 07:41:02 +02:00
|
|
|
def lookup_osm(lat, lon, logger=logging.getLogger()):
|
2021-04-16 20:22:27 +02:00
|
|
|
|
|
|
|
prefer_english_names = get_prefer_english_names()
|
|
|
|
try:
|
|
|
|
locator = Nominatim(user_agent='myGeocoder')
|
|
|
|
coords = (lat, lon)
|
|
|
|
if(prefer_english_names):
|
|
|
|
lang='en'
|
|
|
|
else:
|
|
|
|
lang='local'
|
|
|
|
return locator.reverse(coords, language=lang).raw
|
|
|
|
except geopy.exc.GeocoderUnavailable as e:
|
2021-07-30 07:41:02 +02:00
|
|
|
logger.error(e)
|
2021-04-16 20:22:27 +02:00
|
|
|
return None
|
2021-08-13 19:12:42 +02:00
|
|
|
# Fix *** TypeError: `address` must not be None
|
|
|
|
except (TypeError, ValueError) as e:
|
2021-07-30 07:41:02 +02:00
|
|
|
logger.error(e)
|
2021-04-16 20:22:27 +02:00
|
|
|
return None
|
|
|
|
|
2015-10-14 05:26:55 +02:00
|
|
|
|