lab_weather/geocoder/google.py

295 lines
9.7 KiB
Python

#!/usr/bin/python
# coding: utf8
from __future__ import absolute_import
import six
from geocoder.base import OneResult, MultipleResultsQuery
from geocoder.keys import google_key, google_client, google_client_secret
from collections import OrderedDict
import ratelim
class GoogleResult(OneResult):
def __init__(self, json_content):
# flatten geometry
geometry = json_content.get('geometry', {})
self._location = geometry.get('location', {})
self._location_type = geometry.get('location_type', {})
self._viewport = geometry.get('viewport', {})
# Parse address components with short & long names
for item in json_content['address_components']:
for category in item['types']:
json_content.setdefault(category, {})
json_content[category]['long_name'] = item['long_name']
json_content[category]['short_name'] = item['short_name']
# proceed with super.__init__
super(GoogleResult, self).__init__(json_content)
@property
def lat(self):
return self._location.get('lat')
@property
def lng(self):
return self._location.get('lng')
@property
def place(self):
return self.raw.get('place_id')
@property
def quality(self):
quality = self.raw.get('types')
if quality:
return quality[0]
@property
def accuracy(self):
return self._location_type
@property
def bbox(self):
south = self._viewport.get('southwest', {}).get('lat')
west = self._viewport.get('southwest', {}).get('lng')
north = self._viewport.get('northeast', {}).get('lat')
east = self._viewport.get('northeast', {}).get('lng')
return self._get_bbox(south, west, north, east)
@property
def address(self):
return self.raw.get('formatted_address')
@property
def postal(self):
return self.raw.get('postal_code', {}).get('short_name')
@property
def subpremise(self):
return self.raw.get('subpremise', {}).get('short_name')
@property
def housenumber(self):
return self.raw.get('street_number', {}).get('short_name')
@property
def street(self):
return self.raw.get('route', {}).get('short_name')
@property
def street_long(self):
return self.raw.get('route', {}).get('long_name')
@property
def road_long(self):
return self.street_long
@property
def neighborhood(self):
return self.raw.get('neighborhood', {}).get('short_name')
@property
def sublocality(self):
return self.raw.get('sublocality', {}).get('short_name')
@property
def city(self):
return self.raw.get('locality', {}).get('short_name') or self.postal_town
@property
def city_long(self):
return self.raw.get('locality', {}).get('long_name') or self.postal_town_long
@property
def postal_town(self):
return self.raw.get('postal_town', {}).get('short_name')
@property
def postal_town_long(self):
return self.raw.get('postal_town', {}).get('long_name')
@property
def county(self):
return self.raw.get('administrative_area_level_2', {}).get('short_name')
@property
def state(self):
return self.raw.get('administrative_area_level_1', {}).get('short_name')
@property
def state_long(self):
return self.raw.get('administrative_area_level_1', {}).get('long_name')
@property
def province_long(self):
return self.state_long
@property
def country(self):
return self.raw.get('country', {}).get('short_name')
@property
def country_long(self):
return self.raw.get('country', {}).get('long_name')
class GoogleQuery(MultipleResultsQuery):
"""
Google Geocoding API
====================
Geocoding is the process of converting addresses into geographic
coordinates (like latitude 37.423021 and longitude -122.083739),
which you can use to place markers or position the map.
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
> timezone
> elevation
:param key: Your Google developers free key.
:param language: 2-letter code of preferred language of returned address elements.
:param client: Google for Work client ID. Use with client_secret. Cannot use with key parameter
:param client_secret: Google for Work client secret. Use with client.
"""
provider = 'google'
method = 'geocode'
_URL = 'https://maps.googleapis.com/maps/api/geocode/json'
_RESULT_CLASS = GoogleResult
_KEY = google_key
_KEY_MANDATORY = False
def _build_params(self, location, provider_key, **kwargs):
params = self._location_init(location, **kwargs)
params['language'] = kwargs.get('language', '')
self.rate_limit = kwargs.get('rate_limit', True)
# adapt params to authentication method
# either with client / secret
self.client = kwargs.get('client', google_client)
self.client_secret = kwargs.get('client_secret', google_client_secret)
if self.client and self.client_secret:
params['client'] = self.client
return self._encode_params(params)
# or API key
else:
# provider_key is computed in base.py:
# either cls._KEY (google_key) or kwargs['key'] if provided
params['key'] = provider_key
return params
def _location_init(self, location, **kwargs):
return {
'address': location,
'bounds': kwargs.get('bounds', ''),
'components': kwargs.get('components', ''),
'region': kwargs.get('region', ''),
}
def _encode_params(self, params):
# turn non-empty params into sorted list in order to maintain signature validity.
# Requests will honor the order.
ordered_params = sorted([(k, v)
for (k, v) in params.items() if v])
params = OrderedDict(ordered_params)
# the signature parameter needs to come in the end of the url
params['signature'] = self._sign_url(
self.url, ordered_params, self.client_secret)
return params
def _sign_url(self, base_url=None, params=None, client_secret=None):
""" Sign a request URL with a Crypto Key.
Usage:
from urlsigner import sign_url
signed_url = sign_url(base_url=my_url,
params=url_params,
client_secret=CLIENT_SECRET)
Args:
base_url - The trunk of the URL to sign. E.g. https://maps.googleapis.com/maps/api/geocode/json
params - List of tuples of URL parameters INCLUDING YOUR CLIENT ID ('client','gme-...')
client_secret - Your Crypto Key from Google for Work
Returns:
The signature as a dictionary #signed request URL
"""
import hashlib
import hmac
import base64
if six.PY3:
from urllib.parse import urlparse, urlencode
else:
from urllib import urlencode
from urlparse import urlparse
# Return if any parameters aren't given
if not base_url or not self.client_secret or not self.client:
return None
# assuming parameters will be submitted to Requests in identical order!
url = urlparse(base_url + "?" + urlencode(params))
# We only need to sign the path+query part of the string
url_to_sign = (url.path + "?" + url.query).encode('utf-8')
# Decode the private key into its binary format
# We need to decode the URL-encoded private key
decoded_key = base64.urlsafe_b64decode(client_secret)
# Create a signature using the private key and the URL-encoded
# string using HMAC SHA1. This signature will be binary.
signature = hmac.new(decoded_key, url_to_sign, hashlib.sha1)
# Encode the binary signature into base64 for use within a URL
encoded_signature = base64.urlsafe_b64encode(signature.digest())
# Return signature (to be appended as a 'signature' in params)
return encoded_signature
def rate_limited_get(self, *args, **kwargs):
if not self.rate_limit:
return super(GoogleQuery, self).rate_limited_get(*args, **kwargs)
elif self.client and self.client_secret:
return self.rate_limited_get_for_work(*args, **kwargs)
else:
return self.rate_limited_get_for_dev(*args, **kwargs)
@ratelim.greedy(2500, 60 * 60 * 24)
@ratelim.greedy(10, 1)
def rate_limited_get_for_dev(self, *args, **kwargs):
return super(GoogleQuery, self).rate_limited_get(*args, **kwargs)
@ratelim.greedy(100000, 60 * 60 * 24) # Google for Work daily limit
@ratelim.greedy(50, 1) # Google for Work limit per second
def rate_limited_get_for_work(self, *args, **kwargs):
return super(GoogleQuery, self).rate_limited_get(*args, **kwargs)
def _catch_errors(self, json_response):
status = json_response.get('status')
if not status == 'OK':
self.error = status
return self.error
def _adapt_results(self, json_response):
return json_response.get('results', [])
if __name__ == '__main__':
g = GoogleQuery('11 Wall Street, New York')
g.debug()