PageRenderTime 2ms CodeModel.GetById 40ms app.highlight 16ms RepoModel.GetById 25ms app.codeStats 0ms

/geopy/geocoders/google.py

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