Skip to content

Commit

Permalink
Merge pull request #217 from cspinelive/master
Browse files Browse the repository at this point in the history
Add support for Google Places API for 'ambiguous' geocoding
  • Loading branch information
DenisCarriere committed Dec 1, 2016
2 parents b018c70 + 0007def commit 5d8a3d0
Show file tree
Hide file tree
Showing 8 changed files with 193 additions and 4 deletions.
2 changes: 1 addition & 1 deletion docs/examples/using iPython.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ geocoder.base geocoder.get geocoder.osm
geocoder.bing geocoder.google geocoder.timezone
geocoder.canadapost geocoder.ip geocoder.yahoo
geocoder.cli geocoder.keys geocoder.tomtom
geocoder.elevation geocoder.location
geocoder.elevation geocoder.location geocoder.places
...
```

Expand Down
19 changes: 19 additions & 0 deletions docs/providers/Google.rst
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,21 @@ Read me at Google's Geocoding API
https://developers.google.com/maps/documentation/geocoding/intro#ComponentFiltering


Places
~~~~~~~~~

.. code-block:: python
>>> import geocoder
>>> g = geocoder.google('white house', method='places')
>>> g.latlng
[38.8976763, -77.0365298]
>>> g.address
u'1600 Pennsylvania Ave NW, Washington, DC 20500, United States'
>>> g.json
...
Elevation
~~~~~~~~~

Expand All @@ -74,6 +89,7 @@ Command Line Interface
.. code-block:: bash
$ geocode 'Mountain View, CA' --provider google
$ geocode 'white house' --provider google --method places
$ geocode '45.15, -75.14' --provider google --method reverse
$ geocode '45.15, -75.14' --provider google --method timezone
$ geocode '45.15, -75.14' --provider google --method elevation
Expand Down Expand Up @@ -103,9 +119,12 @@ Parameters
- reverse
- timezone
- elevation
- places


References
----------

- `Google Geocoding API <https://developers.google.com/maps/documentation/geocoding/>`_
- `Google Geocoding Best Practices <https://developers.google.com/maps/documentation/geocoding/best-practices/>`_
- `Google Places API <https://developers.google.com/places/web-service/intro/>`_
2 changes: 1 addition & 1 deletion geocoder/__init__.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
from geocoder.api import yandex, mapzen, komoot, tamu, geocodefarm, tgos # noqa

# EXTRAS
from geocoder.api import timezone, elevation, ip, canadapost, reverse, distance, location # noqa
from geocoder.api import timezone, elevation, places, ip, canadapost, reverse, distance, location # noqa

# CLI
from geocoder.cli import cli # noqa
11 changes: 11 additions & 0 deletions geocoder/api.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
from geocoder.google_timezone import Timezone
from geocoder.google_reverse import GoogleReverse
from geocoder.google_elevation import Elevation
from geocoder.google_places import Places

