From 8b30107a3efa6d8aa9a65439239c45c6801b27b9 Mon Sep 17 00:00:00 2001 From: Zingo Andersen Date: Thu, 24 Dec 2015 20:39:28 +0100 Subject: [PATCH] Add geolocation cache for distances below 3km By reusing lookup's the server need to be contacted a lot less Aslo names get a bit clustered and match together a bit better. --- elodie/constants.py | 1 + elodie/geolocation.py | 23 +++++++++++++--- elodie/localstorage.py | 60 ++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 78 insertions(+), 6 deletions(-) diff --git a/elodie/constants.py b/elodie/constants.py index ebb7c49..9622635 100644 --- a/elodie/constants.py +++ b/elodie/constants.py @@ -3,5 +3,6 @@ from os import path debug = True application_directory = '{}/.elodie'.format(path.expanduser('~')) hash_db = '{}/hash.json'.format(application_directory) +location_db = '{}/location.json'.format(application_directory) script_directory = path.dirname(path.dirname(path.abspath(__file__))) exiftool_config = '%s/configs/ExifTool_config' % script_directory diff --git a/elodie/geolocation.py b/elodie/geolocation.py index 34b6819..e83232d 100644 --- a/elodie/geolocation.py +++ b/elodie/geolocation.py @@ -9,6 +9,7 @@ import sys import urllib from elodie import constants +from elodie.localstorage import Db class Fraction(fractions.Fraction): """Only create Fractions from floats. @@ -84,17 +85,31 @@ def get_key(): return config.get('MapQuest', 'key') def place_name(lat, 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) + if(cached_place_name is not None): + return cached_place_name + + lookup_place_name = None; geolocation_info = reverse_lookup(lat, lon) if(geolocation_info is not None): if('address' in geolocation_info): address = geolocation_info['address'] if('city' in address): - return address['city'] + lookup_place_name = address['city'] elif('state' in address): - return address['state'] + lookup_place_name = address['state'] elif('country' in address): - return address['country'] - return None + lookup_place_name = address['country'] + + if(lookup_place_name is not None): + 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() + return lookup_place_name def reverse_lookup(lat, lon): diff --git a/elodie/localstorage.py b/elodie/localstorage.py index 3ca8375..eb343b5 100644 --- a/elodie/localstorage.py +++ b/elodie/localstorage.py @@ -1,7 +1,7 @@ import hashlib import json import os - +from math import radians, cos, sqrt from elodie import constants class Db(object): @@ -24,7 +24,22 @@ class Db(object): self.hash_db = json.load(f) except ValueError: pass - + + # If the location db doesn't exist we create it. + # Otherwise we only open for reading + if not os.path.isfile(constants.location_db): + with open(constants.location_db, 'a'): + os.utime(constants.location_db, None) + + self.location_db = [] + + # We know from above that this file exists so we open it for reading only. + with open(constants.location_db, 'r') as f: + try: + self.location_db = json.load(f) + except ValueError: + pass + def add_hash(self, key, value, write=False): self.hash_db[key] = value if(write == True): @@ -55,3 +70,44 @@ class Db(object): buf = f.read(blocksize) return hasher.hexdigest() return None + + # Location database + # Currently quite simple just a list of long/lat pairs with a name + # If it gets many entryes a lookup might takt to long and a better + # structure might be needed. Some speed up ideas: + # - Sort it and inter-half method can be used + # - Use integer part of long or lat as key to get a lower search list + # - Cache a smal number of lookups, photos is likey to be taken i clusters + # around a spot during import. + + def add_location(self, latitude, longitude, place, write=False): + data = {} + data['lat'] = latitude + data['long'] = longitude + data['name'] = place + self.location_db.append(data) + if(write == True): + self.update_location_db() + + def get_location_name(self, latitude, longitude,threshold_m): + for data in self.location_db: + # As threshold is quite smal use simple math + # From http://stackoverflow.com/questions/15736995/how-can-i-quickly-estimate-the-distance-between-two-latitude-longitude-points + # convert decimal degrees to radians + + lon1, lat1, lon2, lat2 = map(radians, [longitude, latitude, data['long'], data['lat']]) + + R = 6371000 # radius of the earth in m + x = (lon2 - lon1) * cos( 0.5*(lat2+lat1) ) + y = lat2 - lat1 + d = R * sqrt( x*x + y*y ) + # Use if closer then threshold_km reuse lookup + if(d<=threshold_m): + #print "Found in cached location dist: %d m" % d + return data['name']; + return None + + + def update_location_db(self): + with open(constants.location_db, 'w') as f: + json.dump(self.location_db, f)