From 61208e2cee019a573de81530d2482944643f34d8 Mon Sep 17 00:00:00 2001 From: "Brandon W. King" Date: Thu, 27 Jun 2013 15:41:07 -0700 Subject: [PATCH 1/2] nearby_search was throwing an error during a valid search: * name, lat_lng, radius, and rankby=ranking.DISTANCE. * Test for error was: * if keyword is None and types == [] * Should be: * if keyword is None and types == [] and name is None --- CONTRIBUTORS | 3 ++- googleplaces/__init__.py | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 30820f3..67ac610 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -3,4 +3,5 @@ GitHub contributors JoeJasinski personalplayground yatin-sagade -yyl \ No newline at end of file +yyl +kingb diff --git a/googleplaces/__init__.py b/googleplaces/__init__.py index 2976252..43e581d 100644 --- a/googleplaces/__init__.py +++ b/googleplaces/__init__.py @@ -238,9 +238,11 @@ def nearby_search(self, language=lang.ENGLISH, keyword=None, location=None, if location is None and lat_lng is None: raise ValueError('One of location or lat_lng must be passed in.') if rankby == 'distance': - if keyword is None and types == []: + # As per API docs rankby == distance: + # One or more of keyword, name, or types is required. + if keyword is None and types == [] and name is None: raise ValueError('When rankby = googleplaces.ranking.DISTANCE, ' + - 'one of either the keyword or types kwargs ' + + 'name, keyword or types kwargs ' + 'must be specified.') self._sensor = sensor self._lat_lng = (lat_lng if lat_lng is not None From 7d2cb30f779e6444d23ea187d2e015ecd4deec64 Mon Sep 17 00:00:00 2001 From: "Brandon W. King" Date: Fri, 12 Jul 2013 16:16:39 -0700 Subject: [PATCH 2/2] Added support for Radar Search in Google Places API. * Modified Places object to deal with Radar Search not returning basic details like name and vicinity. * Added GooglePlaces.radar_search method. * FIXME: There a few additional parameters that could be included: * https://developers.google.com/places/documentation/search#RadarSearchRequests * FIXME: Making these results useful pretty much requires calling place_instance.get_details() which is very slow for all 40 results. * FIXME: Need to deal with: * A Radar Search request must include at least one of keyword, name, or types. --- googleplaces/__init__.py | 95 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 91 insertions(+), 4 deletions(-) diff --git a/googleplaces/__init__.py b/googleplaces/__init__.py index 43e581d..45fe302 100644 --- a/googleplaces/__init__.py +++ b/googleplaces/__init__.py @@ -178,6 +178,8 @@ class GooglePlaces(object): 'nearbysearch/json?') TEXT_SEARCH_API_URL = ('https://maps.googleapis.com/maps/api/place/' + 'textsearch/json?') + RADAR_SEARCH_API_URL = ('https://maps.googleapis.com/maps/api/place/' + + 'radarsearch/json?') DETAIL_API_URL = ('https://maps.googleapis.com/maps/api/place/details/' + 'json?') CHECKIN_API_URL = ('https://maps.googleapis.com/maps/api/place/check-in/' + @@ -204,6 +206,62 @@ def query(self, **kwargs): DeprecationWarning, stacklevel=2) return self.nearby_search(**kwargs) + def radar_search(self, language=lang.ENGLISH, keyword=None, location=None, + lat_lng=None, name=None, radius=3200, rankby=ranking.PROMINENCE, + sensor=False, types=[]): + """Perform a radar search using the Google Places API. + + One of either location or lat_lng are required, the rest of the keyword + arguments are optional. + + keyword arguments: + keyword -- A term to be matched against all available fields, including + but not limited to name, type, and address (default None) + location -- A human readable location, e.g 'London, England' + (default None) + language -- The language code, indicating in which language the + results should be returned, if possible. (default lang.ENGLISH) + lat_lng -- A dict containing the following keys: lat, lng + (default None) + name -- A term to be matched against the names of the Places. + Results will be restricted to those containing the passed + name value. (default None) + radius -- The radius (in meters) around the location/lat_lng to + restrict the search to. The maximum is 50000 meters. + (default 3200) + sensor -- Indicates whether or not the Place request came from a + device using a location sensor (default False). + types -- An optional list of types, restricting the results to + Places (default []). + """ + if location is None and lat_lng is None: + raise ValueError('One of location or lat_lng must be passed in.') + + self._sensor = sensor + self._lat_lng = (lat_lng if lat_lng is not None + else geocode_location(location)) + radius = (radius if radius <= GooglePlaces.MAXIMUM_SEARCH_RADIUS + else GooglePlaces.MAXIMUM_SEARCH_RADIUS) + lat_lng_str = '%(lat)s,%(lng)s' % self._lat_lng + self._request_params = {'location': lat_lng_str} + if len(types) > 0: + self._request_params['types'] = '|'.join(types) + if keyword is not None: + self._request_params['keyword'] = keyword + if name is not None: + self._request_params['name'] = name + if language is not None: + self._request_params['language'] = language + if radius is not None: + self._request_params['radius'] = radius + self._add_required_param_keys() + url, places_response = _fetch_remote_json( + GooglePlaces.RADAR_SEARCH_API_URL, self._request_params) + _validate_response(url, places_response) + return GooglePlacesSearchResult(self, places_response) + + + def nearby_search(self, language=lang.ENGLISH, keyword=None, location=None, lat_lng=None, name=None, radius=3200, rankby=ranking.PROMINENCE, sensor=False, types=[]): @@ -468,16 +526,43 @@ def __init__(self, query_instance, place_data): self._query_instance = query_instance self._id = place_data['id'] self._reference = place_data['reference'] - self._name = place_data['name'] + if 'name' in place_data: + self._name = place_data['name'] + self._vicinity = place_data.get('vicinity', '') + self._geo_location = place_data['geometry']['location'] + if 'rating' in place_data: + self._rating = place_data.get('rating') + if 'types' in place_data: + self._types = place_data.get('types') + if 'icon' in place_data: + self._icon = place_data.get('icon') + if place_data.get('address_components') is None: + self._details = None + else: + self._details = place_data + + def update_basic_details(self): + # Bail if there are not details to work with + if self._details is None: + return + + place_data = self._details + + if 'name' in place_data: + self._name = place_data['name'] self._vicinity = place_data.get('vicinity', '') self._geo_location = place_data['geometry']['location'] - self._rating = place_data.get('rating') - self._types = place_data.get('types') - self._icon = place_data.get('icon') + if 'rating' in place_data: + self._rating = place_data.get('rating') + if 'types' in place_data: + self._types = place_data.get('types') + if 'icon' in place_data: + self._icon = place_data.get('icon') if place_data.get('address_components') is None: self._details = None else: self._details = place_data + @property def reference(self): @@ -621,6 +706,8 @@ def get_details(self): self._details = _get_place_details( self.reference, self._query_instance.api_key, self._query_instance.sensor) + # required when doing a radar search, otherwise redundent. + self.update_basic_details() @cached_property def photos(self):