options = {
'osm': {
Expand Down Expand Up @@ -103,6 +104,7 @@
'reverse': GoogleReverse,
'timezone': Timezone,
'elevation': Elevation,
'places': Places,
},
'mapzen': {
'geocode': Mapzen,
Expand Down Expand Up @@ -169,6 +171,7 @@ def google(location, **kwargs):
:param location: Your search location you want geocoded.
:param method: (default=geocode) Use the following:
> geocode
> places
> reverse
> batch
> timezone
Expand Down Expand Up @@ -255,6 +258,14 @@ def elevation(location, **kwargs):
return get(location, method='elevation', provider='google', **kwargs)


def places(location, **kwargs):
"""Places - Google Provider
:param location: Your search location you want geocoded.
"""
return get(location, method='places', provider='google', **kwargs)


def timezone(location, **kwargs):
"""Timezone - Google Provider
Expand Down
2 changes: 1 addition & 1 deletion geocoder/cli.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@


providers = sorted(options.keys())
methods = ['geocode', 'reverse', 'elevation', 'timezone']
methods = ['geocode', 'reverse', 'elevation', 'timezone', 'places']
outputs = ['json', 'osm', 'geojson', 'wkt']
units = ['kilometers', 'miles', 'feet', 'meters']

Expand Down
5 changes: 5 additions & 0 deletions geocoder/google.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,17 @@ class Google(Base):
API Reference
-------------
https://developers.google.com/maps/documentation/geocoding
For ambiguous queries or 'nearby' type queries, use the Places Text Search instead.
https://developers.google.com/maps/documentation/geocoding/best-practices#automated-system
Parameters
----------
:param location: Your search location you want geocoded.
:param components: Component Filtering
:param method: (default=geocode) Use the following:
> geocode
> places
> reverse
> batch
> timezone
Expand Down
149 changes: 149 additions & 0 deletions geocoder/google_places.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
#!/usr/bin/python
# coding: utf8

from __future__ import absolute_import
import time
from geocoder.base import Base
from geocoder.keys import google_key

# todo: Paging (pagetoken) is not fully supported since we only return the first result. Need to return all results to the user so paging will make sense
# todo: Add support for missing results fields html_attributions, opening_hours, photos, scope, alt_ids, types [not just the first one]
# todo: Add support for nearbysearch and radarsearch variations of the Google Places API


class Places(Base):
"""
Google Places API
====================
The Google Places API Web Service allows you to query for place information on a variety of categories,
such as: establishments, prominent points of interest, geographic locations, and more.
You can search for places either by proximity or a text string.
A Place Search returns a list of places along with summary information about each place; additional
information is available via a Place Details query.
At this time, only the "Text Search" is supported by this library. "Text Search" can be used
when you don't have pristine formatted addresses required by the regular Google Maps Geocoding API
or when you want to do 'nearby' searches like 'restaurants near Sydney'.
The Geocoding best practices reference indicates that when you have 'ambiguous queries in an automated system
you would be better served using the Places API Text Search than the Maps Geocoding API
https://developers.google.com/maps/documentation/geocoding/best-practices
API Reference
-------------
https://developers.google.com/places/web-service/intro
https://developers.google.com/places/web-service/search
l = geocoder.google('Elm Plaza Shopping Center, Enfield, CT 06082', method='places')
l = geocoder.google('food near white house', method='places')
l = geocoder.google('1st and main', method='places')
Parameters
----------
:param query: Your search location or phrase you want geocoded.
:param key: Your Google developers free key.
:param location: (optional) lat,lng point around which results will be given preference
:param radius: (optional) in meters, used with location
:param language: (optional) 2-letter code of preferred language of returned address elements.
:param minprice: (optional) 0 (most affordable) to 4 (most expensive)
:param maxprice: (optional) 0 (most affordable) to 4 (most expensive)
:param opennow: (optional) value is ignored. when present, closed places and places without opening hours will be omitted
:param pagetoken: (optional) get next 20 results from previously run search. when set, other criteria are ignored
:param type: (optional) restrict results to one type of place
"""
provider = 'google'
method = 'places'

def __init__(self, query, **kwargs):
self.url = 'https://maps.googleapis.com/maps/api/place/textsearch/json'
self.location = query
self.params = {
# required
'query': self.location,
'key': kwargs.get('key', google_key),

# optional
'location': kwargs.get('location', ''),
'radius': kwargs.get('radius', ''),
'language': kwargs.get('language', ''),
'minprice': kwargs.get('minprice', ''),
'maxprice': kwargs.get('maxprice', ''),
'type': kwargs.get('type', ''),
}

# optional, don't send unless needed
if 'opennow' in kwargs:
self.params['opennow'] = ''

# optional, don't send unless needed
if 'pagetoken' in kwargs:
self.params['pagetoken'] = kwargs['pagetoken']

self._initialize(**kwargs)

def _exceptions(self):
if self.parse['results']:
self._build_tree(self.parse['results'][0])

@property
def lat(self):
return self.parse['location'].get('lat')

@property
def lng(self):
return self.parse['location'].get('lng')

@property
def id(self):
return self.parse.get('id')

@property
def reference(self):
return self.parse.get('reference')

@property
def place_id(self):
return self.parse.get('place_id')

@property
def type(self):
type = self.parse.get('types')
if type:
return type[0]

@property
def address(self):
return self.parse.get('formatted_address')

@property
def icon(self):
return self.parse.get('icon')

@property
def name(self):
return self.parse.get('name')

@property
def vicinity(self):
return self.parse.get('vicinity')

@property
def price_level(self):
return self.parse.get('price_level')

@property
def rating(self):
return self.parse.get('rating')

@property
def next_page_token(self):
return self.parse.get('next_page_token')

@property
def query(self):
return self.location

if __name__ == '__main__':
g = Places('11 Wall Street, New York', method='places', key='<API KEY>')
g.debug()
7 changes: 6 additions & 1 deletion tests/test_geocoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def test_entry_points():
geocoder.geolytica
geocoder.timezone
geocoder.opencage
geocoder.elevation
geocoder.places
geocoder.canadapost
geocoder.tamu
geocoder.geocodefarm
Expand Down Expand Up @@ -132,6 +132,11 @@ def test_google():
assert str(g.city) == city


def test_google_places():
g = geocoder.google(address, method='places')
assert g.ok


def test_google_for_work():
g = geocoder.google(location)
assert g.ok
Expand Down

0 comments on commit 5d8a3d0

Please sign in to comment.