Added geocodio geocoder to the Python library
This commit is contained in:
@@ -2,10 +2,12 @@ from google import GoogleMapsBulkGeocoder
|
|||||||
from here import HereMapsBulkGeocoder
|
from here import HereMapsBulkGeocoder
|
||||||
from tomtom import TomTomBulkGeocoder
|
from tomtom import TomTomBulkGeocoder
|
||||||
from mapbox import MapboxBulkGeocoder
|
from mapbox import MapboxBulkGeocoder
|
||||||
|
from geocodio import GeocodioBulkGeocoder
|
||||||
|
|
||||||
BATCH_GEOCODER_CLASS_BY_PROVIDER = {
|
BATCH_GEOCODER_CLASS_BY_PROVIDER = {
|
||||||
'google': GoogleMapsBulkGeocoder,
|
'google': GoogleMapsBulkGeocoder,
|
||||||
'heremaps': HereMapsBulkGeocoder,
|
'heremaps': HereMapsBulkGeocoder,
|
||||||
'tomtom': TomTomBulkGeocoder,
|
'tomtom': TomTomBulkGeocoder,
|
||||||
'mapbox': MapboxBulkGeocoder
|
'mapbox': MapboxBulkGeocoder,
|
||||||
|
'geocodio': GeocodioBulkGeocoder,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
from geocoder import GeocodioGeocoder
|
||||||
|
from bulk_geocoder import GeocodioBulkGeocoder
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
import requests
|
||||||
|
from cartodb_services import StreetPointBulkGeocoder
|
||||||
|
from cartodb_services.geocodio import GeocodioGeocoder
|
||||||
|
from iso3166 import countries
|
||||||
|
from cartodb_services.tools.country import country_to_iso3
|
||||||
|
|
||||||
|
|
||||||
|
class GeocodioBulkGeocoder(GeocodioGeocoder, StreetPointBulkGeocoder):
|
||||||
|
MAX_BATCH_SIZE = 100 # Setting an upper limit (not stated in the documentation)
|
||||||
|
MIN_BATCHED_SEARCH = 0
|
||||||
|
READ_TIMEOUT = 60
|
||||||
|
CONNECT_TIMEOUT = 10
|
||||||
|
MAX_RETRIES = 1
|
||||||
|
|
||||||
|
def __init__(self, token, logger, service_params=None):
|
||||||
|
GeocodioGeocoder.__init__(self, token, logger, service_params)
|
||||||
|
|
||||||
|
self.connect_timeout = self.CONNECT_TIMEOUT
|
||||||
|
self.read_timeout = self.READ_TIMEOUT
|
||||||
|
self.max_retries = self.MAX_RETRIES
|
||||||
|
|
||||||
|
if service_params is not None:
|
||||||
|
self.connect_timeout = service_params.get('connect_timeout', self.CONNECT_TIMEOUT)
|
||||||
|
self.read_timeout = service_params.get('read_timeout', self.READ_TIMEOUT)
|
||||||
|
self.max_retries = service_params.get('max_retries', self.MAX_RETRIES)
|
||||||
|
|
||||||
|
self.session = requests.Session()
|
||||||
|
|
||||||
|
def _should_use_batch(self, searches):
|
||||||
|
return len(searches) >= self.MIN_BATCHED_SEARCH
|
||||||
|
|
||||||
|
def _serial_geocode(self, searches):
|
||||||
|
results = []
|
||||||
|
for search in searches:
|
||||||
|
elements = self._encoded_elements(search)
|
||||||
|
result = self.geocode_meta(*elements)
|
||||||
|
|
||||||
|
if result:
|
||||||
|
results.append((search[0], result[0], result[1]))
|
||||||
|
else:
|
||||||
|
results.append((search[0], None, None))
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
def _encoded_elements(self, search):
|
||||||
|
(search_id, address, city, state, country) = search
|
||||||
|
address = address.encode('utf-8') if address else None
|
||||||
|
city = city.encode('utf-8') if city else None
|
||||||
|
state = state.encode('utf-8') if state else None
|
||||||
|
country = self._country_code(country) if country else None
|
||||||
|
return address, city, state, country
|
||||||
|
|
||||||
|
def _batch_geocode(self, searches):
|
||||||
|
if len(searches) == 1:
|
||||||
|
return self._serial_geocode(searches)
|
||||||
|
else:
|
||||||
|
frees = []
|
||||||
|
for search in searches:
|
||||||
|
elements = self._encoded_elements(search)
|
||||||
|
free = ', '.join([elem for elem in elements if elem])
|
||||||
|
frees.append(free)
|
||||||
|
|
||||||
|
full_results = self.geocode_free_text_meta(frees)
|
||||||
|
results = []
|
||||||
|
for s, r in zip(searches, full_results):
|
||||||
|
results.append((s[0], r[0], r[1]))
|
||||||
|
return results
|
||||||
|
|
||||||
|
def _country_code(self, country):
|
||||||
|
country_iso3166 = None
|
||||||
|
country_iso3 = country_to_iso3(country)
|
||||||
|
if country_iso3:
|
||||||
|
country_iso3166 = countries.get(country_iso3).alpha2.lower()
|
||||||
|
|
||||||
|
return country_iso3166
|
||||||
|
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
from geocodio import GeocodioClient
|
||||||
|
from geocodio.exceptions import GeocodioAuthError, GeocodioServerError, GeocodioDataError, GeocodioError
|
||||||
|
|
||||||
|
from cartodb_services.tools.qps import qps_retry
|
||||||
|
from cartodb_services.metrics import Traceable
|
||||||
|
from cartodb_services.geocoder import EMPTY_RESPONSE, geocoder_metadata
|
||||||
|
from cartodb_services.tools.exceptions import ServiceException
|
||||||
|
|
||||||
|
|
||||||
|
RELEVANCE_BY_LOCATION_TYPE = {
|
||||||
|
'rooftop': 1,
|
||||||
|
'point': 0.9,
|
||||||
|
'range_interpolation': 0.8,
|
||||||
|
'nearest_rooftop_match': 0.7,
|
||||||
|
'intersection': 0.6,
|
||||||
|
'street_center': 0.5,
|
||||||
|
'place': 0.4,
|
||||||
|
'state': 0.1,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class GeocodioGeocoder(Traceable):
|
||||||
|
'''
|
||||||
|
Python wrapper for the Geocodio Geocoder service.
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, token, logger, service_params=None):
|
||||||
|
service_params = service_params or {}
|
||||||
|
self._token = token
|
||||||
|
self._logger = logger
|
||||||
|
|
||||||
|
self._geocoder = GeocodioClient(self._token)
|
||||||
|
|
||||||
|
def _validate_input(self, searchtext, city=None, state_province=None,
|
||||||
|
country=None):
|
||||||
|
if searchtext and searchtext.strip():
|
||||||
|
return True
|
||||||
|
elif city:
|
||||||
|
return True
|
||||||
|
elif state_province:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
@qps_retry(qps=15, provider='geocodio')
|
||||||
|
def geocode(self, searchtext, city=None, state_province=None,
|
||||||
|
country=None):
|
||||||
|
return self._geocode_meta(searchtext, city, state_province, country)[0]
|
||||||
|
|
||||||
|
def geocode_meta(self, searchtext, city=None, state_province=None,
|
||||||
|
country=None):
|
||||||
|
return self._geocode_meta(searchtext, city, state_province, country)[0]
|
||||||
|
|
||||||
|
@qps_retry(qps=15, provider='geocodio')
|
||||||
|
def _geocode_meta(self, searchtext, city=None, state_province=None,
|
||||||
|
country=None):
|
||||||
|
if not self._validate_input(searchtext, city, state_province, country):
|
||||||
|
return EMPTY_RESPONSE
|
||||||
|
|
||||||
|
try:
|
||||||
|
free_text_components = [searchtext, city, state_province, country]
|
||||||
|
response = self._geocoder.geocode(';'.join([c for c in free_text_components if c is not None and c.strip()]))
|
||||||
|
|
||||||
|
return self._parse_geocoder_response(response)
|
||||||
|
except GeocodioDataError as gde:
|
||||||
|
return EMPTY_RESPONSE
|
||||||
|
except GeocodioAuthError as gae:
|
||||||
|
raise ServiceException('Geocodio authorization error: ' + str(gae), None)
|
||||||
|
except GeocodioServerError as gse:
|
||||||
|
raise ServiceException('geocodio server error: ' + str(gse), None)
|
||||||
|
except GeocodioError as ge:
|
||||||
|
raise ServiceException('Unknown Geocodio error: ' + str(ge), None)
|
||||||
|
|
||||||
|
@qps_retry(qps=15)
|
||||||
|
def geocode_free_text_meta(self, free_searches, country=None):
|
||||||
|
"""
|
||||||
|
:param free_searches: Free text searches
|
||||||
|
:return: list of [x, y] on success, [] on error
|
||||||
|
"""
|
||||||
|
output = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
if country:
|
||||||
|
free_searches = ['{s}, {country}'.format(s, country) for s in free_searches]
|
||||||
|
|
||||||
|
responses = self._geocoder.geocode(free_searches)
|
||||||
|
|
||||||
|
for response in responses:
|
||||||
|
output.append(self._parse_geocoder_response(response))
|
||||||
|
except GeocodioDataError as gde:
|
||||||
|
return EMPTY_RESPONSE
|
||||||
|
except GeocodioAuthError as gae:
|
||||||
|
raise ServiceException('Geocodio authorization error: ' + str(gae), None)
|
||||||
|
except GeocodioServerError as gse:
|
||||||
|
raise ServiceException('geocodio server error: ' + str(gse), None)
|
||||||
|
except GeocodioError as ge:
|
||||||
|
raise ServiceException('Unknown Geocodio error: ' + str(ge), None)
|
||||||
|
|
||||||
|
return output
|
||||||
|
|
||||||
|
def _parse_geocoder_response(self, response):
|
||||||
|
if response is None or not response:
|
||||||
|
return EMPTY_RESPONSE
|
||||||
|
|
||||||
|
if response.get('results') is None or not response.get('results'):
|
||||||
|
return EMPTY_RESPONSE
|
||||||
|
|
||||||
|
if response.coords is None or not response.coords:
|
||||||
|
return EMPTY_RESPONSE
|
||||||
|
|
||||||
|
coords = [None, None]
|
||||||
|
accuracy = None
|
||||||
|
accuracy_type = None
|
||||||
|
|
||||||
|
accuracy = response.accuracy
|
||||||
|
|
||||||
|
if response.coords is not None and response.coords:
|
||||||
|
coords = [response.coords[1], response.coords[0]]
|
||||||
|
|
||||||
|
if response.get('results'):
|
||||||
|
accuracy_type = response.get('results')[0].get('accuracy_type')
|
||||||
|
|
||||||
|
metadata = geocoder_metadata(RELEVANCE_BY_LOCATION_TYPE.get(accuracy_type), response.accuracy, accuracy_type)
|
||||||
|
|
||||||
|
return [coords, metadata]
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
GEOCODIO_GEOCODER_APIKEY_ROUNDROBIN = 'geocodio_geocoder_apikey_roundrobin'
|
||||||
@@ -405,6 +405,8 @@ class GeocoderConfig(ServiceConfig):
|
|||||||
MAPBOX_GEOCODER_API_KEYS = 'mapbox_geocoder_api_keys'
|
MAPBOX_GEOCODER_API_KEYS = 'mapbox_geocoder_api_keys'
|
||||||
TOMTOM_GEOCODER = 'tomtom'
|
TOMTOM_GEOCODER = 'tomtom'
|
||||||
TOMTOM_GEOCODER_API_KEYS = 'tomtom_geocoder_api_keys'
|
TOMTOM_GEOCODER_API_KEYS = 'tomtom_geocoder_api_keys'
|
||||||
|
GEOCODIO_GEOCODER = 'geocodio'
|
||||||
|
GEOCODIO_GEOCODER_API_KEYS = 'geocodio_geocoder_api_keys'
|
||||||
QUOTA_KEY = 'geocoding_quota'
|
QUOTA_KEY = 'geocoding_quota'
|
||||||
SOFT_LIMIT_KEY = 'soft_geocoding_limit'
|
SOFT_LIMIT_KEY = 'soft_geocoding_limit'
|
||||||
USERNAME_KEY = 'username'
|
USERNAME_KEY = 'username'
|
||||||
@@ -437,6 +439,9 @@ class GeocoderConfig(ServiceConfig):
|
|||||||
elif self._geocoder_provider == self.TOMTOM_GEOCODER:
|
elif self._geocoder_provider == self.TOMTOM_GEOCODER:
|
||||||
if not self.tomtom_api_keys:
|
if not self.tomtom_api_keys:
|
||||||
raise ConfigException("""TomTom config is not set up""")
|
raise ConfigException("""TomTom config is not set up""")
|
||||||
|
elif self._geocoder_provider == self.GEOCODIO_GEOCODER:
|
||||||
|
if not self.geocodio_api_keys:
|
||||||
|
raise ConfigException("""Geocodio config is not set up""")
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -476,6 +481,10 @@ class GeocoderConfig(ServiceConfig):
|
|||||||
self._tomtom_api_keys = db_config.tomtom_geocoder_api_keys
|
self._tomtom_api_keys = db_config.tomtom_geocoder_api_keys
|
||||||
self._cost_per_hit = 0
|
self._cost_per_hit = 0
|
||||||
self._tomtom_service_params = db_config.tomtom_geocoder_service_params
|
self._tomtom_service_params = db_config.tomtom_geocoder_service_params
|
||||||
|
elif self._geocoder_provider == self.GEOCODIO_GEOCODER:
|
||||||
|
self._geocodio_api_keys = db_config.geocodio_geocoder_api_keys
|
||||||
|
self._cost_per_hit = 0
|
||||||
|
self._geocodio_service_params = db_config.geocodio_geocoder_service_params
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def service_type(self):
|
def service_type(self):
|
||||||
@@ -489,6 +498,8 @@ class GeocoderConfig(ServiceConfig):
|
|||||||
return 'geocoder_tomtom'
|
return 'geocoder_tomtom'
|
||||||
elif self._geocoder_provider == self.NOKIA_GEOCODER:
|
elif self._geocoder_provider == self.NOKIA_GEOCODER:
|
||||||
return 'geocoder_here'
|
return 'geocoder_here'
|
||||||
|
elif self._geocoder_provider == self.GEOCODIO_GEOCODER:
|
||||||
|
return 'geocoder_geocodio'
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def heremaps_geocoder(self):
|
def heremaps_geocoder(self):
|
||||||
@@ -510,6 +521,10 @@ class GeocoderConfig(ServiceConfig):
|
|||||||
def tomtom_geocoder(self):
|
def tomtom_geocoder(self):
|
||||||
return self._geocoder_provider == self.TOMTOM_GEOCODER
|
return self._geocoder_provider == self.TOMTOM_GEOCODER
|
||||||
|
|
||||||
|
@property
|
||||||
|
def geocodio_geocoder(self):
|
||||||
|
return self._geocoder_provider == self.GEOCODIO_GEOCODER
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def google_client_id(self):
|
def google_client_id(self):
|
||||||
return self._google_maps_client_id
|
return self._google_maps_client_id
|
||||||
@@ -569,6 +584,14 @@ class GeocoderConfig(ServiceConfig):
|
|||||||
def tomtom_service_params(self):
|
def tomtom_service_params(self):
|
||||||
return self._tomtom_service_params
|
return self._tomtom_service_params
|
||||||
|
|
||||||
|
@property
|
||||||
|
def geocodio_api_keys(self):
|
||||||
|
return self._geocodio_api_keys
|
||||||
|
|
||||||
|
@property
|
||||||
|
def geocodio_service_params(self):
|
||||||
|
return self._geocodio_service_params
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_high_resolution(self):
|
def is_high_resolution(self):
|
||||||
return True
|
return True
|
||||||
@@ -600,6 +623,7 @@ class ServicesDBConfig:
|
|||||||
self._get_mapzen_config()
|
self._get_mapzen_config()
|
||||||
self._get_mapbox_config()
|
self._get_mapbox_config()
|
||||||
self._get_tomtom_config()
|
self._get_tomtom_config()
|
||||||
|
self._get_geocodio_config()
|
||||||
self._get_data_observatory_config()
|
self._get_data_observatory_config()
|
||||||
|
|
||||||
def _get_server_config(self):
|
def _get_server_config(self):
|
||||||
@@ -679,6 +703,16 @@ class ServicesDBConfig:
|
|||||||
self._tomtom_geocoder_quota = tomtom_conf['geocoder']['monthly_quota']
|
self._tomtom_geocoder_quota = tomtom_conf['geocoder']['monthly_quota']
|
||||||
self._tomtom_geocoder_service_params = tomtom_conf['geocoder'].get('service', {})
|
self._tomtom_geocoder_service_params = tomtom_conf['geocoder'].get('service', {})
|
||||||
|
|
||||||
|
def _get_geocodio_config(self):
|
||||||
|
geocodio_conf_json = self._get_conf('geocodio_conf')
|
||||||
|
if not geocodio_conf_json:
|
||||||
|
raise ConfigException('Geocodio configuration missing')
|
||||||
|
else:
|
||||||
|
geocodio_conf = json.loads(geocodio_conf_json)
|
||||||
|
self._geocodio_geocoder_api_keys = geocodio_conf['geocoder']['api_keys']
|
||||||
|
self._geocodio_geocoder_quota = geocodio_conf['geocoder']['monthly_quota']
|
||||||
|
self._geocodio_geocoder_service_params = geocodio_conf['geocoder'].get('service', {})
|
||||||
|
|
||||||
def _get_data_observatory_config(self):
|
def _get_data_observatory_config(self):
|
||||||
do_conf_json = self._get_conf('data_observatory_conf')
|
do_conf_json = self._get_conf('data_observatory_conf')
|
||||||
if not do_conf_json:
|
if not do_conf_json:
|
||||||
@@ -848,6 +882,18 @@ class ServicesDBConfig:
|
|||||||
def tomtom_geocoder_service_params(self):
|
def tomtom_geocoder_service_params(self):
|
||||||
return self._tomtom_geocoder_service_params
|
return self._tomtom_geocoder_service_params
|
||||||
|
|
||||||
|
@property
|
||||||
|
def geocodio_geocoder_api_keys(self):
|
||||||
|
return self._geocodio_geocoder_api_keys
|
||||||
|
|
||||||
|
@property
|
||||||
|
def geocodio_geocoder_monthly_quota(self):
|
||||||
|
return self.geocodio_geocoder_quota
|
||||||
|
|
||||||
|
@property
|
||||||
|
def geocodio_geocoder_service_params(self):
|
||||||
|
return self._geocodio_geocoder_service_params
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def data_observatory_connection_str(self):
|
def data_observatory_connection_str(self):
|
||||||
return self._data_observatory_connection_str
|
return self._data_observatory_connection_str
|
||||||
|
|||||||
+128
@@ -0,0 +1,128 @@
|
|||||||
|
from dateutil.parser import parse as date_parse
|
||||||
|
from cartodb_services.refactor.service.utils import round_robin
|
||||||
|
from cartodb_services.geocodio.types import GEOCODIO_GEOCODER_APIKEY_ROUNDROBIN
|
||||||
|
|
||||||
|
|
||||||
|
class GeocodioGeocoderConfig(object):
|
||||||
|
"""
|
||||||
|
Configuration needed to operate the Geocodio geocoder service.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self,
|
||||||
|
geocoding_quota,
|
||||||
|
soft_geocoding_limit,
|
||||||
|
period_end_date,
|
||||||
|
cost_per_hit,
|
||||||
|
log_path,
|
||||||
|
geocodio_api_keys,
|
||||||
|
username,
|
||||||
|
organization,
|
||||||
|
service_params,
|
||||||
|
GD):
|
||||||
|
self._geocoding_quota = geocoding_quota
|
||||||
|
self._soft_geocoding_limit = soft_geocoding_limit
|
||||||
|
self._period_end_date = period_end_date
|
||||||
|
self._cost_per_hit = cost_per_hit
|
||||||
|
self._log_path = log_path
|
||||||
|
self._geocodio_api_keys = geocodio_api_keys
|
||||||
|
self._username = username
|
||||||
|
self._organization = organization
|
||||||
|
self._service_params = service_params
|
||||||
|
self._GD = GD
|
||||||
|
|
||||||
|
@property
|
||||||
|
def service_type(self):
|
||||||
|
return 'geocoder_geocodio'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def provider(self):
|
||||||
|
return 'geocodio'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_high_resolution(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def geocoding_quota(self):
|
||||||
|
return self._geocoding_quota
|
||||||
|
|
||||||
|
@property
|
||||||
|
def soft_geocoding_limit(self):
|
||||||
|
return self._soft_geocoding_limit
|
||||||
|
|
||||||
|
@property
|
||||||
|
def period_end_date(self):
|
||||||
|
return self._period_end_date
|
||||||
|
|
||||||
|
@property
|
||||||
|
def cost_per_hit(self):
|
||||||
|
return self._cost_per_hit
|
||||||
|
|
||||||
|
@property
|
||||||
|
def log_path(self):
|
||||||
|
return self._log_path
|
||||||
|
|
||||||
|
@property
|
||||||
|
def geocodio_api_key(self):
|
||||||
|
return round_robin(self._geocodio_api_keys, self._GD,
|
||||||
|
GEOCODIO_GEOCODER_APIKEY_ROUNDROBIN)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def username(self):
|
||||||
|
return self._username
|
||||||
|
|
||||||
|
@property
|
||||||
|
def organization(self):
|
||||||
|
return self._organization
|
||||||
|
|
||||||
|
@property
|
||||||
|
def service_params(self):
|
||||||
|
return self._service_params
|
||||||
|
|
||||||
|
# TODO: for BW compat, remove
|
||||||
|
@property
|
||||||
|
def google_geocoder(self):
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class GeocodioGeocoderConfigBuilder(object):
|
||||||
|
|
||||||
|
def __init__(self, server_conf, user_conf, org_conf, username, orgname, GD):
|
||||||
|
self._server_conf = server_conf
|
||||||
|
self._user_conf = user_conf
|
||||||
|
self._org_conf = org_conf
|
||||||
|
self._username = username
|
||||||
|
self._orgname = orgname
|
||||||
|
self._GD = GD
|
||||||
|
|
||||||
|
def get(self):
|
||||||
|
geocodio_server_conf = self._server_conf.get('geocodio_conf')
|
||||||
|
geocodio_api_keys = geocodio_server_conf['geocoder']['api_keys']
|
||||||
|
geocodio_service_params = geocodio_server_conf['geocoder'].get('service', {})
|
||||||
|
|
||||||
|
geocoding_quota = self._get_quota()
|
||||||
|
soft_geocoding_limit = self._user_conf.get('soft_geocoding_limit').lower() == 'true'
|
||||||
|
cost_per_hit = 0
|
||||||
|
period_end_date_str = self._org_conf.get('period_end_date') or self._user_conf.get('period_end_date')
|
||||||
|
period_end_date = date_parse(period_end_date_str)
|
||||||
|
|
||||||
|
logger_conf = self._server_conf.get('logger_conf')
|
||||||
|
log_path = logger_conf.get('geocoder_log_path', None)
|
||||||
|
|
||||||
|
return GeocodioGeocoderConfig(geocoding_quota,
|
||||||
|
soft_geocoding_limit,
|
||||||
|
period_end_date,
|
||||||
|
cost_per_hit,
|
||||||
|
log_path,
|
||||||
|
geocodio_api_keys,
|
||||||
|
self._username,
|
||||||
|
self._orgname,
|
||||||
|
geocodio_service_params,
|
||||||
|
self._GD)
|
||||||
|
|
||||||
|
def _get_quota(self):
|
||||||
|
geocoding_quota = self._org_conf.get('geocoding_quota') or self._user_conf.get('geocoding_quota')
|
||||||
|
if geocoding_quota is '':
|
||||||
|
return 0
|
||||||
|
|
||||||
|
return int(geocoding_quota)
|
||||||
@@ -7,6 +7,7 @@ rollbar==0.13.2
|
|||||||
requests==2.9.1
|
requests==2.9.1
|
||||||
rratelimit==0.0.4
|
rratelimit==0.0.4
|
||||||
mapbox==0.14.0
|
mapbox==0.14.0
|
||||||
|
pygeocodio==0.11.1
|
||||||
|
|
||||||
# Test
|
# Test
|
||||||
mock==1.3.0
|
mock==1.3.0
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from setuptools import setup, find_packages
|
|||||||
setup(
|
setup(
|
||||||
name='cartodb_services',
|
name='cartodb_services',
|
||||||
|
|
||||||
version='0.21.4',
|
version='0.22.0',
|
||||||
|
|
||||||
description='CartoDB Services API Python Library',
|
description='CartoDB Services API Python Library',
|
||||||
|
|
||||||
|
|||||||
@@ -9,3 +9,8 @@ def mapbox_api_key():
|
|||||||
def tomtom_api_key():
|
def tomtom_api_key():
|
||||||
"""Returns TomTom API key. Requires setting TOMTOM_API_KEY environment variable."""
|
"""Returns TomTom API key. Requires setting TOMTOM_API_KEY environment variable."""
|
||||||
return os.environ['TOMTOM_API_KEY']
|
return os.environ['TOMTOM_API_KEY']
|
||||||
|
|
||||||
|
|
||||||
|
def geocodio_api_key():
|
||||||
|
"""Returns Geocodio API key. Requires setting GEOCODIO_API_KEY environment variable."""
|
||||||
|
return os.environ['GEOCODIO_API_KEY']
|
||||||
|
|||||||
@@ -0,0 +1,208 @@
|
|||||||
|
import unittest
|
||||||
|
from mock import Mock
|
||||||
|
from cartodb_services.geocodio import GeocodioGeocoder
|
||||||
|
from cartodb_services.geocodio import GeocodioBulkGeocoder
|
||||||
|
from cartodb_services.tools.exceptions import ServiceException
|
||||||
|
from credentials import geocodio_api_key
|
||||||
|
|
||||||
|
INVALID_TOKEN = 'invalid_token'
|
||||||
|
|
||||||
|
VALID_ADDRESS_1 = 'Lexington Ave, New York, US'
|
||||||
|
VALID_ADDRESS_2 = 'E 14th St; New York; US'
|
||||||
|
|
||||||
|
WELL_KNOWN_LONGITUDE_1 = -74.365
|
||||||
|
WELL_KNOWN_LATITUDE_1 = 42.240
|
||||||
|
WELL_KNOWN_LONGITUDE_2 = -73.983
|
||||||
|
WELL_KNOWN_LATITUDE_2 = 40.731
|
||||||
|
|
||||||
|
VALID_SEARCH_TEXT='Lexington Ave'
|
||||||
|
VALID_CITY='New York'
|
||||||
|
VALID_STATE_PROVINCE='New York'
|
||||||
|
VALID_COUNTRY='USA'
|
||||||
|
|
||||||
|
WELL_KNOWN_LONGITUDE_COMPONENTS = -76.710
|
||||||
|
WELL_KNOWN_LATITUDE_COMPONENTS = 39.964
|
||||||
|
|
||||||
|
SEARCH_ID_1 = 1
|
||||||
|
SEARCH_ID_2 = 2
|
||||||
|
|
||||||
|
|
||||||
|
class GeocodioGeocoderTestCase(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.geocoder = GeocodioGeocoder(token=geocodio_api_key(), logger=Mock())
|
||||||
|
self.bulk_geocoder = GeocodioBulkGeocoder(token=geocodio_api_key(), logger=Mock())
|
||||||
|
|
||||||
|
### NON BULK
|
||||||
|
|
||||||
|
def test_invalid_token(self):
|
||||||
|
invalid_geocoder = GeocodioGeocoder(token=INVALID_TOKEN, logger=Mock())
|
||||||
|
with self.assertRaises(ServiceException):
|
||||||
|
invalid_geocoder.geocode(VALID_ADDRESS_1)
|
||||||
|
|
||||||
|
def test_valid_request(self):
|
||||||
|
place = self.geocoder.geocode(VALID_ADDRESS_1)
|
||||||
|
|
||||||
|
self.assertEqual('%.3f' % place[0], '%.3f' % WELL_KNOWN_LONGITUDE_1)
|
||||||
|
self.assertEqual('%.3f' % place[1], '%.3f' % WELL_KNOWN_LATITUDE_1)
|
||||||
|
|
||||||
|
def test_valid_request_components(self):
|
||||||
|
place = self.geocoder.geocode(searchtext=VALID_SEARCH_TEXT,
|
||||||
|
city=VALID_CITY,
|
||||||
|
state_province=VALID_STATE_PROVINCE,
|
||||||
|
country=VALID_COUNTRY)
|
||||||
|
|
||||||
|
self.assertEqual('%.3f' % place[0], '%.3f' % WELL_KNOWN_LONGITUDE_COMPONENTS)
|
||||||
|
self.assertEqual('%.3f' % place[1], '%.3f' % WELL_KNOWN_LATITUDE_COMPONENTS)
|
||||||
|
|
||||||
|
def test_valid_request_namedplace(self):
|
||||||
|
place = self.geocoder.geocode(searchtext='New York')
|
||||||
|
|
||||||
|
assert place
|
||||||
|
|
||||||
|
def test_valid_request_namedplace2(self):
|
||||||
|
place = self.geocoder.geocode(searchtext='New York', country='us')
|
||||||
|
|
||||||
|
assert place
|
||||||
|
|
||||||
|
def test_odd_characters(self):
|
||||||
|
place = self.geocoder.geocode(searchtext='New York; "USA"')
|
||||||
|
|
||||||
|
assert place
|
||||||
|
|
||||||
|
def test_empty_request(self):
|
||||||
|
place = self.geocoder.geocode(searchtext='', country=None, city=None, state_province=None)
|
||||||
|
|
||||||
|
assert place == []
|
||||||
|
|
||||||
|
def test_empty_search_text_request(self):
|
||||||
|
place = self.geocoder.geocode(searchtext=' ', country='us', city=None, state_province="")
|
||||||
|
|
||||||
|
assert place == []
|
||||||
|
|
||||||
|
def test_unknown_place_request(self):
|
||||||
|
place = self.geocoder.geocode(searchtext='[unknown]', country='ch', state_province=None, city=None)
|
||||||
|
|
||||||
|
assert place == []
|
||||||
|
|
||||||
|
### BULK ONE
|
||||||
|
|
||||||
|
def test_invalid_token_bulk_one(self):
|
||||||
|
invalid_geocoder = GeocodioBulkGeocoder(token=INVALID_TOKEN, logger=Mock())
|
||||||
|
with self.assertRaises(ServiceException):
|
||||||
|
invalid_geocoder._batch_geocode([(SEARCH_ID_1, VALID_ADDRESS_1, None, None, None)])
|
||||||
|
|
||||||
|
def test_valid_request_bulk_one(self):
|
||||||
|
place = self.bulk_geocoder._batch_geocode([(SEARCH_ID_1, VALID_ADDRESS_1, None, None, None)])
|
||||||
|
|
||||||
|
self.assertEqual(place[0][0], SEARCH_ID_1)
|
||||||
|
self.assertEqual('%.3f' % place[0][1], '%.3f' % WELL_KNOWN_LONGITUDE_1)
|
||||||
|
self.assertEqual('%.3f' % place[0][2], '%.3f' % WELL_KNOWN_LATITUDE_1)
|
||||||
|
|
||||||
|
def test_valid_request_components_bulk_one(self):
|
||||||
|
place = self.bulk_geocoder._batch_geocode([(SEARCH_ID_1, VALID_SEARCH_TEXT, VALID_CITY, VALID_STATE_PROVINCE, VALID_COUNTRY)])
|
||||||
|
|
||||||
|
self.assertEqual(place[0][0], SEARCH_ID_1)
|
||||||
|
self.assertEqual('%.3f' % place[0][1], '%.3f' % WELL_KNOWN_LONGITUDE_COMPONENTS)
|
||||||
|
self.assertEqual('%.3f' % place[0][2], '%.3f' % WELL_KNOWN_LATITUDE_COMPONENTS)
|
||||||
|
|
||||||
|
def test_valid_request_namedplace_bulk_one(self):
|
||||||
|
place = self.bulk_geocoder._batch_geocode([(SEARCH_ID_1, 'New York', None, None, None)])
|
||||||
|
|
||||||
|
assert place
|
||||||
|
|
||||||
|
def test_valid_request_namedplace2_bulk_one(self):
|
||||||
|
place = self.bulk_geocoder._batch_geocode([(SEARCH_ID_1, 'New York', 'us', None, None)])
|
||||||
|
|
||||||
|
assert place
|
||||||
|
|
||||||
|
def test_odd_characters_bulk_one(self):
|
||||||
|
place = self.bulk_geocoder._batch_geocode([(SEARCH_ID_1, 'New York; "USA"', None, None, None)])
|
||||||
|
|
||||||
|
assert place
|
||||||
|
|
||||||
|
def test_empty_request_bulk_one(self):
|
||||||
|
place = self.bulk_geocoder._batch_geocode([(SEARCH_ID_1, '', None, None, None)])
|
||||||
|
|
||||||
|
assert place == [(SEARCH_ID_1, None, None)]
|
||||||
|
|
||||||
|
def test_empty_search_text_request_bulk_one(self):
|
||||||
|
place = self.bulk_geocoder._batch_geocode([(SEARCH_ID_1, ' ', 'us', None, "")])
|
||||||
|
|
||||||
|
assert place == [(SEARCH_ID_1, None, None)]
|
||||||
|
|
||||||
|
def test_unknown_place_request_bulk_one(self):
|
||||||
|
place = self.bulk_geocoder._batch_geocode([(SEARCH_ID_1, '[unknown]', 'ch', None, None)])
|
||||||
|
|
||||||
|
assert place == [(SEARCH_ID_1, None, None)]
|
||||||
|
|
||||||
|
### BULK MANY
|
||||||
|
|
||||||
|
def test_invalid_token_bulk_many(self):
|
||||||
|
invalid_geocoder = GeocodioBulkGeocoder(token=INVALID_TOKEN, logger=Mock())
|
||||||
|
with self.assertRaises(ServiceException):
|
||||||
|
invalid_geocoder._batch_geocode([(SEARCH_ID_1, VALID_ADDRESS_1, None, None, None),
|
||||||
|
(SEARCH_ID_2, VALID_ADDRESS_2, None, None, None)])
|
||||||
|
|
||||||
|
def test_valid_request_bulk_many(self):
|
||||||
|
places = self.bulk_geocoder._batch_geocode([(SEARCH_ID_1, VALID_ADDRESS_1, None, None, None),
|
||||||
|
(SEARCH_ID_2, VALID_ADDRESS_2, None, None, None)])
|
||||||
|
|
||||||
|
self.assertEqual(places[0][0], SEARCH_ID_1)
|
||||||
|
self.assertEqual('%.3f' % places[0][1][0], '%.3f' % WELL_KNOWN_LONGITUDE_1)
|
||||||
|
self.assertEqual('%.3f' % places[0][1][1], '%.3f' % WELL_KNOWN_LATITUDE_1)
|
||||||
|
|
||||||
|
self.assertEqual(places[1][0], SEARCH_ID_2)
|
||||||
|
self.assertEqual('%.3f' % places[1][1][0], '%.3f' % WELL_KNOWN_LONGITUDE_2)
|
||||||
|
self.assertEqual('%.3f' % places[1][1][1], '%.3f' % WELL_KNOWN_LATITUDE_2)
|
||||||
|
|
||||||
|
def test_valid_request_components_bulk_many(self):
|
||||||
|
places = self.bulk_geocoder._batch_geocode([(SEARCH_ID_1, VALID_SEARCH_TEXT, VALID_CITY, VALID_STATE_PROVINCE, VALID_COUNTRY),
|
||||||
|
(SEARCH_ID_2, VALID_SEARCH_TEXT, VALID_CITY, VALID_STATE_PROVINCE, VALID_COUNTRY)])
|
||||||
|
|
||||||
|
self.assertEqual(places[0][0], SEARCH_ID_1)
|
||||||
|
self.assertEqual(places[1][0], SEARCH_ID_2)
|
||||||
|
|
||||||
|
def test_valid_request_namedplace_bulk_many(self):
|
||||||
|
places = self.bulk_geocoder._batch_geocode([(SEARCH_ID_1, 'New York', None, None, None),
|
||||||
|
(SEARCH_ID_2, 'Los Angeles', None, None, None)])
|
||||||
|
|
||||||
|
assert places
|
||||||
|
|
||||||
|
self.assertEqual(places[0][0], SEARCH_ID_1)
|
||||||
|
self.assertEqual(places[1][0], SEARCH_ID_2)
|
||||||
|
|
||||||
|
def test_valid_request_namedplace2_bulk_many(self):
|
||||||
|
places = self.bulk_geocoder._batch_geocode([(SEARCH_ID_1, 'New York', 'us', None, None),
|
||||||
|
(SEARCH_ID_2, 'Los Angeles', None, None, None)])
|
||||||
|
|
||||||
|
assert places
|
||||||
|
|
||||||
|
self.assertEqual(places[0][0], SEARCH_ID_1)
|
||||||
|
self.assertEqual(places[1][0], SEARCH_ID_2)
|
||||||
|
|
||||||
|
def test_odd_characters_bulk_many(self):
|
||||||
|
places = self.bulk_geocoder._batch_geocode([(SEARCH_ID_1, 'New York; "USA"', None, None, None),
|
||||||
|
(SEARCH_ID_2, 'Los Angeles', None, None, None)])
|
||||||
|
|
||||||
|
assert places
|
||||||
|
|
||||||
|
self.assertEqual(places[0][0], SEARCH_ID_1)
|
||||||
|
self.assertEqual(places[1][0], SEARCH_ID_2)
|
||||||
|
|
||||||
|
def test_empty_request_bulk_many(self):
|
||||||
|
places = self.bulk_geocoder._batch_geocode([(SEARCH_ID_1, '', None, None, None),
|
||||||
|
(SEARCH_ID_2, '', None, None, None)])
|
||||||
|
|
||||||
|
assert places == [(SEARCH_ID_1, [], {}), (SEARCH_ID_2, [], {})]
|
||||||
|
|
||||||
|
def test_empty_search_text_request_bulk_many(self):
|
||||||
|
places = self.bulk_geocoder._batch_geocode([(SEARCH_ID_1, ' ', 'us', None, ""),
|
||||||
|
(SEARCH_ID_2, ' ', 'us', None, "")])
|
||||||
|
|
||||||
|
assert places == [(SEARCH_ID_1, [], {}), (SEARCH_ID_2, [], {})]
|
||||||
|
|
||||||
|
def test_unknown_place_request_bulk_many(self):
|
||||||
|
places = self.bulk_geocoder._batch_geocode([(SEARCH_ID_1, '[unknown]', 'ch', None, None),
|
||||||
|
(SEARCH_ID_2, '[unknown]', 'ch', None, None)])
|
||||||
|
|
||||||
|
assert places == [(SEARCH_ID_1, [], {}), (SEARCH_ID_2, [], {})]
|
||||||
Reference in New Issue
Block a user