184 lines
5.4 KiB
Python
184 lines
5.4 KiB
Python
|
import collections.abc
|
||
|
from functools import partial
|
||
|
from urllib.parse import urlencode
|
||
|
|
||
|
from geopy.geocoders.base import DEFAULT_SENTINEL, Geocoder
|
||
|
from geopy.location import Location
|
||
|
from geopy.util import join_filter, logger
|
||
|
|
||
|
__all__ = ("Geolake", )
|
||
|
|
||
|
|
||
|
class Geolake(Geocoder):
|
||
|
"""Geocoder using the Geolake API.
|
||
|
|
||
|
Documentation at:
|
||
|
https://geolake.com/docs/api
|
||
|
|
||
|
Terms of Service at:
|
||
|
https://geolake.com/terms-of-use
|
||
|
"""
|
||
|
|
||
|
structured_query_params = {
|
||
|
'country',
|
||
|
'state',
|
||
|
'city',
|
||
|
'zipcode',
|
||
|
'street',
|
||
|
'address',
|
||
|
'houseNumber',
|
||
|
'subNumber',
|
||
|
}
|
||
|
|
||
|
api_path = '/v1/geocode'
|
||
|
|
||
|
def __init__(
|
||
|
self,
|
||
|
api_key,
|
||
|
*,
|
||
|
domain='api.geolake.com',
|
||
|
scheme=None,
|
||
|
timeout=DEFAULT_SENTINEL,
|
||
|
proxies=DEFAULT_SENTINEL,
|
||
|
user_agent=None,
|
||
|
ssl_context=DEFAULT_SENTINEL,
|
||
|
adapter_factory=None
|
||
|
):
|
||
|
"""
|
||
|
|
||
|
:param str api_key: The API key required by Geolake
|
||
|
to perform geocoding requests. You can get your key here:
|
||
|
https://geolake.com/
|
||
|
|
||
|
:param str domain: Currently it is ``'api.geolake.com'``, can
|
||
|
be changed for testing purposes.
|
||
|
|
||
|
:param str scheme:
|
||
|
See :attr:`geopy.geocoders.options.default_scheme`.
|
||
|
|
||
|
:param int timeout:
|
||
|
See :attr:`geopy.geocoders.options.default_timeout`.
|
||
|
|
||
|
:param dict proxies:
|
||
|
See :attr:`geopy.geocoders.options.default_proxies`.
|
||
|
|
||
|
:param str user_agent:
|
||
|
See :attr:`geopy.geocoders.options.default_user_agent`.
|
||
|
|
||
|
:type ssl_context: :class:`ssl.SSLContext`
|
||
|
:param ssl_context:
|
||
|
See :attr:`geopy.geocoders.options.default_ssl_context`.
|
||
|
|
||
|
:param callable adapter_factory:
|
||
|
See :attr:`geopy.geocoders.options.default_adapter_factory`.
|
||
|
|
||
|
.. versionadded:: 2.0
|
||
|
|
||
|
"""
|
||
|
super().__init__(
|
||
|
scheme=scheme,
|
||
|
timeout=timeout,
|
||
|
proxies=proxies,
|
||
|
user_agent=user_agent,
|
||
|
ssl_context=ssl_context,
|
||
|
adapter_factory=adapter_factory,
|
||
|
)
|
||
|
|
||
|
self.api_key = api_key
|
||
|
self.domain = domain.strip('/')
|
||
|
self.api = '%s://%s%s' % (self.scheme, self.domain, self.api_path)
|
||
|
|
||
|
def geocode(
|
||
|
self,
|
||
|
query,
|
||
|
*,
|
||
|
country_codes=None,
|
||
|
exactly_one=True,
|
||
|
timeout=DEFAULT_SENTINEL
|
||
|
):
|
||
|
"""
|
||
|
Return a location point by address.
|
||
|
|
||
|
:param query: The address or query you wish to geocode.
|
||
|
|
||
|
For a structured query, provide a dictionary whose keys
|
||
|
are one of: `country`, `state`, `city`, `zipcode`, `street`, `address`,
|
||
|
`houseNumber` or `subNumber`.
|
||
|
:type query: str or dict
|
||
|
|
||
|
:param country_codes: Provides the geocoder with a list
|
||
|
of country codes that the query may reside in. This value will
|
||
|
limit the geocoder to the supplied countries. The country code
|
||
|
is a 2 character code as defined by the ISO-3166-1 alpha-2
|
||
|
standard (e.g. ``FR``). Multiple countries can be specified with
|
||
|
a Python list.
|
||
|
|
||
|
:type country_codes: str or list
|
||
|
|
||
|
:param bool exactly_one: Return one result or a list of one result.
|
||
|
|
||
|
:param int timeout: Time, in seconds, to wait for the geocoding service
|
||
|
to respond before raising a :class:`geopy.exc.GeocoderTimedOut`
|
||
|
exception. Set this only if you wish to override, on this call
|
||
|
only, the value set during the geocoder's initialization.
|
||
|
|
||
|
:rtype: ``None``, :class:`geopy.location.Location` or a list of them, if
|
||
|
``exactly_one=False``.
|
||
|
|
||
|
"""
|
||
|
|
||
|
if isinstance(query, collections.abc.Mapping):
|
||
|
params = {
|
||
|
key: val
|
||
|
for key, val
|
||
|
in query.items()
|
||
|
if key in self.structured_query_params
|
||
|
}
|
||
|
params['api_key'] = self.api_key
|
||
|
else:
|
||
|
params = {
|
||
|
'api_key': self.api_key,
|
||
|
'q': query,
|
||
|
}
|
||
|
|
||
|
if not country_codes:
|
||
|
country_codes = []
|
||
|
if isinstance(country_codes, str):
|
||
|
country_codes = [country_codes]
|
||
|
if country_codes:
|
||
|
params['countryCodes'] = ",".join(country_codes)
|
||
|
|
||
|
url = "?".join((self.api, urlencode(params)))
|
||
|
|
||
|
logger.debug("%s.geocode: %s", self.__class__.__name__, url)
|
||
|
callback = partial(self._parse_json, exactly_one=exactly_one)
|
||
|
return self._call_geocoder(url, callback, timeout=timeout)
|
||
|
|
||
|
def _parse_json(self, page, exactly_one):
|
||
|
"""Returns location, (latitude, longitude) from json feed."""
|
||
|
|
||
|
if not page.get('success'):
|
||
|
return None
|
||
|
|
||
|
latitude = page['latitude']
|
||
|
longitude = page['longitude']
|
||
|
|
||
|
address = self._get_address(page)
|
||
|
result = Location(address, (latitude, longitude), page)
|
||
|
if exactly_one:
|
||
|
return result
|
||
|
else:
|
||
|
return [result]
|
||
|
|
||
|
def _get_address(self, page):
|
||
|
"""
|
||
|
Returns address string from page dictionary
|
||
|
:param page: dict
|
||
|
:return: str
|
||
|
"""
|
||
|
place = page.get('place')
|
||
|
address_city = place.get('city')
|
||
|
address_country_code = place.get('countryCode')
|
||
|
address = join_filter(', ', [address_city, address_country_code])
|
||
|
return address
|