Skip to content

Commit

Permalink
Including pygeocoder source until fixed for python 3
Browse files Browse the repository at this point in the history
  • Loading branch information
geduldig committed Jun 20, 2013
1 parent f93f3d4 commit cb3f269
Show file tree
Hide file tree
Showing 2 changed files with 392 additions and 0 deletions.
218 changes: 218 additions & 0 deletions TwitterGeoPics/pygeocoder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
#!/usr/bin/env python
#
# Xiao Yu - Montreal - 2010
# Based on googlemaps by John Kleint
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.

"""
Python wrapper for Google Geocoding API V3.
* **Geocoding**: convert a postal address to latitude and longitude
* **Reverse Geocoding**: find the nearest address to coordinates
"""

import requests
import functools
import base64
import hmac
import hashlib
from pygeolib import GeocoderError, GeocoderResult
#from __version__ import VERSION

try:
import json
except ImportError:
import simplejson as json

__all__ = ['Geocoder', 'GeocoderError', 'GeocoderResult']


# this decorator lets me use methods as both static and instance methods
class omnimethod(object):
def __init__(self, func):
self.func = func

def __get__(self, instance, owner):
return functools.partial(self.func, instance)


class Geocoder(object):
"""
A Python wrapper for Google Geocoding V3's API
"""

GEOCODE_QUERY_URL = 'https://maps.google.com/maps/api/geocode/json?'

def __init__(self, client_id=None, private_key=None):
"""
Create a new :class:`Geocoder` object using the given `client_id` and
`referrer_url`.
:param client_id: Google Maps Premier API key
:type client_id: string
Google Maps API Premier users can provide his key to make 100,000 requests
a day vs the standard 2,500 requests a day without a key
"""
self.client_id = client_id
self.private_key = private_key

@omnimethod
def get_data(self, params={}):
"""
Retrieve a JSON object from a (parameterized) URL.
:param params: Dictionary mapping (string) query parameters to values
:type params: dict
:return: JSON object with the data fetched from that URL as a JSON-format object.
:rtype: (dict or array)
"""
request = requests.Request('GET',
url = Geocoder.GEOCODE_QUERY_URL,
params = params,
headers = {
'User-Agent': 'pygeocoder/' + VERSION + ' (Python)'
})

if self and self.client_id and self.private_key:
self.add_signature(request)

response = requests.Session().send(request.prepare())
if response.status_code == 403:
raise GeocoderError("Forbidden, 403", response.url)
response_json = response.json()

if response_json['status'] != GeocoderError.G_GEO_OK:
raise GeocoderError(response_json['status'], response.url)
return response_json['results']

@omnimethod
def add_signature(self, request):
decoded_key = base64.urlsafe_b64decode(str(self.private_key))
signature = hmac.new(decoded_key, request.url, hashlib.sha1)
encoded_signature = base64.urlsafe_b64encode(signature.digest())
request.params['client'] = str(self.client_id)
request.params['signature'] = encoded_signature

@omnimethod
def geocode(self, address, sensor='false', bounds='', region='', language=''):
"""
Given a string address, return a dictionary of information about
that location, including its latitude and longitude.
:param address: Address of location to be geocoded.
:type address: string
:param sensor: ``'true'`` if the address is coming from, say, a GPS device.
:type sensor: string
:param bounds: The bounding box of the viewport within which to bias geocode results more prominently.
:type bounds: string
:param region: The region code, specified as a ccTLD ("top-level domain") two-character value for biasing
:type region: string
:param language: The language in which to return results.
:type language: string
:returns: `geocoder return value`_ dictionary
:rtype: dict
:raises GeocoderError: if there is something wrong with the query.
For details on the input parameters, visit
http://code.google.com/apis/maps/documentation/geocoding/#GeocodingRequests
For details on the output, visit
http://code.google.com/apis/maps/documentation/geocoding/#GeocodingResponses
"""

params = {
'address': address,
'sensor': sensor,
'bounds': bounds,
'region': region,
'language': language,
}
if self is not None:
return GeocoderResult(self.get_data(params=params))
else:
return GeocoderResult(Geocoder.get_data(params=params))

@omnimethod
def reverse_geocode(self, lat, lng, sensor='false', bounds='', region='', language=''):
"""
Converts a (latitude, longitude) pair to an address.
:param lat: latitude
:type lat: float
:param lng: longitude
:type lng: float
:return: `Reverse geocoder return value`_ dictionary giving closest
address(es) to `(lat, lng)`
:rtype: dict
:raises GeocoderError: If the coordinates could not be reverse geocoded.
Keyword arguments and return value are identical to those of :meth:`geocode()`.
For details on the input parameters, visit
http://code.google.com/apis/maps/documentation/geocoding/#GeocodingRequests
For details on the output, visit
http://code.google.com/apis/maps/documentation/geocoding/#ReverseGeocoding
"""
params = {
'latlng': "%f,%f" % (lat, lng),
'sensor': sensor,
'bounds': bounds,
'region': region,
'language': language,
}

if self is not None:
return GeocoderResult(self.get_data(params=params))
else:
return GeocoderResult(Geocoder.get_data(params=params))

