├── googleplaces ├── tests │ ├── __init__.py │ ├── tests.py │ └── testfixtures.py ├── ranking.py ├── lang.py ├── types.py └── __init__.py ├── .gitignore ├── CONTRIBUTORS ├── setup.py ├── LICENSE └── README.rst /googleplaces/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .project 3 | .pydevproject 4 | .DS_Store 5 | MANIFEST 6 | dist/ -------------------------------------------------------------------------------- /googleplaces/ranking.py: -------------------------------------------------------------------------------- 1 | """ 2 | Valid place search rankings to be optionally used in Google Place query 3 | api calls. 4 | 5 | @author: sam@slimkrazy.com 6 | """ 7 | 8 | DISTANCE = 'distance' 9 | PROMINENCE = 'prominence' 10 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | GitHub contributors 2 | --------------------- 3 | JoeJasinski 4 | personalplayground 5 | yatin-sagade 6 | yyl 7 | prateekjassal 8 | kingb 9 | fela 10 | jfk-urbandaddy 11 | twiddle 12 | elbaschid 13 | commadelimited 14 | isms 15 | scottcode 16 | jpulec 17 | ruhman 18 | drootnar 19 | keradus 20 | daniil-shumko 21 | beamerblvd 22 | pjdelport 23 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from distutils.core import setup 4 | 5 | from googleplaces import __author__, __email__, __version__ 6 | 7 | DESCRIPTION = 'A simple wrapper around the Google Places API.' 8 | 9 | setup( 10 | name = 'python-google-places', 11 | version = __version__, 12 | url = 'http://github.com/slimkrazy/python-google-places', 13 | author = __author__, 14 | author_email = __email__, 15 | packages=['googleplaces'], 16 | install_requires=[ 17 | 'six', 18 | ], 19 | description = DESCRIPTION, 20 | ) 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2013 Samuel Adu 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in the 6 | Software without restriction, including without limitation the rights to use, copy, 7 | modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 8 | and to permit persons to whom the Software is furnished to do so, subject to the 9 | following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all copies 12 | or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 15 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 16 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 17 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 18 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 19 | OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /googleplaces/lang.py: -------------------------------------------------------------------------------- 1 | """ 2 | Valid languages to be used in Google Places API calls. 3 | 4 | @author: sam@slimkrazy.com 5 | """ 6 | 7 | ARABIC = 'ar' 8 | BASQUE = 'eu' 9 | BULGARIAN = 'bg' 10 | BENGALI = 'bn' 11 | CATALAN = 'ca' 12 | CZECH ='cs' 13 | DANISH ='da' 14 | GERMAN = 'de' 15 | GREEK = 'el' 16 | ENGLISH = 'en' 17 | ENGLISH_AUSTRALIAN = 'en-AU' 18 | ENGLISH_GREAT_BRITAIN = 'en-GB' 19 | SPANISH = 'es' 20 | FARSI = 'fa' 21 | FINNISH = 'fi' 22 | FILIPINO = 'fil' 23 | FRENCH ='fr' 24 | GALICAIN = 'gl' 25 | GUJURATI = 'gu' 26 | HINDI ='hi' 27 | CROATIAN ='hr' 28 | HUNGARIAN ='hu' 29 | INDONESIAN ='id' 30 | ITALIAN = 'it' 31 | HEBREW = 'iw' 32 | JAPANESE = 'ja' 33 | KANNADA = 'kn' 34 | KOREAN = 'ko' 35 | LITHUANIAN = 'lt' 36 | LATVIAN = 'lv' 37 | MALAYALAM = 'ml' 38 | MARATHI = 'mr' 39 | DUTCH = 'nl' 40 | NORWEGIAN_NYNORSK = 'nn' 41 | NORWEGIAN = 'no' 42 | ORIYA = 'or' 43 | POLISH = 'pl' 44 | PORTUGUESE = 'pt' 45 | PORTUGUESE_BRAZIL = 'pt-BR' 46 | PORTUGUESE_PORTUGAL = 'pt-PT' 47 | ROMANSCH = 'rm' 48 | ROMANIAN = 'ro' 49 | RUSSIAN = 'ru' 50 | SLOVAK = 'sk' 51 | SLOVENIAN = 'sl' 52 | SERBIAN = 'sr' 53 | SWEDISH = 'sv' 54 | TAGALOG = 'tl' 55 | TAMIL ='ta' 56 | TELUGU = 'te' 57 | THAI = 'th' 58 | TURKISH = 'tr' 59 | UKRANIAN = 'uk' 60 | VIETNAMESE = 'vi' 61 | CHINESE_SIMPLIFIED = 'zh-CN' 62 | CHINESE_TRADITIONAL = 'zh-TW' 63 | -------------------------------------------------------------------------------- /googleplaces/tests/tests.py: -------------------------------------------------------------------------------- 1 | """ 2 | Unit tests for google places. 3 | 4 | @author: sam@slimkrazy.com 5 | """ 6 | 7 | from random import randint 8 | import unittest 9 | import warnings 10 | 11 | from googleplaces import ( 12 | GooglePlaces, 13 | GooglePlacesSearchResult, 14 | GoogleAutocompleteSearchResult, 15 | ) 16 | from testfixtures import PLACES_QUERY_RESPONSE, AUTOCOMPLETE_QUERY_RESPONSE 17 | 18 | DUMMY_API_KEY = 'foobarbaz' 19 | 20 | 21 | class Test(unittest.TestCase): 22 | 23 | def setUp(self): 24 | self._places_instance = GooglePlaces(DUMMY_API_KEY) 25 | 26 | def tearDown(self): 27 | self._places_instance = None 28 | 29 | def testSuccessfulPlaceResponse(self): 30 | query_result = GooglePlacesSearchResult( 31 | self._places_instance, 32 | PLACES_QUERY_RESPONSE) 33 | 34 | self.assertEqual(5, len(query_result.places), 'Place count is incorrect.') 35 | place_index = randint(0, len(query_result.places)-1) 36 | place = query_result.places[place_index] 37 | response_place_entity = PLACES_QUERY_RESPONSE['results'][place_index] 38 | 39 | # make sure Place.id and Place.reference raise appropriate warnings 40 | with warnings.catch_warnings(record=True) as w: 41 | place_id = place.id 42 | place_reference = place.reference 43 | self.assertEqual(len(w), 2) 44 | self.assertEqual(w[0].category, FutureWarning) 45 | self.assertEqual(w[1].category, FutureWarning) 46 | 47 | self.assertEqual(place_id, response_place_entity.get('id'), 'ID value is incorrect.') 48 | self.assertEqual(place_reference, 49 | response_place_entity.get('reference'), 50 | 'Reference value is incorrect.') 51 | self.assertEqual(place.name, response_place_entity.get('name'), 52 | 'Name value is incorrect.') 53 | self.assertEqual(place.vicinity, response_place_entity.get('vicinity'), 54 | 'Vicinity value is incorrect.') 55 | self.assertEqual( 56 | place.geo_location, 57 | response_place_entity['geometry']['location'], 58 | 'Geo-location value is incorrect.') 59 | 60 | def testSuccessfulAutocompleteResponse(self): 61 | query_result = GoogleAutocompleteSearchResult( 62 | self._places_instance, 63 | AUTOCOMPLETE_QUERY_RESPONSE) 64 | 65 | self.assertEqual(5, len(query_result.predictions), 'Prediction count is incorrect.') 66 | prediction_index = randint(0, len(query_result.predictions)-1) 67 | prediction = query_result.predictions[prediction_index] 68 | response_prediction_entity = AUTOCOMPLETE_QUERY_RESPONSE['predictions'][prediction_index] 69 | self.assertEqual(prediction.id, response_prediction_entity.get('id'), 'ID value is incorrect.') 70 | self.assertEqual( 71 | prediction.place_id, 72 | response_prediction_entity.get('place_id'), 73 | 'Place ID value is incorrect.') 74 | self.assertEqual( 75 | prediction.reference, 76 | response_prediction_entity.get('reference'), 77 | 'Reference value is incorrect.') 78 | self.assertEqual(prediction.description, 79 | response_prediction_entity.get('description'), 80 | 'Description value is incorrect.') 81 | self.assertEqual(prediction.types, 82 | response_prediction_entity.get('types'), 83 | 'Types value is incorrect.') 84 | 85 | 86 | if __name__ == "__main__": 87 | unittest.main() 88 | -------------------------------------------------------------------------------- /googleplaces/types.py: -------------------------------------------------------------------------------- 1 | """ 2 | Valid types to be optionally used in Google Place query api calls. 3 | 4 | @author: sam@slimkrazy.com 5 | """ 6 | 7 | TYPE_ACCOUNTING = 'accounting' 8 | TYPE_AIRPORT = 'airport' 9 | TYPE_AMUSEMENT_PARK = 'amusement_park' 10 | TYPE_AQUARIUM = 'aquarium' 11 | TYPE_ART_GALLERY = 'art_gallery' 12 | TYPE_ATM = 'atm' 13 | TYPE_BAKERY = 'bakery' 14 | TYPE_BANK = 'bank' 15 | TYPE_BAR = 'bar' 16 | TYPE_BEAUTY_SALON = 'beauty_salon' 17 | TYPE_BICYCLE_STORE = 'bicycle_store' 18 | TYPE_BOOK_STORE = 'book_store' 19 | TYPE_BOWLING_ALLEY = 'bowling_alley' 20 | TYPE_BUS_STATION = 'bus_station' 21 | TYPE_CAFE = 'cafe' 22 | TYPE_CAMPGROUND = 'campground' 23 | TYPE_CAR_DEALER = 'car_dealer' 24 | TYPE_CAR_RENTAL = 'car_rental' 25 | TYPE_CAR_REPAIR = 'car_repair' 26 | TYPE_CAR_WASH = 'car_wash' 27 | TYPE_CASINO = 'casino' 28 | TYPE_CEMETERY = 'cemetery' 29 | TYPE_CHURCH = 'church' 30 | TYPE_CITY_HALL = 'city_hall' 31 | TYPE_CLOTHING_STORE = 'clothing_store' 32 | TYPE_CONVENIENCE_STORE = 'convenience_store' 33 | TYPE_COURTHOUSE = 'courthouse' 34 | TYPE_DENTIST = 'dentist' 35 | TYPE_DEPARTMENT_STORE = 'department_store' 36 | TYPE_DOCTOR = 'doctor' 37 | TYPE_ELECTRICIAN = 'electrician' 38 | TYPE_ELECTRONICS_STORE = 'electronics_store' 39 | TYPE_EMBASSY = 'embassy' 40 | TYPE_ESTABLISHMENT = 'establishment' 41 | TYPE_FINANCE = 'finance' 42 | TYPE_FIRE_STATION = 'fire_station' 43 | TYPE_FLORIST = 'florist' 44 | TYPE_FOOD = 'food' 45 | TYPE_FUNERAL_HOME = 'funeral_home' 46 | TYPE_FURNITURE_STORE = 'furniture_store' 47 | TYPE_GAS_STATION = 'gas_station' 48 | TYPE_GENERAL_CONTRACTOR = 'general_contractor' 49 | TYPE_GEOCODE = 'geocode' 50 | TYPE_GROCERY_OR_SUPERMARKET = 'grocery_or_supermarket' 51 | TYPE_GYM = 'gym' 52 | TYPE_HAIR_CARE = 'hair_care' 53 | TYPE_HARDWARE_STORE = 'hardware_store' 54 | TYPE_HEALTH = 'health' 55 | TYPE_HINDU_TEMPLE = 'hindu_temple' 56 | TYPE_HOME_GOODS_STORE = 'home_goods_store' 57 | TYPE_HOSPITAL = 'hospital' 58 | TYPE_INSURANCE_AGENCY = 'insurance_agency' 59 | TYPE_JEWELRY_STORE = 'jewelry_store' 60 | TYPE_LAUNDRY = 'laundry' 61 | TYPE_LAWYER = 'lawyer' 62 | TYPE_LIBRARY = 'library' 63 | TYPE_LIQUOR_STORE = 'liquor_store' 64 | TYPE_LOCAL_GOVERNMENT_OFFICE = 'local_government_office' 65 | TYPE_LOCKSMITH = 'locksmith' 66 | TYPE_LODGING = 'lodging' 67 | TYPE_MEAL_DELIVERY = 'meal_delivery' 68 | TYPE_MEAL_TAKEAWAY = 'meal_takeaway' 69 | TYPE_MOSQUE = 'mosque' 70 | TYPE_MOVIE_RENTAL = 'movie_rental' 71 | TYPE_MOVIE_THEATER = 'movie_theater' 72 | TYPE_MOVING_COMPANY = 'moving_company' 73 | TYPE_MUSEUM = 'museum' 74 | TYPE_NIGHT_CLUB = 'night_club' 75 | TYPE_PAINTER = 'painter' 76 | TYPE_PARK = 'park' 77 | TYPE_PARKING = 'parking' 78 | TYPE_PET_STORE = 'pet_store' 79 | TYPE_PHARMACY = 'pharmacy' 80 | TYPE_PHYSIOTHERAPIST = 'physiotherapist' 81 | TYPE_PLACE_OF_WORSHIP = 'place_of_worship' 82 | TYPE_PLUMBER = 'plumber' 83 | TYPE_POLICE = 'police' 84 | TYPE_POST_OFFICE = 'post_office' 85 | TYPE_REAL_ESTATE_AGENCY = 'real_estate_agency' 86 | TYPE_RESTAURANT = 'restaurant' 87 | TYPE_ROOFING_CONTRACTOR = 'roofing_contractor' 88 | TYPE_RV_PARK = 'rv_park' 89 | TYPE_SCHOOL = 'school' 90 | TYPE_SHOE_STORE = 'shoe_store' 91 | TYPE_SHOPPING_MALL = 'shopping_mall' 92 | TYPE_SPA = 'spa' 93 | TYPE_STADIUM = 'stadium' 94 | TYPE_STORAGE = 'storage' 95 | TYPE_STORE = 'store' 96 | TYPE_SUBWAY_STATION = 'subway_station' 97 | TYPE_SYNAGOGUE = 'synagogue' 98 | TYPE_TAXI_STAND = 'taxi_stand' 99 | TYPE_TRAIN_STATION = 'train_station' 100 | TYPE_TRAVEL_AGENCY = 'travel_agency' 101 | TYPE_UNIVERSITY = 'university' 102 | TYPE_VETERINARY_CARE = 'veterinary_care' 103 | TYPE_ZOO = 'zoo' 104 | 105 | # The following types supported by the Places API when sending 106 | # Place Search requests. These types cannot be used when adding a new Place. 107 | 108 | TYPE_ADMINISTRATIVE_AREA_LEVEL_1 = 'administrative_area_level_1' 109 | TYPE_ADMINISTRATIVE_AREA_LEVEL_2 = 'administrative_area_level_2' 110 | TYPE_ADMINISTRATIVE_AREA_LEVEL_3 = 'administrative_area_level_3' 111 | TYPE_COLLOQUIAL_AREA = 'colloquial_area' 112 | TYPE_COUNTRY = 'country' 113 | TYPE_FLOOR = 'floor' 114 | TYPE_INTERSECTION = 'intersection' 115 | TYPE_LOCALITY = 'locality' 116 | TYPE_NATURAL_FEATURE = 'natural_feature' 117 | TYPE_NEIGHBORHOOD = 'neighborhood' 118 | TYPE_POLITICAL = 'political' 119 | TYPE_POINT_OF_INTEREST = 'point_of_interest' 120 | TYPE_POST_BOX = 'post_box' 121 | TYPE_POSTAL_CODE = 'postal_code' 122 | TYPE_POSTAL_CODE_PREFIX = 'postal_code_prefix' 123 | TYPE_POSTAL_TOWN = 'postal_town' 124 | TYPE_PREMISE = 'premise' 125 | TYPE_ROOM = 'room' 126 | TYPE_ROUTE = 'route' 127 | TYPE_STREET_ADDRESS = 'street_address' 128 | TYPE_STREET_NUMBER = 'street_number' 129 | TYPE_SUBLOCALITY = 'sublocality' 130 | TYPE_SUBLOCALITY_LEVEL_4 = 'sublocality_level_4' 131 | TYPE_SUBLOCALITY_LEVEL_5 = 'sublocality_level_5' 132 | TYPE_SUBLOCALITY_LEVEL_3 = 'sublocality_level_3' 133 | TYPE_SUBLOCALITY_LEVEL_2 = 'sublocality_level_2' 134 | TYPE_SUBLOCALITY_LEVEL_1 = 'sublocality_level_1' 135 | TYPE_SUBPREMISE = 'subpremise' 136 | TYPE_TRANSIT_STATION = 'transit_station' 137 | 138 | 139 | # autocomplete types 140 | AC_TYPE_GEOCODE = 'geocode' 141 | AC_TYPE_ADDDRESS = 'address' 142 | AC_TYPE_ESTABLISHMENT = 'establishment' 143 | AC_TYPE_REGIONS = '(regions)' 144 | AC_TYPE_CITIES = '(cities)' 145 | -------------------------------------------------------------------------------- /googleplaces/tests/testfixtures.py: -------------------------------------------------------------------------------- 1 | """ 2 | Sample JSON responses pulled from Google Places API. 3 | """ 4 | 5 | PLACES_QUERY_RESPONSE = { 6 | "html_attributions" : [ 7 | "Listings by \u003ca href=\"http://www.yellowpages.com.au/\"\u003eYellow Pages\u003c/a\u003e" 8 | ], 9 | "results" : [ 10 | { 11 | "geometry" : { 12 | "location" : { 13 | "lat" : -33.8719830, 14 | "lng" : 151.1990860 15 | } 16 | }, 17 | "icon" : "http://maps.gstatic.com/mapfiles/place_api/icons/restaurant-71.png", 18 | "id" : "677679492a58049a7eae079e0890897eb953d79b", 19 | "name" : "Zaaffran Restaurant - BBQ and GRILL, Darling Harbour", 20 | "rating" : 3.90, 21 | "reference" : "CpQBjAAAAFAOaZhKjoDYfDsnISY6p4DKgdtrXTLJBhYsF0WnLBrkLHN3LdLpxc9VsbQKfbtg87nnDsl-SdCKT60Vs4Sxe_lCNCgRBxgq0JBBj8maNZ9pEp_LWjq8O-shdjh-LexdN5o-ZYLVBXhqX2az4TFvuOqme0eRirqMyatKgfn9nuKEkKR2a5tfFQlMfSZSlbyoOxIQVffhpcBqaua-_Yb364wx9xoUC1I-81Wj7aBmSmkctXv_YE7jqgQ", 22 | "place_id": "ChIJfZmx2Jxw44kR11GqlDbYxxx", 23 | "types" : [ "restaurant", "food", "establishment" ], 24 | "vicinity" : "Harbourside Centre 10 Darling Drive, Darling Harbour, Sydney" 25 | }, 26 | { 27 | "geometry" : { 28 | "location" : { 29 | "lat" : -33.8721970, 30 | "lng" : 151.1987820 31 | } 32 | }, 33 | "icon" : "http://maps.gstatic.com/mapfiles/place_api/icons/restaurant-71.png", 34 | "id" : "27ea39c8fed1c0437069066b8dccf958a2d06f19", 35 | "name" : "Criniti's", 36 | "rating" : 3.10, 37 | "reference" : "CmRgAAAAm4ajUz0FWaV2gB5mBbdIFhg-Jn98p1AQOrr1QxUWh7Q0nhEUhZL-hY9L4l5ifvRfGttf_gyBpSsGaxMjnr_pcPGUIQKES0vScLQpwM7jsS3BQKB83G9B_SlJFcRuD5dDEhCoNxepsgfJ5YSuXlYjVo9tGhQaKigmZ0WQul__A702AiH3WIy6-A", 38 | "place_id": "ChIJfZmx2Jxw44kR11GqlDbYsss", 39 | "types" : [ "restaurant", "food", "establishment" ], 40 | "vicinity" : "231/10 Darling Dr, DARLING HARBOUR" 41 | }, 42 | { 43 | "geometry" : { 44 | "location" : { 45 | "lat" : -33.8720340, 46 | "lng" : 151.198540 47 | } 48 | }, 49 | "icon" : "http://maps.gstatic.com/mapfiles/place_api/icons/restaurant-71.png", 50 | "id" : "cb853832ab8368db3adc52c657fe063dac0f3b11", 51 | "name" : "Al Ponte Harbour View Restaurant", 52 | "reference" : "CoQBeAAAAMQ4yYBquhcHj8qzcgUNdwgeiIOhh-Eyf21y9J58y9JXVO7yzw1mFd_wKKjEYJLR_PPjbPRGJEDFnR6eCK_zw1qwrzdyxjnM2zwvdiJ-MLwt3PxVvkkPAjLJYp1cerBc0KTyUVfBo7B4U7RFt4r3DueQ4mz6N-6G7CBoddtfRnm5EhCSGc8yi1k4EQ8whHhKfzxpGhTA1mKVV8kydhqLCsbWDitFMxqzvA", 53 | "place_id": "ChIJfZmx2Jxw44kR11GqlDbYyyy", 54 | "types" : [ "restaurant", "food", "establishment" ], 55 | "vicinity" : "10 Darling Dr, Sydney CBD" 56 | }, 57 | { 58 | "geometry" : { 59 | "location" : { 60 | "lat" : -33.8711490, 61 | "lng" : 151.1985180 62 | } 63 | }, 64 | "icon" : "http://maps.gstatic.com/mapfiles/place_api/icons/restaurant-71.png", 65 | "id" : "400d8b4ee30d39175f69fddfcc50590860e59d62", 66 | "name" : "JB's Bar at Holiday Inn Darling Harbour", 67 | "reference" : "CoQBfgAAACn9RQ5w_BCAcdF14KQjTh_youPZUA5a7Fbbc74gu3gWaGkl78jlDnIYuUCNOEBs4Up-iw_KrHHDRx58A91Pwqnhrf5RSMihz5gAj3M7X7IW8a_Qxl7-MuAbkoNd6rTbHXtTTWtFtKAhQBljsHPahn0kDPXXSwrhn3WjSfFQX6FfEhCWPSB0ISfYioqpCBWFveZlGhSdW7eYv0NUEAtgTAzJ7x0r4NDHPQ", 68 | "place_id": "ChIJfZmx2Jxw44kR11GqlDbYzzz", 69 | "types" : [ "restaurant", "food", "establishment" ], 70 | "vicinity" : "Furama Hotel, 68 Harbour Street, Darling Harbour, Sydney" 71 | }, 72 | { 73 | "geometry" : { 74 | "location" : { 75 | "lat" : -33.8711490, 76 | "lng" : 151.1985180 77 | } 78 | }, 79 | "icon" : "http://maps.gstatic.com/mapfiles/place_api/icons/generic_business-71.png", 80 | "id" : "f12a10b450db59528c18e57dea9f56f88c65c2fa", 81 | "name" : "Health Harbour", 82 | "reference" : "CnRlAAAA97YiSpT9ArwBWRZ_7FeddhMtQ4rGTy9v277_B4Y3jxUFKkZVczf3YHrhSLGuKugNQQpCDMWjYKv6LkSA8CiECzh5z7B2wOMkhn0PGjpq01p0QRapJuA6z9pQFS_oTeUq0M_paSCQ_GEB8A5-PpkJXxIQHAuoj0nyrgNwjLtByDHAgBoUdHaA6D2ceLp8ga5IJqxfqOnOwS4", 83 | "place_id": "ChIJfZmx2Jxw44kR11GqlDbYmmm", 84 | "types" : [ "food", "store", "establishment" ], 85 | "vicinity" : "Darling Harbour" 86 | } 87 | ], 88 | "status" : "OK" 89 | } 90 | 91 | AUTOCOMPLETE_QUERY_RESPONSE = { 92 | 'predictions': [ 93 | { 94 | 'description': u"Hattie B's Hot Chicken, 19th Avenue South, Nashville, TN, United States", 95 | 'id': '8d41b3887c71f5240a26d8b5badc792708e5ea2a', 96 | 'matched_substrings': [{'length': 6, 'offset': 0}], 97 | 'place_id': 'ChIJSbQCipNmZIgRm4c6Nz9sGaE', 98 | 'reference': 'CmRWAAAAqGYRANjgide_-pBFmUnaOY-m5Cy0RV8by6-pDkB_FsqouiehU-j-dV6oAdZuoMueEvKAqE1FAXcNivsB0mx9a40EEpPOXKKSiag_8wuFAlwGpeQUoXwn_ccF5zs6vldhEhAm161WaDqwSfBFKqjRE04vGhS3BcwJ2MeUbpuPbtVTUx3w3OEpLQ', 99 | "place_id": "ChIJfZmx2Jxw44kR11GqlDbYnnn", 100 | 'terms': [ 101 | {'offset': 0, 'value': u"Hattie B's Hot Chicken"}, 102 | {'offset': 24, 'value': '19th Avenue South'}, 103 | {'offset': 43, 'value': 'Nashville'}, 104 | {'offset': 54, 'value': 'TN'}, 105 | {'offset': 58, 'value': 'United States'} 106 | ], 107 | 'types': ['establishment'] 108 | }, 109 | { 110 | 'description': 'Hattiesburg, MS, United States', 111 | 'id': '25f9c17302a8a1a8e432c53d6d4c712063e93188', 112 | 'matched_substrings': [{'length': 6, 'offset': 0}], 113 | 'place_id': 'ChIJ3yio1UncnIgRyrUcLZK_sXQ', 114 | 'reference': 'CkQ2AAAAwRcCnvJl5ZvhsZ6TxUrkpDqPIW7mVIANGti65-SK7MlCNEPj7vGkN2Dh_KnxI7XI6pyDcTsXCoi7wLnQrr4H7xIQ_D35yzn61ZNmQelYcjk9xBoUsgP8yimN1xd9J4L6k4b6ZpK_E94', 115 | "place_id": "ChIJfZmx2Jxw44kR11GqlDbYooo", 116 | 'terms': [ 117 | {'offset': 0, 'value': 'Hattiesburg'}, 118 | {'offset': 13, 'value': 'MS'}, 119 | {'offset': 17, 'value': 'United States'} 120 | ], 121 | 'types': ['locality', 'political', 'geocode']\ 122 | }, 123 | { 124 | 'description': 'Hattie Cotton Elementary School, Nashville, TN, United States', 125 | 'id': 'f8998e62bf80300a07a8d007b890bb17368b3fd4', 126 | 'matched_substrings': [{'length': 6, 'offset': 0}], 127 | 'place_id': 'ChIJXzft89tnZIgRm0ovqWZebMs', 128 | 'reference': 'ClRMAAAAVe4DIy63p4YGFHVQpmlrgBXkA1K0T6xZbWm2SHitS28bZcPl2ctmi7QNKR2EX_2RNY5ckfPVIBL3iDOmhzjeuCrn48qNnoE0z9QJzskfdnASEKo5EvKlKYNuqep4dyThEDcaFFYegROsLxF0tssPzZAYyL9ra70z', 129 | "place_id": "ChIJfZmx2Jxw44kR11GqlDbYppp", 130 | 'terms': [ 131 | {'offset': 0, 'value': 'Hattie Cotton Elementary School'}, 132 | {'offset': 33, 'value': 'Nashville'}, 133 | {'offset': 44, 'value': 'TN'}, 134 | {'offset': 48, 'value': 'United States'} 135 | ], 136 | 'types': ['establishment'] 137 | }, 138 | { 139 | 'description': 'Hattie Ct, Hendersonville, TN, United States', 140 | 'id': 'eaeb7403d82be002de3ae52ab811d4014d4dff5b', 141 | 'matched_substrings': [{'length': 6, 'offset': 0}], 142 | 'place_id': 'EixIYXR0aWUgQ3QsIEhlbmRlcnNvbnZpbGxlLCBUTiwgVW5pdGVkIFN0YXRlcw', 143 | 'reference': 'CjQwAAAA16vktOVr7wLMig9H8Eb0CQFaDuIhqsG-3G7YCH7NE9qKxImG9p8W_mXbhjnkg9f4EhA6hP-p7r4I-iBMu6vcpb2VGhRFB4IPb0QupUlhD_B-Q8T6UJ8NQQ', 144 | "place_id": "ChIJfZmx2Jxw44kR11GqlDbYqqq", 145 | 'terms': [ 146 | {'offset': 0, 'value': 'Hattie Ct'}, 147 | {'offset': 11, 'value': 'Hendersonville'}, 148 | {'offset': 27, 'value': 'TN'}, 149 | {'offset': 31, 'value': 'United States'} 150 | ], 151 | 'types': ['route', 'geocode'] 152 | }, 153 | { 154 | 'description': 'Hattieville, AR, United States', 155 | 'id': '4a144096d3107fd6f3ef0254c5ca4dd106abebcd', 156 | 'matched_substrings': [{'length': 6, 'offset': 0}], 157 | 'place_id': 'ChIJjTUp5w2izYcRwRTy8ZpwjkI', 158 | 'reference': 'CkQ2AAAA-9G9z7Y3SSTPJOxNcCSVhUxeaC7OTL_ADLq00_WXSHBhXi1PTsm08gX2Zz_uLnLTBFJhqxm4g9HrbTO8Rm25lBIQepnjuQ02A6BqH2lNVjsIzRoUM_ndW4AhrGZFxnKOWSAPWfAm7hY', 159 | "place_id": "ChIJfZmx2Jxw44kR11GqlDbYrrr", 160 | 'terms': [ 161 | {'offset': 0, 'value': 'Hattieville'}, 162 | {'offset': 13, 'value': 'AR'}, 163 | {'offset': 17, 'value': 'United States'} 164 | ], 165 | 'types': ['locality', 'political', 'geocode'] 166 | } 167 | ], 168 | 'status': 'OK' 169 | } -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | python-google-places 2 | ======================= 3 | 4 | .. _introduction: 5 | 6 | **python-google-places** provides a simple wrapper around the experimental 7 | Google Places API. 8 | 9 | 10 | Installation 11 | ----------------- 12 | 13 | .. _installation: 14 | 15 | .. code-block:: sh 16 | 17 | pip install https://github.com/slimkrazy/python-google-places/zipball/master 18 | 19 | OR 20 | 21 | .. code-block:: sh 22 | 23 | pip install python-google-places 24 | 25 | OR 26 | 27 | Download source and then: 28 | 29 | .. code-block:: sh 30 | 31 | python setup.py install 32 | 33 | 34 | Prerequisites 35 | ----------------- 36 | .. _prerequisites: 37 | 38 | A Google API key with Google Places API Web Service and Google Maps Geocoding API activated against 39 | it. Please check the Google API console, here: http://code.google.com/apis/console 40 | 41 | 42 | Usage 43 | ------ 44 | 45 | .. _usage: 46 | 47 | Code is easier to understand than words, so let us dive right in: 48 | 49 | .. code-block:: python 50 | 51 | from googleplaces import GooglePlaces, types, lang 52 | 53 | YOUR_API_KEY = 'AIzaSyAiFpFd85eMtfbvmVNEYuNds5TEF9FjIPI' 54 | 55 | google_places = GooglePlaces(YOUR_API_KEY) 56 | 57 | # You may prefer to use the text_search API, instead. 58 | query_result = google_places.nearby_search( 59 | location='London, England', keyword='Fish and Chips', 60 | radius=20000, types=[types.TYPE_FOOD]) 61 | # If types param contains only 1 item the request to Google Places API 62 | # will be send as type param to fullfil: 63 | # http://googlegeodevelopers.blogspot.com.au/2016/02/changes-and-quality-improvements-in_16.html 64 | 65 | if query_result.has_attributions: 66 | print query_result.html_attributions 67 | 68 | 69 | for place in query_result.places: 70 | # Returned places from a query are place summaries. 71 | print place.name 72 | print place.geo_location 73 | print place.place_id 74 | 75 | # The following method has to make a further API call. 76 | place.get_details() 77 | # Referencing any of the attributes below, prior to making a call to 78 | # get_details() will raise a googleplaces.GooglePlacesAttributeError. 79 | print place.details # A dict matching the JSON response from Google. 80 | print place.local_phone_number 81 | print place.international_phone_number 82 | print place.website 83 | print place.url 84 | 85 | # Getting place photos 86 | 87 | for photo in place.photos: 88 | # 'maxheight' or 'maxwidth' is required 89 | photo.get(maxheight=500, maxwidth=500) 90 | # MIME-type, e.g. 'image/jpeg' 91 | photo.mimetype 92 | # Image URL 93 | photo.url 94 | # Original filename (optional) 95 | photo.filename 96 | # Raw image data 97 | photo.data 98 | 99 | 100 | # Are there any additional pages of results? 101 | if query_result.has_next_page_token: 102 | query_result_next_page = google_places.nearby_search( 103 | pagetoken=query_result.next_page_token) 104 | 105 | 106 | # Adding and deleting a place 107 | try: 108 | added_place = google_places.add_place(name='Mom and Pop local store', 109 | lat_lng={'lat': 51.501984, 'lng': -0.141792}, 110 | accuracy=100, 111 | types=types.TYPE_HOME_GOODS_STORE, 112 | language=lang.ENGLISH_GREAT_BRITAIN) 113 | print added_place.place_id # The Google Places identifier - Important! 114 | print added_place.id 115 | 116 | # Delete the place that you've just added. 117 | google_places.delete_place(added_place.place_id) 118 | except GooglePlacesError as error_detail: 119 | # You've passed in parameter values that the Places API doesn't like.. 120 | print error_detail 121 | 122 | 123 | Reference 124 | ---------- 125 | 126 | :: 127 | 128 | googleplaces.GooglePlacesError 129 | googleplaces.GooglePlacesAttributeError 130 | 131 | 132 | googleplaces.geocode_location(location, sensor=False, api_key=None) 133 | Converts a human-readable location to a Dict containing the keys: lat, lng. 134 | Raises googleplaces.GooglePlacesError if the geocoder fails to find the 135 | specified location. 136 | 137 | 138 | googleplaces.GooglePlaces 139 | nearby_search(**kwargs) 140 | Returns googleplaces.GooglePlacesSearchResult 141 | kwargs: 142 | keyword -- A term to be matched against all available fields, including but 143 | not limited to name, type, and address (default None) 144 | 145 | language -- The language code, indicating in which language the results 146 | should be returned, if possble. (default en) 147 | 148 | lat_lng -- A dict containing the following keys: lat, lng (default None) 149 | 150 | location -- A human readable location, e.g 'London, England' (default None) 151 | 152 | name -- A term to be matched against the names of the Places. 153 | Results will be restricted to those containing the passed name value. (default None) 154 | 155 | pagetoken-- Optional parameter to force the search result to return the next 156 | 20 results from a previously run search. Setting this parameter 157 | will execute a search with the same parameters used previously. (default None) 158 | 159 | radius -- The radius (in meters) around the location/lat_lng to restrict 160 | the search to. The maximum is 50000 meters (default 3200) 161 | 162 | rankby -- Specifies the order in which results are listed: 163 | 'prominence' (default) or 'distance' (imply no radius argument) 164 | 165 | sensor -- Indicates whether or not the Place request came from a device 166 | using a location sensor (default False) 167 | 168 | type -- An optional type used for restricting the results to Places (default None) 169 | 170 | types -- An optional list of types, restricting the results to Places (default []). 171 | This kwarg has been deprecated in favour of the 'type' kwarg. 172 | 173 | 174 | 175 | text_search(**kwargs) 176 | Returns googleplaces.GooglePlacesSearchResult 177 | kwargs: 178 | query -- The text string on which to search, for example: 179 | "Restaurant in New York". 180 | 181 | lat_lng -- A dict containing the following keys: lat, lng (default None) 182 | 183 | location -- A human readable location, e.g 'London, England' (default None) 184 | 185 | language -- The language code, indicating in which language the results 186 | should be returned, if possble. (default en) 187 | 188 | pagetoken-- Optional parameter to force the search result to return the next 189 | 20 results from a previously run search. Setting this parameter 190 | will execute a search with the same parameters used previously. (default None) 191 | 192 | radius -- The radius (in meters) around the location/lat_lng to restrict 193 | the search to. The maximum is 50000 meters (default 3200) 194 | 195 | type -- An optional type used for restricting the results to Places (default None) 196 | 197 | types -- An optional list of types, restricting the results to Places (default []) 198 | This kwarg has been deprecated in favour of the 'type' kwarg. 199 | 200 | autocomplete(**kwargs): 201 | Returns googleplaces.GoogleAutocompleteSearchResult 202 | kwargs: 203 | input -- The text string on which to search, for example: 204 | "Hattie B's". 205 | 206 | lat_lng -- A dict containing the following keys: lat, lng (default None) 207 | 208 | location -- A human readable location, e.g 'London, England' (default None) 209 | 210 | radius -- The radius (in meters) around the location to which the 211 | search is to be restricted. The maximum is 50000 meters. 212 | (default 3200) 213 | 214 | language -- The language code, indicating in which language the 215 | results should be returned, if possible. (default lang.ENGLISH) 216 | 217 | types -- A type to search against. See `types.py` "autocomplete types" 218 | for complete list 219 | https://developers.google.com/places/documentation/autocomplete#place_types. 220 | 221 | components -- An optional grouping of places to which you would 222 | like to restrict your results. An array containing one or 223 | more tuples of: 224 | * country: matches a country name or a two letter ISO 3166-1 country code. 225 | eg: [('country','US')] 226 | 227 | radar_search(**kwargs) 228 | Returns googleplaces.GooglePlacesSearchResult 229 | kwargs: 230 | keyword -- A term to be matched against all available fields, including 231 | but not limited to name, type, and address (default None) 232 | 233 | name -- A term to be matched against the names of Places. Results will 234 | be restricted to those containing the passed name value. 235 | 236 | opennow -- Returns only those Places that are open for business at the time 237 | the query is sent 238 | 239 | lat_lng -- A dict containing the following keys: lat, lng (default None) 240 | 241 | location -- A human readable location, e.g 'London, England' (default None) 242 | 243 | language -- The language code, indicating in which language the results 244 | should be returned, if possble. (default en) 245 | 246 | radius -- The radius (in meters) around the location/lat_lng to restrict 247 | the search to. The maximum is 50000 meters (default 3200) 248 | 249 | sensor -- Indicates whether or not the Place request came from a 250 | device using a location sensor (default False). 251 | 252 | type -- An optional type used for restricting the results to Places (default None) 253 | 254 | types -- An optional list of types, restricting the results to Places (default []) 255 | This kwarg has been deprecated in favour of the 'type' kwarg. 256 | 257 | get_place(**kwargs) 258 | Returns a detailed instance of googleplaces.Place 259 | place_id -- The unique Google identifier for the required place. 260 | 261 | language -- The language code, indicating in which language the results 262 | should be returned, if possble. (default en) 263 | 264 | sensor -- Indicates whether or not the Place request came from a 265 | device using a location sensor (default False). 266 | 267 | 268 | checkin(place_id, sensor=False) 269 | Checks in an anonymous user in to the Place that matches the place_id. 270 | kwargs: 271 | place_id -- The unique Google identifier for the required place. 272 | 273 | sensor -- Boolean flag denoting if the location came from a device 274 | using its location sensor (default False). 275 | 276 | 277 | add_place(**kwargs) 278 | Returns a dict containing the following keys: place_id, id. 279 | kwargs: 280 | name -- The full text name of the Place. Limited to 255 281 | characters. 282 | 283 | lat_lng -- A dict containing the following keys: lat, lng. 284 | 285 | accuracy -- The accuracy of the location signal on which this request 286 | is based, expressed in meters. 287 | 288 | types -- The category in which this Place belongs. Only one type 289 | can currently be specified for a Place. A string or 290 | single element list may be passed in. 291 | 292 | language -- The language in which the Place's name is being reported. 293 | (default googleplaces.lang.ENGLISH). 294 | 295 | sensor -- Boolean flag denoting if the location came from a device 296 | using its location sensor (default False). 297 | 298 | 299 | delete_place(place_id, sensor=False) 300 | Deletes a place from the Google Places database. 301 | kwargs: 302 | place_id -- The unique Google identifier for the required place. 303 | 304 | sensor -- Boolean flag denoting if the location came from a 305 | device using its location sensor (default False). 306 | 307 | 308 | googleplaces.GoogleAutocompleteSearchResult 309 | raw_response 310 | Returns the raw JSON response from the Autocomplete API. 311 | 312 | predictions 313 | Returns an array of prediction objects. 314 | 315 | 316 | googleplaces.GooglePlacesSearchResult 317 | raw_response 318 | The raw JSON response returned by the Google Places API. 319 | 320 | places 321 | A list of summary googleplaces.Place instances. 322 | 323 | has_attributions() 324 | Returns a flag indicating if the search result has html attributions that 325 | must be displayed. 326 | 327 | html_attributions() 328 | Returns a List of String html attributions that must be displayed along with 329 | the search results. 330 | 331 | 332 | googleplaces.Prediction 333 | description 334 | String representation of a Prediction location. Generally contains 335 | name, country, and elements contained in the terms property. 336 | 337 | id 338 | Returns a unique stable identifier denoting this Place. This identifier 339 | may not be used to retrieve information about this Place, but can be used 340 | to consolidate data about this Place, and to verify the identity of a 341 | Place across separate searches 342 | 343 | matched_substrings 344 | Returns the placement and offset of the matched strings for this search. 345 | A an array of dicts, each with the keys 'length' and 'offset', will be returned. 346 | 347 | place_id 348 | Returns the unique stable identifier denoting this place. 349 | This identifier may be used to retrieve information about this 350 | place. 351 | This should be considered the primary identifier of a place. 352 | 353 | reference 354 | Returns a unique identifier for the Place that can be used to fetch full 355 | details about it. It is recommended that stored references for Places be 356 | regularly updated. A Place may have many valid reference tokens. 357 | 358 | terms 359 | A list of terms which build up the description string 360 | A an array of dicts, each with the keys `offset` and `value`, will be returned. 361 | 362 | types 363 | Returns a List of feature types describing the given result. 364 | 365 | place 366 | Returns a Dict representing the full response from the details API request. 367 | This property will raise a googleplaces.GooglePlacesAttributeError if it is 368 | referenced prior to get_details() 369 | 370 | get_details(**kwargs) 371 | Retrieves full information on the place matching the reference. 372 | kwargs: 373 | language -- The language code, indicating in which language the 374 | results should be returned, if possible. This value defaults 375 | to the language that was used to generate the 376 | GooglePlacesSearchResult instance. 377 | 378 | 379 | googleplaces.Place 380 | reference 381 | (DEPRECATED) Returns a unique identifier for the Place that can be used to 382 | fetch full details about it. It is recommended that stored references for 383 | Places be regularly updated. A Place may have many valid reference tokens. 384 | 385 | id 386 | (DEPECATED) Returns a unique stable identifier denoting this Place. This 387 | identifier may not be used to retrieve information about this Place, but 388 | can be used to consolidate data about this Place, and to verify the identity 389 | of a Place across separate searches. 390 | 391 | place_id 392 | A textual identifier that uniquely identifies a place. To retrieve information 393 | about the place, pass this identifier in the placeId field of a Places API 394 | request. 395 | 396 | icon 397 | contains the URL of a suggested icon which may be displayed to the user when 398 | indicating this result on a map. 399 | 400 | types 401 | Returns a List of feature types describing the given result. 402 | 403 | geo_location 404 | Returns the geocoded latitude,longitude value for this Place. 405 | 406 | name 407 | Returns the human-readable name for the Place. 408 | 409 | vicinity 410 | Returns a feature name of a nearby location. Often this feature refers to a 411 | street or neighborhood. 412 | 413 | rating 414 | Returns the Place's rating, from 0.0 to 5.0, based on user reviews. 415 | 416 | details 417 | Returns a Dict representing the full response from the details API request. 418 | This property will raise a googleplaces.GooglePlacesAttributeError if it is 419 | referenced prior to get_details() 420 | 421 | photos 422 | returns a list of available googleplaces.Photo objects. 423 | 424 | formatted_address 425 | Returns a string containing the human-readable address of this place. Often 426 | this address is equivalent to the "postal address". 427 | This property will raise a googleplaces.GooglePlacesAttributeError if it is 428 | referenced prior to get_details() 429 | 430 | local_phone_number 431 | Returns the Place's phone number in its local format. 432 | This property will raise a googleplaces.GooglePlacesAttributeError if it is 433 | referenced prior to get_details() 434 | 435 | international_phone_number 436 | Returns the Place's phone number in international format. International 437 | format includes the country code, and is prefixed with the plus (+) sign. 438 | This property will raise a googleplaces.GooglePlacesAttributeError if it is 439 | referenced prior to get_details() 440 | 441 | website 442 | Returns the authoritative website for this Place, such as a business' 443 | homepage. 444 | 445 | url 446 | Returns the official Google Place Page URL of this Place. 447 | 448 | has_attributions 449 | Returns a flag indicating if the search result has html attributions that 450 | must be displayed. along side the detailed query result. 451 | 452 | html_attributions 453 | Returns a List of String html attributions that must be displayed along with 454 | the detailed query result. 455 | 456 | checkin() 457 | Checks in an anonynomous user in. 458 | 459 | get_details(**kwargs) 460 | Retrieves full information on the place matching the place_id. 461 | kwargs: 462 | language -- The language code, indicating in which language the 463 | results should be returned, if possible. This value defaults 464 | to the language that was used to generate the 465 | GooglePlacesSearchResult instance. 466 | 467 | googleplaces.Photo 468 | orig_height 469 | the maximum height of the origin image. 470 | 471 | orig_width 472 | the maximum height of the origin image. 473 | 474 | html_attributions 475 | Contains any required attributions. This field will always be present, 476 | but may be empty. 477 | 478 | photo_reference 479 | A string used to identify the photo when you perform a Photo request 480 | via the get method. 481 | 482 | get 483 | Fetches the actual photo data from the Google places API. 484 | 485 | mimetype 486 | Specifies the mimetype if the fetched image. This property is only 487 | available after the get API has been invoked. 488 | 489 | filename 490 | Specifies the filename of the fetched image. This property is only 491 | available after the get API has been invoked. 492 | 493 | data 494 | The binary data of the image. This property is only available after the 495 | get API has been invoked. 496 | 497 | url 498 | The url of the image. This property is only available after the get API 499 | has been invoked. 500 | -------------------------------------------------------------------------------- /googleplaces/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | A simple wrapper around the 'experimental' Google Places API, documented 3 | here: http://code.google.com/apis/maps/documentation/places/. This library 4 | also makes use of the v3 Maps API for geocoding. 5 | 6 | Prerequisites: A Google API key with Places activated against it. Please 7 | check the Google API console, here: http://code.google.com/apis/console 8 | 9 | NOTE: Please ensure that you read the Google terms of service (labelled 'Limits 10 | and Requirements' on the documentation url) prior to using this library in a 11 | production environment. 12 | 13 | @author: sam@slimkrazy.com 14 | """ 15 | from __future__ import absolute_import 16 | 17 | import cgi 18 | from decimal import Decimal 19 | try: 20 | import json 21 | except ImportError: 22 | import simplejson as json 23 | 24 | try: 25 | import six 26 | from six.moves import urllib 27 | except ImportError: 28 | pass 29 | 30 | import warnings 31 | 32 | from . import lang 33 | from . import ranking 34 | 35 | 36 | __all__ = ['GooglePlaces', 'GooglePlacesError', 'GooglePlacesAttributeError', 37 | 'geocode_location'] 38 | __version__ = '1.4.2' 39 | __author__ = 'Samuel Adu' 40 | __email__ = 'sam@slimkrazy.com' 41 | 42 | 43 | class cached_property(object): 44 | def __init__(self, func): 45 | self.func = func 46 | 47 | def __get__(self, instance, cls=None): 48 | result = instance.__dict__[self.func.__name__] = self.func(instance) 49 | return result 50 | 51 | 52 | def _fetch_remote(service_url, params=None, use_http_post=False): 53 | if not params: 54 | params = {} 55 | 56 | encoded_data = {} 57 | for k, v in params.items(): 58 | if isinstance(v, six.string_types): 59 | v = v.encode('utf-8') 60 | encoded_data[k] = v 61 | encoded_data = urllib.parse.urlencode(encoded_data) 62 | 63 | if not use_http_post: 64 | query_url = (service_url if service_url.endswith('?') else 65 | '%s?' % service_url) 66 | request_url = query_url + encoded_data 67 | request = urllib.request.Request(request_url) 68 | else: 69 | request_url = service_url 70 | request = urllib.request.Request(service_url, data=encoded_data) 71 | return (request_url, urllib.request.urlopen(request)) 72 | 73 | def _fetch_remote_json(service_url, params=None, use_http_post=False): 74 | """Retrieves a JSON object from a URL.""" 75 | if not params: 76 | params = {} 77 | 78 | request_url, response = _fetch_remote(service_url, params, use_http_post) 79 | if six.PY3: 80 | str_response = response.read().decode('utf-8') 81 | return (request_url, json.loads(str_response, parse_float=Decimal)) 82 | return (request_url, json.load(response, parse_float=Decimal)) 83 | 84 | def _fetch_remote_file(service_url, params=None, use_http_post=False): 85 | """Retrieves a file from a URL. 86 | 87 | Returns a tuple (mimetype, filename, data) 88 | """ 89 | if not params: 90 | params = {} 91 | 92 | request_url, response = _fetch_remote(service_url, params, use_http_post) 93 | dummy, params = cgi.parse_header( 94 | response.headers.get('Content-Disposition', '')) 95 | fn = params['filename'] 96 | 97 | return (response.headers.get('content-type'), 98 | fn, response.read(), response.geturl()) 99 | 100 | def geocode_location(location, sensor=False, api_key=None): 101 | """Converts a human-readable location to lat-lng. 102 | 103 | Returns a dict with lat and lng keys. 104 | 105 | keyword arguments: 106 | location -- A human-readable location, e.g 'London, England' 107 | sensor -- Boolean flag denoting if the location came from a device using 108 | its' location sensor (default False) 109 | api_key -- A valid Google Places API key. 110 | 111 | raises: 112 | GooglePlacesError -- if the geocoder fails to find a location. 113 | """ 114 | params = {'address': location, 'sensor': str(sensor).lower()} 115 | if api_key is not None: 116 | params['key'] = api_key 117 | url, geo_response = _fetch_remote_json( 118 | GooglePlaces.GEOCODE_API_URL, params) 119 | _validate_response(url, geo_response) 120 | if geo_response['status'] == GooglePlaces.RESPONSE_STATUS_ZERO_RESULTS: 121 | error_detail = ('Lat/Lng for location \'%s\' can\'t be determined.' % 122 | location) 123 | raise GooglePlacesError(error_detail) 124 | return geo_response['results'][0]['geometry']['location'] 125 | 126 | def _get_place_details(place_id, api_key, sensor=False, 127 | language=lang.ENGLISH): 128 | """Gets a detailed place response. 129 | 130 | keyword arguments: 131 | place_id -- The unique identifier for the required place. 132 | """ 133 | url, detail_response = _fetch_remote_json(GooglePlaces.DETAIL_API_URL, 134 | {'placeid': place_id, 135 | 'sensor': str(sensor).lower(), 136 | 'key': api_key, 137 | 'language': language}) 138 | _validate_response(url, detail_response) 139 | return detail_response['result'] 140 | 141 | def _get_place_photo(photoreference, api_key, maxheight=None, maxwidth=None, 142 | sensor=False): 143 | """Gets a place's photo by reference. 144 | See detailed documentation at https://developers.google.com/places/documentation/photos 145 | 146 | Arguments: 147 | photoreference -- The unique Google reference for the required photo. 148 | 149 | Keyword arguments: 150 | maxheight -- The maximum desired photo height in pixels 151 | maxwidth -- The maximum desired photo width in pixels 152 | 153 | You must specify one of this keyword arguments. Acceptable value is an 154 | integer between 1 and 1600. 155 | """ 156 | 157 | params = {'photoreference': photoreference, 158 | 'sensor': str(sensor).lower(), 159 | 'key': api_key} 160 | 161 | if maxheight: 162 | params['maxheight'] = maxheight 163 | 164 | if maxwidth: 165 | params['maxwidth'] = maxwidth 166 | 167 | return _fetch_remote_file(GooglePlaces.PHOTO_API_URL, params) 168 | 169 | def _validate_response(url, response): 170 | """Validates that the response from Google was successful.""" 171 | if response['status'] not in [GooglePlaces.RESPONSE_STATUS_OK, 172 | GooglePlaces.RESPONSE_STATUS_ZERO_RESULTS]: 173 | error_detail = ('Request to URL %s failed with response code: %s' % 174 | (url, response['status'])) 175 | raise GooglePlacesError(error_detail) 176 | 177 | 178 | class GooglePlacesError(Exception): 179 | pass 180 | 181 | 182 | class GooglePlacesAttributeError(AttributeError): 183 | """Exception thrown when a detailed property is unavailable. 184 | 185 | A search query from the places API returns only a summary of the Place. 186 | in order to get full details, a further API call must be made using 187 | the place_id. This exception will be thrown when a property made 188 | available by only the detailed API call is looked up against the summary 189 | object. 190 | 191 | An explicit call to get_details() must be made on the summary object in 192 | order to convert a summary object to a detailed object. 193 | """ 194 | # I could spend forever muling between this design decision and creating 195 | # a PlaceSummary object as well as a Place object. I'm leaning towards this 196 | # method in order to keep the API as simple as possible. 197 | pass 198 | 199 | 200 | class GooglePlaces(object): 201 | """A wrapper around the Google Places Query API.""" 202 | 203 | BASE_URL = 'https://maps.googleapis.com/maps/api' 204 | PLACE_URL = BASE_URL + '/place' 205 | GEOCODE_API_URL = BASE_URL + '/geocode/json?' 206 | RADAR_SEARCH_API_URL = PLACE_URL + '/radarsearch/json?' 207 | NEARBY_SEARCH_API_URL = PLACE_URL + '/nearbysearch/json?' 208 | TEXT_SEARCH_API_URL = PLACE_URL + '/textsearch/json?' 209 | AUTOCOMPLETE_API_URL = PLACE_URL + '/autocomplete/json?' 210 | DETAIL_API_URL = PLACE_URL + '/details/json?' 211 | CHECKIN_API_URL = PLACE_URL + '/check-in/json?sensor=%s&key=%s' 212 | ADD_API_URL = PLACE_URL + '/add/json?sensor=%s&key=%s' 213 | DELETE_API_URL = PLACE_URL + '/delete/json?sensor=%s&key=%s' 214 | PHOTO_API_URL = PLACE_URL + '/photo?' 215 | 216 | MAXIMUM_SEARCH_RADIUS = 50000 217 | RESPONSE_STATUS_OK = 'OK' 218 | RESPONSE_STATUS_ZERO_RESULTS = 'ZERO_RESULTS' 219 | 220 | def __init__(self, api_key): 221 | self._api_key = api_key 222 | self._sensor = False 223 | self._request_params = None 224 | 225 | def query(self, **kwargs): 226 | with warnings.catch_warnings(): 227 | warnings.simplefilter('always') 228 | warnings.warn('The query API is deprecated. Please use nearby_search.', 229 | DeprecationWarning, stacklevel=2) 230 | return self.nearby_search(**kwargs) 231 | 232 | def nearby_search(self, language=lang.ENGLISH, keyword=None, location=None, 233 | lat_lng=None, name=None, radius=3200, rankby=ranking.PROMINENCE, 234 | sensor=False, type=None, types=[], pagetoken=None): 235 | """Perform a nearby search using the Google Places API. 236 | 237 | One of either location, lat_lng or pagetoken are required, the rest of 238 | the keyword arguments are optional. 239 | 240 | keyword arguments: 241 | keyword -- A term to be matched against all available fields, including 242 | but not limited to name, type, and address (default None) 243 | location -- A human readable location, e.g 'London, England' 244 | (default None) 245 | language -- The language code, indicating in which language the 246 | results should be returned, if possible. (default lang.ENGLISH) 247 | lat_lng -- A dict containing the following keys: lat, lng 248 | (default None) 249 | name -- A term to be matched against the names of the Places. 250 | Results will be restricted to those containing the passed 251 | name value. (default None) 252 | radius -- The radius (in meters) around the location/lat_lng to 253 | restrict the search to. The maximum is 50000 meters. 254 | (default 3200) 255 | rankby -- Specifies the order in which results are listed : 256 | ranking.PROMINENCE (default) or ranking.DISTANCE 257 | (imply no radius argument). 258 | sensor -- Indicates whether or not the Place request came from a 259 | device using a location sensor (default False). 260 | type -- Optional type param used to indicate place category. 261 | types -- An optional list of types, restricting the results to 262 | Places (default []). If there is only one item the request 263 | will be send as type param. 264 | pagetoken-- Optional parameter to force the search result to return the next 265 | 20 results from a previously run search. Setting this parameter 266 | will execute a search with the same parameters used previously. 267 | (default None) 268 | """ 269 | if location is None and lat_lng is None and pagetoken is None: 270 | raise ValueError('One of location, lat_lng or pagetoken must be passed in.') 271 | if rankby == 'distance': 272 | # As per API docs rankby == distance: 273 | # One or more of keyword, name, or types is required. 274 | if keyword is None and types == [] and name is None: 275 | raise ValueError('When rankby = googleplaces.ranking.DISTANCE, ' + 276 | 'name, keyword or types kwargs ' + 277 | 'must be specified.') 278 | self._sensor = sensor 279 | radius = (radius if radius <= GooglePlaces.MAXIMUM_SEARCH_RADIUS 280 | else GooglePlaces.MAXIMUM_SEARCH_RADIUS) 281 | lat_lng_str = self._generate_lat_lng_string(lat_lng, location) 282 | self._request_params = {'location': lat_lng_str} 283 | if rankby == 'prominence': 284 | self._request_params['radius'] = radius 285 | else: 286 | self._request_params['rankby'] = rankby 287 | if type: 288 | self._request_params['type'] = type 289 | elif types: 290 | if len(types) == 1: 291 | self._request_params['type'] = types[0] 292 | elif len(types) > 1: 293 | self._request_params['types'] = '|'.join(types) 294 | if keyword is not None: 295 | self._request_params['keyword'] = keyword 296 | if name is not None: 297 | self._request_params['name'] = name 298 | if pagetoken is not None: 299 | self._request_params['pagetoken'] = pagetoken 300 | if language is not None: 301 | self._request_params['language'] = language 302 | self._add_required_param_keys() 303 | url, places_response = _fetch_remote_json( 304 | GooglePlaces.NEARBY_SEARCH_API_URL, self._request_params) 305 | _validate_response(url, places_response) 306 | return GooglePlacesSearchResult(self, places_response) 307 | 308 | def text_search(self, query=None, language=lang.ENGLISH, lat_lng=None, 309 | radius=3200, type=None, types=[], location=None, pagetoken=None): 310 | """Perform a text search using the Google Places API. 311 | 312 | Only the one of the query or pagetoken kwargs are required, the rest of the 313 | keyword arguments are optional. 314 | 315 | keyword arguments: 316 | lat_lng -- A dict containing the following keys: lat, lng 317 | (default None) 318 | location -- A human readable location, e.g 'London, England' 319 | (default None) 320 | pagetoken-- Optional parameter to force the search result to return the next 321 | 20 results from a previously run search. Setting this parameter 322 | will execute a search with the same parameters used previously. 323 | (default None) 324 | radius -- The radius (in meters) around the location/lat_lng to 325 | restrict the search to. The maximum is 50000 meters. 326 | (default 3200) 327 | query -- The text string on which to search, for example: 328 | "Restaurant in New York". 329 | type -- Optional type param used to indicate place category. 330 | types -- An optional list of types, restricting the results to 331 | Places (default []). If there is only one item the request 332 | will be send as type param. 333 | """ 334 | self._request_params = {'query': query} 335 | if lat_lng is not None or location is not None: 336 | lat_lng_str = self._generate_lat_lng_string(lat_lng, location) 337 | self._request_params['location'] = lat_lng_str 338 | self._request_params['radius'] = radius 339 | if type: 340 | self._request_params['type'] = type 341 | elif types: 342 | if len(types) == 1: 343 | self._request_params['type'] = types[0] 344 | elif len(types) > 1: 345 | self._request_params['types'] = '|'.join(types) 346 | if language is not None: 347 | self._request_params['language'] = language 348 | if pagetoken is not None: 349 | self._request_params['pagetoken'] = pagetoken 350 | self._add_required_param_keys() 351 | url, places_response = _fetch_remote_json( 352 | GooglePlaces.TEXT_SEARCH_API_URL, self._request_params) 353 | _validate_response(url, places_response) 354 | return GooglePlacesSearchResult(self, places_response) 355 | 356 | def autocomplete(self, input, lat_lng=None, location=None, radius=3200, 357 | language=lang.ENGLISH, types=None, components=[]): 358 | """ 359 | Perform an autocomplete search using the Google Places API. 360 | 361 | Only the input kwarg is required, the rest of the keyword arguments 362 | are optional. 363 | 364 | keyword arguments: 365 | input -- The text string on which to search, for example: 366 | "Hattie B's". 367 | lat_lng -- A dict containing the following keys: lat, lng 368 | (default None) 369 | location -- A human readable location, e.g 'London, England' 370 | (default None) 371 | radius -- The radius (in meters) around the location to which the 372 | search is to be restricted. The maximum is 50000 meters. 373 | (default 3200) 374 | language -- The language code, indicating in which language the 375 | results should be returned, if possible. (default lang.ENGLISH) 376 | types -- A type to search against. See `types.py` "autocomplete types" 377 | for complete list 378 | https://developers.google.com/places/documentation/autocomplete#place_types. 379 | components -- An optional grouping of places to which you would 380 | like to restrict your results. An array containing one or 381 | more tuples of: 382 | * country: matches a country name or a two letter ISO 3166-1 country code. 383 | eg: [('country','US')] 384 | """ 385 | self._request_params = {'input': input} 386 | if lat_lng is not None or location is not None: 387 | lat_lng_str = self._generate_lat_lng_string(lat_lng, location) 388 | self._request_params['location'] = lat_lng_str 389 | self._request_params['radius'] = radius 390 | if types: 391 | self._request_params['types'] = types 392 | if len(components) > 0: 393 | self._request_params['components'] = '|'.join(['{}:{}'.format( 394 | c[0],c[1]) for c in components]) 395 | if language is not None: 396 | self._request_params['language'] = language 397 | self._add_required_param_keys() 398 | url, places_response = _fetch_remote_json( 399 | GooglePlaces.AUTOCOMPLETE_API_URL, self._request_params) 400 | _validate_response(url, places_response) 401 | return GoogleAutocompleteSearchResult(self, places_response) 402 | 403 | def radar_search(self, sensor=False, keyword=None, name=None, 404 | language=lang.ENGLISH, lat_lng=None, opennow=False, 405 | radius=3200, type=None, types=[], location=None): 406 | """Perform a radar search using the Google Places API. 407 | 408 | One of lat_lng or location are required, the rest of the keyword 409 | arguments are optional. 410 | 411 | keyword arguments: 412 | keyword -- A term to be matched against all available fields, including 413 | but not limited to name, type, and address (default None) 414 | name -- A term to be matched against the names of Places. Results will 415 | be restricted to those containing the passed name value. 416 | language -- The language code, indicating in which language the 417 | results should be returned, if possible. (default lang.ENGLISH) 418 | lat_lng -- A dict containing the following keys: lat, lng 419 | (default None) 420 | location -- A human readable location, e.g 'London, England' 421 | (default None) 422 | radius -- The radius (in meters) around the location/lat_lng to 423 | restrict the search to. The maximum is 50000 meters. 424 | (default 3200) 425 | opennow -- Returns only those Places that are open for business at the time 426 | the query is sent. (default False) 427 | sensor -- Indicates whether or not the Place request came from a 428 | device using a location sensor (default False). 429 | type -- Optional type param used to indicate place category 430 | types -- An optional list of types, restricting the results to 431 | Places (default []). If there is only one item the request 432 | will be send as type param 433 | """ 434 | if keyword is None and name is None and len(types) is 0: 435 | raise ValueError('One of keyword, name or types must be supplied.') 436 | if location is None and lat_lng is None: 437 | raise ValueError('One of location or lat_lng must be passed in.') 438 | try: 439 | radius = int(radius) 440 | except: 441 | raise ValueError('radius must be passed supplied as an integer.') 442 | if sensor not in [True, False]: 443 | raise ValueError('sensor must be passed in as a boolean value.') 444 | 445 | self._request_params = {'radius': radius} 446 | self._sensor = sensor 447 | self._request_params['location'] = self._generate_lat_lng_string( 448 | lat_lng, location) 449 | if keyword is not None: 450 | self._request_params['keyword'] = keyword 451 | if name is not None: 452 | self._request_params['name'] = name 453 | if type: 454 | self._request_params['type'] = type 455 | elif types: 456 | if len(types) == 1: 457 | self._request_params['type'] = types[0] 458 | elif len(types) > 1: 459 | self._request_params['types'] = '|'.join(types) 460 | if language is not None: 461 | self._request_params['language'] = language 462 | if opennow is True: 463 | self._request_params['opennow'] = 'true' 464 | self._add_required_param_keys() 465 | url, places_response = _fetch_remote_json( 466 | GooglePlaces.RADAR_SEARCH_API_URL, self._request_params) 467 | _validate_response(url, places_response) 468 | return GooglePlacesSearchResult(self, places_response) 469 | 470 | def checkin(self, place_id, sensor=False): 471 | """Checks in a user to a place. 472 | 473 | keyword arguments: 474 | place_id -- The unique Google identifier for the relevant place. 475 | sensor -- Boolean flag denoting if the location came from a 476 | device using its location sensor (default False). 477 | """ 478 | data = {'placeid': place_id} 479 | url, checkin_response = _fetch_remote_json( 480 | GooglePlaces.CHECKIN_API_URL % (str(sensor).lower(), 481 | self.api_key), json.dumps(data), use_http_post=True) 482 | _validate_response(url, checkin_response) 483 | 484 | def get_place(self, place_id, sensor=False, language=lang.ENGLISH): 485 | """Gets a detailed place object. 486 | 487 | keyword arguments: 488 | place_id -- The unique Google identifier for the required place. 489 | sensor -- Boolean flag denoting if the location came from a 490 | device using its' location sensor (default False). 491 | language -- The language code, indicating in which language the 492 | results should be returned, if possible. (default lang.ENGLISH) 493 | """ 494 | place_details = _get_place_details(place_id, 495 | self.api_key, sensor, language=language) 496 | return Place(self, place_details) 497 | 498 | def add_place(self, **kwargs): 499 | """Adds a place to the Google Places database. 500 | 501 | On a successful request, this method will return a dict containing 502 | the the new Place's place_id and id in keys 'place_id' and 'id' 503 | respectively. 504 | 505 | keyword arguments: 506 | name -- The full text name of the Place. Limited to 255 507 | characters. 508 | lat_lng -- A dict containing the following keys: lat, lng. 509 | accuracy -- The accuracy of the location signal on which this request 510 | is based, expressed in meters. 511 | types -- The category in which this Place belongs. Only one type 512 | can currently be specified for a Place. A string or 513 | single element list may be passed in. 514 | language -- The language in which the Place's name is being reported. 515 | (defaults 'en'). 516 | sensor -- Boolean flag denoting if the location came from a device 517 | using its location sensor (default False). 518 | """ 519 | required_kwargs = {'name': [str], 'lat_lng': [dict], 520 | 'accuracy': [int], 'types': [str, list]} 521 | request_params = {} 522 | for key in required_kwargs: 523 | if key not in kwargs or kwargs[key] is None: 524 | raise ValueError('The %s argument is required.' % key) 525 | expected_types = required_kwargs[key] 526 | type_is_valid = False 527 | for expected_type in expected_types: 528 | if isinstance(kwargs[key], expected_type): 529 | type_is_valid = True 530 | break 531 | if not type_is_valid: 532 | raise ValueError('Invalid value for %s' % key) 533 | if key is not 'lat_lng': 534 | request_params[key] = kwargs[key] 535 | 536 | if len(kwargs['name']) > 255: 537 | raise ValueError('The place name must not exceed 255 characters ' + 538 | 'in length.') 539 | try: 540 | kwargs['lat_lng']['lat'] 541 | kwargs['lat_lng']['lng'] 542 | request_params['location'] = kwargs['lat_lng'] 543 | except KeyError: 544 | raise ValueError('Invalid keys for lat_lng.') 545 | 546 | request_params['language'] = (kwargs.get('language') 547 | if kwargs.get('language') is not None else 548 | lang.ENGLISH) 549 | 550 | sensor = (kwargs.get('sensor') 551 | if kwargs.get('sensor') is not None else 552 | False) 553 | 554 | # At some point Google might support multiple types, so this supports 555 | # strings and lists. 556 | if isinstance(kwargs['types'], str): 557 | request_params['types'] = [kwargs['types']] 558 | else: 559 | request_params['types'] = kwargs['types'] 560 | url, add_response = _fetch_remote_json( 561 | GooglePlaces.ADD_API_URL % (str(sensor).lower(), 562 | self.api_key), json.dumps(request_params), use_http_post=True) 563 | _validate_response(url, add_response) 564 | return {'place_id': add_response['place_id'], 565 | 'id': add_response['id']} 566 | 567 | def delete_place(self, place_id, sensor=False): 568 | """Deletes a place from the Google Places database. 569 | 570 | keyword arguments: 571 | place_id -- The textual identifier that uniquely identifies this 572 | Place, returned from a Place Search request. 573 | sensor -- Boolean flag denoting if the location came from a device 574 | using its location sensor (default False). 575 | """ 576 | 577 | request_params = {'place_id': place_id} 578 | url, delete_response = _fetch_remote_json( 579 | GooglePlaces.DELETE_API_URL % (str(sensor).lower(), 580 | self.api_key), json.dumps(request_params), use_http_post=True) 581 | _validate_response(url, delete_response) 582 | 583 | def _add_required_param_keys(self): 584 | self._request_params['key'] = self.api_key 585 | self._request_params['sensor'] = str(self.sensor).lower() 586 | 587 | def _generate_lat_lng_string(self, lat_lng, location): 588 | try: 589 | return '%(lat)s,%(lng)s' % (lat_lng if lat_lng is not None 590 | else geocode_location(location=location, api_key=self.api_key)) 591 | except GooglePlacesError as e: 592 | raise ValueError( 593 | 'lat_lng must be a dict with the keys, \'lat\' and \'lng\'. Cause: %s' % str(e)) 594 | 595 | @property 596 | def request_params(self): 597 | return self._request_params 598 | 599 | @property 600 | def api_key(self): 601 | return self._api_key 602 | 603 | @property 604 | def sensor(self): 605 | return self._sensor 606 | 607 | 608 | class GoogleAutocompleteSearchResult(object): 609 | """Wrapper around the Google Autocomplete API query JSON response.""" 610 | 611 | def __init__(self, query_instance, response): 612 | self._response = response 613 | self._predictions = [] 614 | for prediction in response['predictions']: 615 | self._predictions.append(Prediction(query_instance, prediction)) 616 | 617 | @property 618 | def raw_response(self): 619 | """Returns the raw JSON response returned by the Autocomplete API.""" 620 | return self._response 621 | 622 | @property 623 | def predictions(self): 624 | return self._predictions 625 | 626 | def __repr__(self): 627 | """Return a string representation stating number of predictions.""" 628 | return '<{} with {} prediction(s)>'.format( 629 | self.__class__.__name__, 630 | len(self.predictions) 631 | ) 632 | 633 | 634 | class Prediction(object): 635 | """ 636 | Represents a prediction from the results of a Google Places Autocomplete API query. 637 | """ 638 | def __init__(self, query_instance, prediction): 639 | self._query_instance = query_instance 640 | self._description = prediction['description'] 641 | self._id = prediction.get('id') 642 | self._matched_substrings = prediction['matched_substrings'] 643 | self._place_id = prediction['place_id'] 644 | self._reference = prediction['reference'] 645 | self._terms = prediction['terms'] 646 | self._types = prediction.get('types',[]) 647 | if prediction.get('_description') is None: 648 | self._place = None 649 | else: 650 | self._place = prediction 651 | 652 | @property 653 | def description(self): 654 | """ 655 | String representation of a Prediction location. Generally contains 656 | name, country, and elements contained in the terms property. 657 | """ 658 | return self._description 659 | 660 | @property 661 | def id(self): 662 | """ 663 | Returns the deprecated id property. 664 | 665 | This identifier may not be used to retrieve information about this 666 | place, but is guaranteed to be valid across sessions. It can be used 667 | to consolidate data about this Place, and to verify the identity of a 668 | Place across separate searches. 669 | 670 | This property is deprecated: 671 | https://developers.google.com/places/documentation/autocomplete#deprecation 672 | """ 673 | return self._id 674 | 675 | @property 676 | def matched_substrings(self): 677 | """ 678 | Returns the placement and offset of the matched strings for this search. 679 | 680 | A an array of dicts, each with the keys 'length' and 'offset', will be returned. 681 | """ 682 | return self._matched_substrings 683 | 684 | @property 685 | def place_id(self): 686 | """ 687 | Returns the unique stable identifier denoting this place. 688 | 689 | This identifier may be used to retrieve information about this 690 | place. 691 | 692 | This should be considered the primary identifier of a place. 693 | """ 694 | return self._place_id 695 | 696 | @property 697 | def reference(self): 698 | """ 699 | Returns the deprecated reference property. 700 | 701 | The token can be used to retrieve additional information about this 702 | place when invoking the getPlace method on an GooglePlaces instance. 703 | 704 | You can store this token and use it at any time in future to refresh 705 | cached data about this Place, but the same token is not guaranteed to 706 | be returned for any given Place across different searches. 707 | 708 | This property is deprecated: 709 | https://developers.google.com/places/documentation/autocomplete#deprecation 710 | """ 711 | return self._reference 712 | 713 | @property 714 | def terms(self): 715 | """ 716 | A list of terms which build up the description string 717 | 718 | A an array of dicts, each with the keys `offset` and `value`, will be returned. 719 | """ 720 | return self._terms 721 | 722 | @property 723 | def types(self): 724 | """ 725 | Returns a list of feature types describing the given result. 726 | """ 727 | if self._types == '' and self.details != None and 'types' in self.details: 728 | self._icon = self.details['types'] 729 | return self._types 730 | 731 | # The following properties require a further API call in order to be 732 | # available. 733 | @property 734 | def place(self): 735 | """ 736 | Returns the JSON response from Google Places Detail search API. 737 | """ 738 | self._validate_status() 739 | return self._place 740 | 741 | def get_details(self, language=None): 742 | """ 743 | Retrieves full information on the place matching the place_id. 744 | 745 | Stores the response in the `place` property. 746 | """ 747 | if self._place is None: 748 | if language is None: 749 | try: 750 | language = self._query_instance._request_params['language'] 751 | except KeyError: 752 | language = lang.ENGLISH 753 | place = _get_place_details( 754 | self.place_id, self._query_instance.api_key, 755 | self._query_instance.sensor, language=language) 756 | self._place = Place(self._query_instance, place) 757 | 758 | def _validate_status(self): 759 | """ 760 | Indicates specific properties are only available after a details call. 761 | """ 762 | if self._place is None: 763 | error_detail = ('The attribute requested is only available after ' + 764 | 'an explicit call to get_details() is made.') 765 | raise GooglePlacesAttributeError(error_detail) 766 | 767 | def __repr__(self): 768 | """ Return a string representation with description. """ 769 | return '<{} description="{}">'.format(self.__class__.__name__, self.description) 770 | 771 | 772 | class GooglePlacesSearchResult(object): 773 | """ 774 | Wrapper around the Google Places API query JSON response. 775 | """ 776 | 777 | def __init__(self, query_instance, response): 778 | self._response = response 779 | self._places = [] 780 | for place in response['results']: 781 | self._places.append(Place(query_instance, place)) 782 | self._html_attributions = response.get('html_attributions', []) 783 | self._next_page_token = response.get('next_page_token', []) 784 | 785 | @property 786 | def raw_response(self): 787 | """ 788 | Returns the raw JSON response returned by the Places API. 789 | """ 790 | return self._response 791 | 792 | @property 793 | def places(self): 794 | return self._places 795 | 796 | @property 797 | def html_attributions(self): 798 | """Returns the HTML attributions for the specified response. 799 | 800 | Any returned HTML attributions MUST be displayed as-is, in accordance 801 | with the requirements as found in the documentation. Please see the 802 | module comments for links to the relevant url. 803 | """ 804 | return self._html_attributions 805 | 806 | @property 807 | def next_page_token(self): 808 | """Returns the next page token(next_page_token).""" 809 | return self._next_page_token 810 | 811 | @property 812 | def has_attributions(self): 813 | """Returns a flag denoting if the response had any html attributions.""" 814 | return len(self.html_attributions) > 0 815 | 816 | @property 817 | def has_next_page_token(self): 818 | """Returns a flag denoting if the response had a next page token.""" 819 | return len(self.next_page_token) > 0 820 | 821 | def __repr__(self): 822 | """ Return a string representation stating the number of results.""" 823 | return '<{} with {} result(s)>'.format(self.__class__.__name__, len(self.places)) 824 | 825 | 826 | class Place(object): 827 | """ 828 | Represents a place from the results of a Google Places API query. 829 | """ 830 | def __init__(self, query_instance, place_data): 831 | self._query_instance = query_instance 832 | self._place_id = place_data['place_id'] 833 | self._id = place_data.get('id', '') 834 | self._reference = place_data.get('reference', '') 835 | self._name = place_data.get('name','') 836 | self._vicinity = place_data.get('vicinity', '') 837 | self._geo_location = place_data['geometry']['location'] 838 | self._rating = place_data.get('rating','') 839 | self._types = place_data.get('types','') 840 | self._icon = place_data.get('icon','') 841 | if place_data.get('address_components') is None: 842 | self._details = None 843 | else: 844 | self._details = place_data 845 | 846 | @property 847 | def reference(self): 848 | """DEPRECATED AS OF JUNE 24, 2014. May stop being returned on June 24, 849 | 2015. Reference: https://developers.google.com/places/documentation/search 850 | 851 | Returns contains a unique token for the place. 852 | 853 | The token can be used to retrieve additional information about this 854 | place when invoking the getPlace method on an GooglePlaces instance. 855 | 856 | You can store this token and use it at any time in future to refresh 857 | cached data about this Place, but the same token is not guaranteed to 858 | be returned for any given Place across different searches.""" 859 | warnings.warn('The "reference" feature is deprecated and may' 860 | 'stop working any time after June 24, 2015.', 861 | FutureWarning) 862 | return self._reference 863 | 864 | @property 865 | def id(self): 866 | """DEPRECATED AS OF JUNE 24, 2014. May stop being returned on June 24, 867 | 2015. Reference: https://developers.google.com/places/documentation/search 868 | 869 | Returns the unique stable identifier denoting this place. 870 | 871 | This identifier may not be used to retrieve information about this 872 | place, but is guaranteed to be valid across sessions. It can be used 873 | to consolidate data about this Place, and to verify the identity of a 874 | Place across separate searches. 875 | """ 876 | warnings.warn('The "id" feature is deprecated and may' 877 | 'stop working any time after June 24, 2015.', 878 | FutureWarning) 879 | return self._id 880 | 881 | @property 882 | def place_id(self): 883 | """Returns the unique stable identifier denoting this place. 884 | 885 | This identifier may be used to retrieve information about this 886 | place. 887 | 888 | This should be considered the primary identifier of a place. 889 | """ 890 | return self._place_id 891 | 892 | @property 893 | def icon(self): 894 | """Returns the URL of a recommended icon for display.""" 895 | if self._icon == '' and self.details != None and 'icon' in self.details: 896 | self._icon = self.details['icon'] 897 | return self._icon 898 | 899 | @property 900 | def types(self): 901 | """Returns a list of feature types describing the given result.""" 902 | if self._types == '' and self.details != None and 'types' in self.details: 903 | self._icon = self.details['types'] 904 | return self._types 905 | 906 | @property 907 | def geo_location(self): 908 | """Returns the lat lng co-ordinates of the place. 909 | 910 | A dict with the keys 'lat' and 'lng' will be returned. 911 | """ 912 | return self._geo_location 913 | 914 | @property 915 | def name(self): 916 | """Returns the human-readable name of the place.""" 917 | if self._name == '' and self.details != None and 'name' in self.details: 918 | self._name = self.details['name'] 919 | return self._name 920 | 921 | @property 922 | def vicinity(self): 923 | """Returns a feature name of a nearby location. 924 | 925 | Often this feature refers to a street or neighborhood within the given 926 | results. 927 | """ 928 | if self._vicinity == '' and self.details != None and 'vicinity' in self.details: 929 | self._vicinity = self.details['vicinity'] 930 | return self._vicinity 931 | 932 | @property 933 | def rating(self): 934 | """Returns the Place's rating, from 0.0 to 5.0, based on user reviews. 935 | 936 | This method will return None for places that have no rating. 937 | """ 938 | if self._rating == '' and self.details != None and 'rating' in self.details: 939 | self._rating = self.details['rating'] 940 | return self._rating 941 | 942 | # The following properties require a further API call in order to be 943 | # available. 944 | @property 945 | def details(self): 946 | """Returns the JSON response from Google Places Detail search API.""" 947 | self._validate_status() 948 | return self._details 949 | 950 | @property 951 | def formatted_address(self): 952 | """Returns a string containing the human-readable address of this place. 953 | 954 | Often this address is equivalent to the "postal address," which 955 | sometimes differs from country to country. (Note that some countries, 956 | such as the United Kingdom, do not allow distribution of complete postal 957 | addresses due to licensing restrictions.) 958 | """ 959 | self._validate_status() 960 | return self.details.get('formatted_address') 961 | 962 | @property 963 | def local_phone_number(self): 964 | """Returns the Place's phone number in its local format.""" 965 | self._validate_status() 966 | return self.details.get('formatted_phone_number') 967 | 968 | @property 969 | def international_phone_number(self): 970 | self._validate_status() 971 | return self.details.get('international_phone_number') 972 | 973 | @property 974 | def website(self): 975 | """Returns the authoritative website for this Place.""" 976 | self._validate_status() 977 | return self.details.get('website') 978 | 979 | @property 980 | def url(self): 981 | """Contains the official Google Place Page URL of this establishment. 982 | 983 | Applications must link to or embed the Google Place page on any screen 984 | that shows detailed results about this Place to the user. 985 | """ 986 | self._validate_status() 987 | return self.details.get('url') 988 | 989 | @property 990 | def html_attributions(self): 991 | """Returns the HTML attributions for the specified response. 992 | 993 | Any returned HTML attributions MUST be displayed as-is, in accordance 994 | with the requirements as found in the documentation. Please see the 995 | module comments for links to the relevant url. 996 | """ 997 | self._validate_status() 998 | return self.details.get('html_attributions', []) 999 | 1000 | @property 1001 | def has_attributions(self): 1002 | """Returns a flag denoting if the response had any html attributions.""" 1003 | return (False if self._details is None else 1004 | len(self.html_attributions) > 0) 1005 | 1006 | def checkin(self): 1007 | """Checks in an anonymous user in.""" 1008 | self._query_instance.checkin(self.place_id, 1009 | self._query_instance.sensor) 1010 | 1011 | def get_details(self, language=None): 1012 | """Retrieves full information on the place matching the place_id. 1013 | 1014 | Further attributes will be made available on the instance once this 1015 | method has been invoked. 1016 | 1017 | keyword arguments: 1018 | language -- The language code, indicating in which language the 1019 | results should be returned, if possible. This value defaults 1020 | to the language that was used to generate the 1021 | GooglePlacesSearchResult instance. 1022 | """ 1023 | if self._details is None: 1024 | if language is None: 1025 | try: 1026 | language = self._query_instance._request_params['language'] 1027 | except KeyError: 1028 | language = lang.ENGLISH 1029 | self._details = _get_place_details( 1030 | self.place_id, self._query_instance.api_key, 1031 | self._query_instance.sensor, language=language) 1032 | 1033 | @cached_property 1034 | def photos(self): 1035 | self.get_details() 1036 | return [Photo(self._query_instance, i) 1037 | for i in self.details.get('photos', [])] 1038 | 1039 | def _validate_status(self): 1040 | if self._details is None: 1041 | error_detail = ('The attribute requested is only available after ' + 1042 | 'an explicit call to get_details() is made.') 1043 | raise GooglePlacesAttributeError(error_detail) 1044 | 1045 | def __repr__(self): 1046 | """ Return a string representation including the name, lat, and lng. """ 1047 | return '<{} name="{}", lat={}, lng={}>'.format( 1048 | self.__class__.__name__, 1049 | self.name, 1050 | self.geo_location['lat'], 1051 | self.geo_location['lng'] 1052 | ) 1053 | 1054 | 1055 | class Photo(object): 1056 | def __init__(self, query_instance, attrs): 1057 | self._query_instance = query_instance 1058 | self.orig_height = attrs.get('height') 1059 | self.orig_width = attrs.get('width') 1060 | self.html_attributions = attrs.get('html_attributions') 1061 | self.photo_reference = attrs.get('photo_reference') 1062 | 1063 | def get(self, maxheight=None, maxwidth=None, sensor=False): 1064 | """Fetch photo from API.""" 1065 | if not maxheight and not maxwidth: 1066 | raise GooglePlacesError('You must specify maxheight or maxwidth!') 1067 | 1068 | result = _get_place_photo(self.photo_reference, 1069 | self._query_instance.api_key, 1070 | maxheight=maxheight, maxwidth=maxwidth, 1071 | sensor=sensor) 1072 | 1073 | self.mimetype, self.filename, self.data, self.url = result 1074 | --------------------------------------------------------------------------------