PageRenderTime 37ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/geopy/geocoders/google.py

http://geopy.googlecode.com/
Python | 179 lines | 164 code | 7 blank | 8 comment | 8 complexity | c7da89792b2ea3ac8a90242d4c3844dd MD5 | raw file
  1. from urllib import urlencode
  2. from urllib2 import urlopen
  3. try:
  4. import json
  5. except ImportError:
  6. try:
  7. import simplejson as json
  8. except ImportError:
  9. from django.utils import simplejson as json
  10. import xml
  11. from xml.parsers.expat import ExpatError
  12. from geopy.geocoders.base import Geocoder,GeocoderError,GeocoderResultError
  13. from geopy import Point, Location, util
  14. class Google(Geocoder):
  15. """Geocoder using the Google Maps API."""
  16. def __init__(self, api_key=None, domain='maps.google.com',
  17. resource=None, format_string='%s', output_format=None):
  18. """Initialize a customized Google geocoder with location-specific
  19. address information and your Google Maps API key.
  20. ``api_key`` should be a valid Google Maps API key. Required as per Google Geocoding API
  21. V2 docs, but the API works without a key in practice.
  22. ``domain`` should be the localized Google Maps domain to connect to. The default
  23. is 'maps.google.com', but if you're geocoding address in the UK (for
  24. example), you may want to set it to 'maps.google.co.uk' to properly bias results.
  25. ``resource`` is DEPRECATED and ignored -- the parameter remains for compatibility
  26. purposes. The supported 'maps/geo' API is used regardless of this parameter.
  27. ``format_string`` is a string containing '%s' where the string to
  28. geocode should be interpolated before querying the geocoder.
  29. For example: '%s, Mountain View, CA'. The default is just '%s'.
  30. ``output_format`` (DEPRECATED) can be 'json', 'xml', or 'kml' and will
  31. control the output format of Google's response. The default is 'json'. 'kml' is
  32. an alias for 'xml'.
  33. """
  34. if resource != None:
  35. from warnings import warn
  36. warn('geopy.geocoders.google.GoogleGeocoder: The `resource` parameter is deprecated '+
  37. 'and now ignored. The Google-supported "maps/geo" API will be used.', DeprecationWarning)
  38. if output_format != None:
  39. from warnings import warn
  40. warn('geopy.geocoders.google.GoogleGeocoder: The `output_format` parameter is deprecated.', DeprecationWarning)
  41. self.api_key = api_key
  42. self.domain = domain
  43. self.format_string = format_string
  44. if output_format:
  45. if output_format not in ('json','xml','kml'):
  46. raise ValueError('if defined, `output_format` must be one of: "json","xml","kml"')
  47. else:
  48. if output_format == "kml":
  49. self.output_format = "xml"
  50. else:
  51. self.output_format = output_format
  52. else:
  53. self.output_format = "xml"
  54. @property
  55. def url(self):
  56. domain = self.domain.strip('/')
  57. return "http://%s/maps/geo?%%s" % domain
  58. def geocode(self, string, exactly_one=True):
  59. params = {'q': self.format_string % string,
  60. 'output': self.output_format.lower(),
  61. }
  62. if self.api_key:
  63. params['key'] = self.api_key
  64. url = self.url % urlencode(params)
  65. return self.geocode_url(url, exactly_one)
  66. def geocode_url(self, url, exactly_one=True):
  67. util.logger.debug("Fetching %s..." % url)
  68. page = urlopen(url)
  69. dispatch = getattr(self, 'parse_' + self.output_format)
  70. return dispatch(page, exactly_one)
  71. def parse_xml(self, page, exactly_one=True):
  72. """Parse a location name, latitude, and longitude from an XML response.
  73. """
  74. if not isinstance(page, basestring):
  75. page = util.decode_page(page)
  76. try:
  77. doc = xml.dom.minidom.parseString(page)
  78. except ExpatError:
  79. places = []
  80. doc = None
  81. else:
  82. places = doc.getElementsByTagName('Placemark')
  83. if len(places) == 0 and doc is not None:
  84. # Got empty result. Parse out the status code and raise an error if necessary.
  85. status = doc.getElementsByTagName("Status")
  86. status_code = int(util.get_first_text(status[0], 'code'))
  87. self.check_status_code(status_code)
  88. if exactly_one and len(places) != 1:
  89. raise ValueError("Didn't find exactly one placemark! " \
  90. "(Found %d.)" % len(places))
  91. def parse_place(place):
  92. location = util.get_first_text(place, ['address', 'name']) or None
  93. points = place.getElementsByTagName('Point')
  94. point = points and points[0] or None
  95. coords = util.get_first_text(point, 'coordinates') or None
  96. if coords:
  97. longitude, latitude = [float(f) for f in coords.split(',')[:2]]
  98. else:
  99. latitude = longitude = None
  100. _, (latitude, longitude) = self.geocode(location)
  101. return (location, (latitude, longitude))
  102. if exactly_one:
  103. return parse_place(places[0])
  104. else:
  105. return [parse_place(place) for place in places]
  106. def parse_json(self, page, exactly_one=True):
  107. if not isinstance(page, basestring):
  108. page = util.decode_page(page)
  109. doc = json.loads(page)
  110. places = doc.get('Placemark', [])
  111. if len(places) == 0:
  112. # Got empty result. Parse out the status code and raise an error if necessary.
  113. status = doc.get("Status", [])
  114. status_code = status["code"]
  115. self.check_status_code(status_code)
  116. return None
  117. elif exactly_one and len(places) != 1:
  118. raise ValueError("Didn't find exactly one placemark! " \
  119. "(Found %d.)" % len(places))
  120. def parse_place(place):
  121. location = place.get('address')
  122. longitude, latitude = place['Point']['coordinates'][:2]
  123. return (location, (latitude, longitude))
  124. if exactly_one:
  125. return parse_place(places[0])
  126. else:
  127. return [parse_place(place) for place in places]
  128. def check_status_code(self,status_code):
  129. if status_code == 400:
  130. raise GeocoderResultError("Bad request (Server returned status 400)")
  131. elif status_code == 500:
  132. raise GeocoderResultError("Unkown error (Server returned status 500)")
  133. elif status_code == 601:
  134. raise GQueryError("An empty lookup was performed")
  135. elif status_code == 602:
  136. raise GQueryError("No corresponding geographic location could be found for the specified location, possibly because the address is relatively new, or because it may be incorrect.")
  137. elif status_code == 603:
  138. raise GQueryError("The geocode for the given location could be returned due to legal or contractual reasons")
  139. elif status_code == 610:
  140. raise GBadKeyError("The api_key is either invalid or does not match the domain for which it was given.")
  141. elif status_code == 620:
  142. raise GTooManyQueriesError("The given key has gone over the requests limit in the 24 hour period or has submitted too many requests in too short a period of time.")
  143. class GBadKeyError(GeocoderError):
  144. pass
  145. class GQueryError(GeocoderResultError):
  146. pass
  147. class GTooManyQueriesError(GeocoderResultError):
  148. pass