if __name__ == "__main__":
import sys
from optparse import OptionParser

def main():
"""
Geocodes a location given on the command line.
Usage:
pygeocoder.py "1600 amphitheatre mountain view ca" [YOUR_API_KEY]
pygeocoder.py 37.4219720,-122.0841430 [YOUR_API_KEY]
When providing a latitude and longitude on the command line, ensure
they are separated by a comma and no space.
"""
usage = "usage: %prog [options] address"
parser = OptionParser(usage, version=VERSION)
parser.add_option("-k", "--key", dest="key", help="Your Google Maps API key")
(options, args) = parser.parse_args()

if len(args) != 1:
parser.print_usage()
sys.exit(1)

query = args[0]
gcoder = Geocoder(options.key)

try:
result = gcoder.geocode(query)
except GeocoderError as err:
sys.stderr.write('%s\n%s\nResponse:\n' % (err.url, err))
json.dump(err.response, sys.stderr, indent=4)
sys.exit(1)

print(result)
print(result.coordinates)
main()
174 changes: 174 additions & 0 deletions TwitterGeoPics/pygeolib.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import sys
import collections

class GeocoderResult(collections.Iterator):
"""
A geocoder resultset to iterate through address results.
Exemple:
results = Geocoder.geocode('paris, us')
for result in results:
print(result.formatted_address, result.location)
Provide shortcut to ease field retrieval, looking at 'types' in each
'address_components'.
Example:
result.country
result.postal_code
You can also choose a different property to display for each lookup type.
Example:
result.country__short_name
By default, use 'long_name' property of lookup type, so:
result.country
and:
result.country__long_name
are equivalent.
"""

attribute_mapping = {
"state": "administrative_area_level_1",
"province": "administrative_area_level_1",
"city": "locality",
"county": "administrative_area_level_2",
}

def __init__(self, data):
"""
Creates instance of GeocoderResult from the provided JSON data array
"""
self.data = data
self.len = len(self.data)
self.current_index = 0
self.current_data = self.data[0]

def __len__(self):
return self.len

def __iter__(self):
return self

def return_next(self):
if self.current_index >= self.len:
raise StopIteration
self.current_data = self.data[self.current_index]
self.current_index += 1
return self

def __getitem__(self, key):
"""
Accessing GeocoderResult by index will return a GeocoderResult
with just one data entry
"""
return GeocoderResult([self.data[key]])

def __unicode__(self):
return self.formatted_address

if sys.version_info[0] >= 3: # Python 3
def __str__(self):
return self.__unicode__()

def __next__(self):
return self.return_next()
else: # Python 2
def __str__(self):
return self.__unicode__().encode('utf8')

def next(self):
return self.return_next()

@property
def count(self):
return self.len

@property
def coordinates(self):
"""
Return a (latitude, longitude) coordinate pair of the current result
"""
location = self.current_data['geometry']['location']
return location['lat'], location['lng']

@property
def latitude(self):
return self.coordinates[0]

@property
def longitude(self):
return self.coordinates[1]

@property
def raw(self):
"""
Returns the full result set in dictionary format
"""
return self.data

@property
def valid_address(self):
"""
Returns true if queried address is valid street address
"""
return self.current_data['types'] == [u'street_address']

@property
def formatted_address(self):
return self.current_data['formatted_address']

def __getattr__(self, name):
lookup = name.split('__')
attribute = lookup[0]

if (attribute in GeocoderResult.attribute_mapping):
attribute = GeocoderResult.attribute_mapping[attribute]

try:
prop = lookup[1]
except IndexError:
prop = 'long_name'

for elem in self.current_data['address_components']:
if attribute in elem['types']:
return elem[prop]


class GeocoderError(Exception):
"""Base class for errors in the :mod:`pygeocoder` module.
Methods of the :class:`Geocoder` raise this when something goes wrong.
"""
#: See http://code.google.com/apis/maps/documentation/geocoding/index.html#StatusCodes
#: for information on the meaning of these status codes.
G_GEO_OK = "OK"
G_GEO_ZERO_RESULTS = "ZERO_RESULTS"
G_GEO_OVER_QUERY_LIMIT = "OVER_QUERY_LIMIT"
G_GEO_REQUEST_DENIED = "REQUEST_DENIED"
G_GEO_MISSING_QUERY = "INVALID_REQUEST"

def __init__(self, status, url=None, response=None):
"""Create an exception with a status and optional full response.
:param status: Either a ``G_GEO_`` code or a string explaining the
exception.
:type status: int or string
:param url: The query URL that resulted in the error, if any.
:type url: string
:param response: The actual response returned from Google, if any.
:type response: dict
"""
Exception.__init__(self, status) # Exception is an old-school class
self.status = status
self.url = url
self.response = response

def __str__(self):
"""Return a string representation of this :exc:`GeocoderError`."""
return 'Error %s\nQuery: %s' % (self.status, self.url)

def __unicode__(self):
"""Return a unicode representation of this :exc:`GeocoderError`."""
return unicode(self.__str__())

0 comments on commit cb3f269

Please sign in to comment.