Skip to content

Commit

Permalink
Add Google geocoder
Browse files Browse the repository at this point in the history
  • Loading branch information
KlaasH committed Jul 25, 2016
1 parent d72d7c3 commit 418e16a
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.rst
Expand Up @@ -13,6 +13,7 @@ supported geocoders:
* `MapQuest Licensed Data API <http://developer.mapquest.com/web/products/dev-services/geocoding-ws>`_
* `MapQuest-hosted Nominatim Open Data API <http://developer.mapquest.com/web/products/open/geocoding-service>`_
* `Mapzen Search API <https://mapzen.com/projects/search/>`_
* `Google geocoder <https://developers.google.com/maps/documentation/geocoding/intro>`_

.. NOTE::
Check out `this project's page on GitHub <https://github.com/azavea/python-omgeo/>`_.
Expand Down
5 changes: 5 additions & 0 deletions omgeo/places.py
Expand Up @@ -47,6 +47,11 @@ def to_mapzen_dict(self):
'boundary.rect.max_lon': vb.right
}

def to_google_str(self):
""" Convert to Google's bounds format: 'latMin,lonMin|latMax,lonMax' """
vb = self.convert_srs(4326)
return '%s,%s|%s,%s' % (vb.bottom, vb.left, vb.top, vb.right)

def to_mapquest_str(self):
"""
Convert Viewbox object to a string that can be used by
Expand Down
13 changes: 13 additions & 0 deletions omgeo/preprocessors.py
Expand Up @@ -127,6 +127,19 @@ def process(self, pq):
return pq


class ComposeSingleLine(_PreProcessor):
""" Compose address components into a single-line query if no query is already defined. """
def process(self, pq):
if pq.query == '':
parts = [pq.address, pq.city, pq.subregion]
parts.append(' '.join([p for p in (pq.state, pq.postal) if p != '']))
if pq.country != '':
parts.append(pq.country)
pq.query = ', '.join([part for part in parts if part != ''])

return pq


class CountryPreProcessor(_PreProcessor):
"""
Used to filter acceptable countries
Expand Down
1 change: 1 addition & 0 deletions omgeo/services/__init__.py
Expand Up @@ -4,3 +4,4 @@
from .mapquest import MapQuest, MapQuestSSL
from .nominatim import Nominatim
from .mapzen import Mapzen
from .google import Google
80 changes: 80 additions & 0 deletions omgeo/services/google.py
@@ -0,0 +1,80 @@
import logging

from .base import GeocodeService
from omgeo.places import Candidate
from omgeo.preprocessors import ComposeSingleLine

logger = logging.getLogger(__name__)


class Google(GeocodeService):
"""
Class to geocode using Google's geocoding API.
"""
_endpoint = 'https://maps.googleapis.com/maps/api/geocode/json'

DEFAULT_PREPROCESSORS = [ComposeSingleLine()]

LOCATOR_MAPPING = {
'ROOFTOP': 'rooftop',
'RANGE_INTERPOLATED': 'interpolated',
}

def __init__(self, preprocessors=None, postprocessors=None, settings=None):
preprocessors = self.DEFAULT_PREPROCESSORS if preprocessors is None else preprocessors
GeocodeService.__init__(self, preprocessors, postprocessors, settings)

def _geocode(self, pq):
params = {
'address': pq.query,
'api_key': self._settings['api_key']
}

if pq.country:
params['components'] = 'country:' + pq.country
if pq.viewbox:
params['bounds'] = pq.viewbox.to_google_str()

response_obj = self._get_json_obj(self._endpoint, params)
return [self._make_candidate_from_result(r) for r in response_obj['results']]

def _make_candidate_from_result(self, result):
""" Make a Candidate from a Google geocoder results dictionary. """
candidate = Candidate()
candidate.match_addr = result['formatted_address']
candidate.x = result['geometry']['location']['lng']
candidate.y = result['geometry']['location']['lat']
candidate.locator = self.LOCATOR_MAPPING.get(result['geometry']['location_type'], '')

component_lookups = {
'city': {'type': 'locality', 'key': 'long_name'},
'subregion': {'type': 'administrative_area_level_2', 'key': 'long_name'},
'region': {'type': 'administrative_area_level_1', 'key': 'short_name'},
'postal': {'type': 'postal_code', 'key': 'long_name'},
'country': {'type': 'country', 'key': 'short_name'},
}
for (field, lookup) in component_lookups.iteritems():
setattr(candidate, 'match_' + field, self._get_component_from_result(result, lookup))
candidate.geoservice = self.__class__.__name__
return candidate

def _get_component_from_result(self, result, lookup):
"""
Helper function to get a particular address component from a Google result.
Since the address components in results are an array of objects containing a types array,
we have to search for a particular component rather than being able to look it up directly.
Returns the first match, so this should be used for unique component types (e.g.
'locality'), not for categories (e.g. 'political') that can describe multiple components.
:arg dict result: A results dict with an 'address_components' key, as returned by the
Google geocoder.
:arg dict lookup: The type (e.g. 'street_number') and key ('short_name' or 'long_name') of
the desired address component value.
:returns: address component or empty string
"""
for component in result['address_components']:
if lookup['type'] in component['types']:
return component.get(lookup['key'], '')
return ''

0 comments on commit 418e16a

Please sign in to comment.