Skip to content
Snippets Groups Projects
Commit b05f1f73 authored by Alex Reisner's avatar Alex Reisner
Browse files

Merge pull request #699 from blacklane/google_places

Integrate the Google Places Details API
parents 0c08fb63 4202ac35
No related branches found
No related tags found
No related merge requests found
...@@ -396,6 +396,27 @@ The following is a comparison of the supported geocoding APIs. The "Limitations" ...@@ -396,6 +396,27 @@ The following is a comparison of the supported geocoding APIs. The "Limitations"
* **Limitations**: "You must not use or display the Content without a corresponding Google map, unless you are explicitly permitted to do so in the Maps APIs Documentation, or through written permission from Google." "You must not pre-fetch, cache, or store any Content, except that you may store: (i) limited amounts of Content for the purpose of improving the performance of your Maps API Implementation..." * **Limitations**: "You must not use or display the Content without a corresponding Google map, unless you are explicitly permitted to do so in the Maps APIs Documentation, or through written permission from Google." "You must not pre-fetch, cache, or store any Content, except that you may store: (i) limited amounts of Content for the purpose of improving the performance of your Maps API Implementation..."
* **Notes**: To use Google Premier set `Geocoder.configure(:lookup => :google_premier, :api_key => [key, client, channel])`. * **Notes**: To use Google Premier set `Geocoder.configure(:lookup => :google_premier, :api_key => [key, client, channel])`.
#### Google Places Details (`:google_places_details`)
The [Google Places Details API](https://developers.google.com/places/documentation/details) extends results provided by the
[Google Places Autocomplete API](https://developers.google.com/places/documentation/autocomplete) with address information,
coordinates, ratings and reviews. It can be used as a replacement to the Google Geocoding API, because some information
provided by the Autocomplete API is not geocodable over the Geocoding API - like hotels, restaurants, bars.
The Google Places Details search requires a valid `place_id` - instead of the usual search query. This `place_id` can be
obtained over the Google Places Autocomplete API and should be passed to Geocoder as the first search argument:
`Geocoder.search("ChIJhRwB-yFawokR5Phil-QQ3zM", :lookup => :google_places_details)`.
* **API key**: required
* **Key signup**: https://code.google.com/apis/console/
* **Quota**: 1,000 request/day, 100,000 after credit card authentication
* **Region**: world
* **SSL support**: yes
* **Languages**: ar, eu, bg, bn, ca, cs, da, de, el, en, en-AU, en-GB, es, eu, fa, fi, fil, fr, gl, gu, hi, hr, hu, id, it, iw, ja, kn, ko, lt, lv, ml, mr, nl, no, pl, pt, pt-BR, pt-PT, ro, ru, sk, sl, sr, sv, tl, ta, te, th, tr, uk, vi, zh-CN, zh-TW (see http://spreadsheets.google.com/pub?key=p9pdwsai2hDMsLkXsoM05KQ&gid=1)
* **Documentation**: https://developers.google.com/places/documentation/details
* **Terms of Service**: https://developers.google.com/places/policies
* **Limitations**: "If your application displays Places API data on a page or view that does not also display a Google Map, you must show a "Powered by Google" logo with that data."
#### Yahoo BOSS (`:yahoo`) #### Yahoo BOSS (`:yahoo`)
* **API key**: requires OAuth consumer key and secret (set `Geocoder.configure(:api_key => [key, secret])`) * **API key**: requires OAuth consumer key and secret (set `Geocoder.configure(:api_key => [key, secret])`)
......
...@@ -25,6 +25,7 @@ module Geocoder ...@@ -25,6 +25,7 @@ module Geocoder
:esri, :esri,
:google, :google,
:google_premier, :google_premier,
:google_places_details,
:yahoo, :yahoo,
:bing, :bing,
:geocoder_ca, :geocoder_ca,
......
require "geocoder/lookups/google"
require "geocoder/results/google_places_details"
module Geocoder
module Lookup
class GooglePlacesDetails < Google
def name
"Google Places Details"
end
def required_api_key_parts
["key"]
end
def use_ssl?
true
end
def query_url(query)
"#{protocol}://maps.googleapis.com/maps/api/place/details/json?#{url_query_string(query)}"
end
private
def results(query)
return [] unless doc = fetch_data(query)
case doc["status"]
when "OK"
return [doc["result"]]
when "OVER_QUERY_LIMIT"
raise_error(Geocoder::OverQueryLimitError) || warn("Google Places Details API error: over query limit.")
when "REQUEST_DENIED"
raise_error(Geocoder::RequestDenied) || warn("Google Places Details API error: request denied.")
when "INVALID_REQUEST"
raise_error(Geocoder::InvalidRequest) || warn("Google Places Details API error: invalid request.")
end
[]
end
def query_url_google_params(query)
{
placeid: query.text,
language: query.language || configuration.language
}
end
end
end
end
require "geocoder/results/google"
module Geocoder
module Result
class GooglePlacesDetails < Google
def place_id
@data["place_id"]
end
def types
@data["types"] || []
end
def reviews
@data["reviews"] || []
end
def rating
@data["rating"]
end
def rating_count
@data["user_ratings_total"]
end
def phone_number
@data["international_phone_number"]
end
def website
@data["website"]
end
end
end
end
{
"html_attributions" : [],
"status" : "INVALID_REQUEST"
}
{
"html_attributions" : [],
"result" : {
"address_components" : [
{
"long_name" : "4",
"short_name" : "4",
"types" : [ "street_number" ]
},
{
"long_name" : "Pennsylvania Plaza",
"short_name" : "Pennsylvania Plaza",
"types" : [ "route" ]
},
{
"long_name" : "Chelsea",
"short_name" : "Chelsea",
"types" : [ "neighborhood", "political" ]
},
{
"long_name" : "Manhattan",
"short_name" : "Manhattan",
"types" : [ "sublocality_level_1", "sublocality", "political" ]
},
{
"long_name" : "New York",
"short_name" : "New York",
"types" : [ "locality", "political" ]
},
{
"long_name" : "New York County",
"short_name" : "New York County",
"types" : [ "administrative_area_level_2", "political" ]
},
{
"long_name" : "NY",
"short_name" : "NY",
"types" : [ "administrative_area_level_1", "political" ]
},
{
"long_name" : "United States",
"short_name" : "US",
"types" : [ "country", "political" ]
},
{
"long_name" : "10001",
"short_name" : "10001",
"types" : [ "postal_code" ]
}
],
"adr_address" : "\u003cspan class=\"street-address\"\u003e4 Pennsylvania Plaza\u003c/span\u003e, \u003cspan class=\"locality\"\u003eNew York\u003c/span\u003e, \u003cspan class=\"region\"\u003eNY\u003c/span\u003e \u003cspan class=\"postal-code\"\u003e10001\u003c/span\u003e, \u003cspan class=\"country-name\"\u003eUnited States\u003c/span\u003e",
"formatted_address" : "4 Pennsylvania Plaza, New York, NY, United States",
"formatted_phone_number" : "(212) 465-6741",
"geometry" : {
"location" : {
"lat" : 40.750504,
"lng" : -73.993439
}
},
"icon" : "http://maps.gstatic.com/mapfiles/place_api/icons/stadium-71.png",
"id" : "55e3174d410b31da010030a7dfc0c9819027445a",
"international_phone_number" : "+1 212-465-6741",
"name" : "Madison Square Garden",
"photos" : [
{
"height" : 267,
"html_attributions" : [ "Someone" ],
"photo_reference" : "CnRoAAAAB1lz_vOkA1Ffaf7vJ1xcjzVsII-873Z_f8_v3EyW4XbEvlR3VkW_HvNfwF6AwDA0U-ont7fIUqEMyDcovCTWN8RSYN3ibEhqgJPvXxBjeYVi0cc-t-3KfkB_LpV7chPAqhOsdMG56kyzTKIo4lISHxIQqru7QXV6xU11jLaLpi4FNRoUhutg9aq027NghW1d-o9GGE8N3yM",
"width" : 400
},
{
"height" : 732,
"html_attributions" : [ "Someone else" ],
"photo_reference" : "CoQBfwAAADbYj554hM1BgVbBOs6rDjO9UtQ63ecU1P8tI3PfaHame3MB4ipgcNRlv9N2iUa-tXoMq9iXAaDStWgCJ3f48WIuIRbz-Xv-HzSJ0hMCrFiZMRs7kLgaBO7eDJwioTySWcoyAwojretq4mZKF_AcRSUhkqOokBhOstmshWWpSAyyEhAdpL2yp42xDNq2YRiFrx4DGhRZIdt7mochSwZcgSdjESea27Qy9Q",
"width" : 929
}
],
"place_id" : "ChIJhRwB-yFawokR5Phil-QQ3zM",
"rating" : 4.4,
"reference" : "CnRuAAAArSB-mFxXRjm0ERZIC4c_1gbXwxqmyxUrxws_PQUrz9Y3xZstEwm7_8dLw7zM8hV3vWkPF4RkkZQQ_X01ikDALx2VgV60YwiSX7k3TXxkWnzyFcX0fnHCQLerlYttk_usL7UALQQgMeuf25_eFx3SnhIQxN95Ek3bQVuLVM16yb99ehoU7djcL3cjllohLZ6oJ6X3Tzb9MJQ",
"reviews" : [
{
"aspects" : [
{
"rating" : 5,
"type" : "overall"
}
],
"author_name" : "John Smith",
"author_url" : "https://plus.google.com/john.smith",
"language" : "en",
"rating" : 5,
"text" : "It's nice.",
"time" : 1407266916
},
{
"aspects" : [
{
"rating" : 2,
"type" : "overall"
}
],
"author_name" : "Jane Smith",
"author_url" : "https://plus.google.com/jane.smith",
"language" : "en",
"rating" : 2,
"text" : "Not so nice.",
"time" : 1398779079
}
],
"scope" : "GOOGLE",
"types" : [ "stadium", "establishment" ],
"url" : "https://plus.google.com/112180896421099179463/about?hl=en-US",
"user_ratings_total" : 382,
"utc_offset" : -240,
"vicinity" : "4 Pennsylvania Plaza, New York",
"website" : "http://www.thegarden.com/"
},
"status" : "OK"
}
{
"html_attributions" : [],
"result": {}
}
{
"html_attributions" : [],
"result" : {
"address_components" : [
{
"long_name" : "25",
"short_name" : "25",
"types" : [ "street_number" ]
},
{
"long_name" : "Oranienstraße",
"short_name" : "Oranienstraße",
"types" : [ "route" ]
},
{
"long_name" : "Friedrichshain-Kreuzberg",
"short_name" : "Friedrichshain-Kreuzberg",
"types" : [ "sublocality_level_1", "sublocality", "political" ]
},
{
"long_name" : "Berlin",
"short_name" : "Berlin",
"types" : [ "locality", "political" ]
},
{
"long_name" : "Berlin",
"short_name" : "Berlin",
"types" : [ "administrative_area_level_1", "political" ]
},
{
"long_name" : "Germany",
"short_name" : "DE",
"types" : [ "country", "political" ]
},
{
"long_name" : "10999",
"short_name" : "10999",
"types" : [ "postal_code" ]
}
],
"adr_address" : "\u003cspan class=\"street-address\"\u003eOranienstraße 25\u003c/span\u003e, \u003cspan class=\"postal-code\"\u003e10999\u003c/span\u003e \u003cspan class=\"locality\"\u003eBerlin\u003c/span\u003e, \u003cspan class=\"country-name\"\u003eGermany\u003c/span\u003e",
"formatted_address" : "Oranienstraße 25, 10999 Berlin, Germany",
"geometry" : {
"location" : {
"lat" : 52.5010652,
"lng" : 13.4206563
}
},
"icon" : "http://maps.gstatic.com/mapfiles/place_api/icons/geocode-71.png",
"id" : "b49e917abd41c247425645a6ac3e5a6756ad80f8",
"name" : "Oranienstraße 25",
"place_id" : "ChIJQ8-HeTROqEcRGdCTErYy5D0",
"reference" : "CpQBjAAAAKrbnaAglhQLI6KPs3EbRp1lVh5UkB4xLEyzOmvvGmtpmwD2EAnlokKcx1VU5PvvB7moRGw6lHTlMTScpGL3GTCC_WM2pzDxgeaAtoB-SR4YQ7PRhHkT2eqxACbaP_70Z9Wyb2J31tG66xBrYASAuBzXgEXYaFpo8dhRBY4xMTfexvMziw6rHs01SxLhCFh-uBIQBQcGVz70_HtaG_g6ZZ5omhoUdIDDSVApRiVoIh61NiCzStYa-x0",
"scope" : "GOOGLE",
"types" : [ "street_address" ],
"url" : "https://maps.google.com/maps/place?q=Oranienstra%C3%9Fe+25,+10999+Berlin,+Germany&ftid=0x47a84e347987cf43:0x3de432b61293d019",
"vicinity" : "Friedrichshain-Kreuzberg"
},
"status" : "OK"
}
{
"html_attributions" : [],
"result" : {
"address_components" : [
{
"long_name" : "6",
"short_name" : "6",
"types" : [ "street_number" ]
},
{
"long_name" : "Graniczna",
"short_name" : "Graniczna",
"types" : [ "route" ]
},
{
"long_name" : "Gdynia",
"short_name" : "Gdynia",
"types" : [ "locality", "political" ]
},
{
"long_name" : "Gdynia",
"short_name" : "Gdynia",
"types" : [ "administrative_area_level_3", "political" ]
},
{
"long_name" : "Gdynia",
"short_name" : "Gdynia",
"types" : [ "administrative_area_level_2", "political" ]
},
{
"long_name" : "Pomeranian Voivodeship",
"short_name" : "Pomeranian Voivodeship",
"types" : [ "administrative_area_level_1", "political" ]
},
{
"long_name" : "Poland",
"short_name" : "PL",
"types" : [ "country", "political" ]
},
{
"long_name" : "81-626",
"short_name" : "81-626",
"types" : [ "postal_code" ]
}
],
"adr_address" : "\u003cspan class=\"street-address\"\u003eGraniczna 6\u003c/span\u003e, \u003cspan class=\"postal-code\"\u003e81-626\u003c/span\u003e \u003cspan class=\"locality\"\u003eGdynia\u003c/span\u003e, \u003cspan class=\"country-name\"\u003ePoland\u003c/span\u003e",
"formatted_address" : "Graniczna 6, Gdynia, Poland",
"formatted_phone_number" : "795 085 050",
"geometry" : {
"location" : {
"lat" : 54.493608,
"lng" : 18.508983
}
},
"id" : "aed47c412bce35bb385c86edef64f8f7860b1cf8",
"international_phone_number" : "+48 795 085 050",
"name" : "Taxi Gdynia",
"place_id" : "ChIJgT2H0tig_UYRD9iM2NSOo4Y",
"reference" : "CnRlAAAA6bzgakKCXzwjtLBSMLvj0fvv3OSwGp2WsA54VwQELEupFzqtFuUmxMyMSNYt745EukJR0Ui6Ih9WX3AdL--HXjQprE8xHSb_6qQh2eauKWFIoHzIvbMrkjDIcUPxPDMdJc2XkwpOr_EUimZplCy-gBIQb_UHtRVaswAMjdHHtDqzURoU3tGbILe-zhN3oISBAvc3AyoyznE",
"scope" : "GOOGLE",
"url" : "https://plus.google.com/100174651414421384172/about?hl=en-US",
"utc_offset" : 120,
"vicinity" : "Graniczna 6, Gdynia"
},
"status" : "OK"
}
...@@ -122,6 +122,13 @@ module Geocoder ...@@ -122,6 +122,13 @@ module Geocoder
end end
end end
class GooglePlacesDetails
private
def fixture_prefix
"google_places_details"
end
end
class Dstk class Dstk
private private
def fixture_prefix def fixture_prefix
......
# encoding: utf-8
$: << File.join(File.dirname(__FILE__), "..", "..")
require 'test_helper'
class GooglePlacesDetailsTest < GeocoderTestCase
def setup
Geocoder.configure(lookup: :google_places_details)
set_api_key!(:google_places_details)
end
def test_google_places_details_result_components
assert_equal "Manhattan", madison_square_garden.address_components_of_type(:sublocality).first["long_name"]
end
def test_google_places_details_result_components_contains_route
assert_equal "Pennsylvania Plaza", madison_square_garden.address_components_of_type(:route).first["long_name"]
end
def test_google_places_details_result_components_contains_street_number
assert_equal "4", madison_square_garden.address_components_of_type(:street_number).first["long_name"]
end
def test_google_places_details_street_address_returns_formatted_street_address
assert_equal "4 Pennsylvania Plaza", madison_square_garden.street_address
end
def test_google_places_details_result_contains_place_id
assert_equal "ChIJhRwB-yFawokR5Phil-QQ3zM", madison_square_garden.place_id
end
def test_google_places_details_result_contains_latitude
assert_equal madison_square_garden.latitude, 40.750504
end
def test_google_places_details_result_contains_longitude
assert_equal madison_square_garden.longitude, -73.993439
end
def test_google_places_details_result_contains_rating
assert_equal 4.4, madison_square_garden.rating
end
def test_google_places_details_result_contains_rating_count
assert_equal 382, madison_square_garden.rating_count
end
def test_google_places_details_result_contains_reviews
reviews = madison_square_garden.reviews
assert_equal 2, reviews.size
assert_equal "John Smith", reviews.first["author_name"]
assert_equal 5, reviews.first["rating"]
assert_equal "It's nice.", reviews.first["text"]
assert_equal "en", reviews.first["language"]
end
def test_google_places_details_result_contains_types
assert_equal madison_square_garden.types, %w(stadium establishment)
end
def test_google_places_details_result_contains_website
assert_equal madison_square_garden.website, "http://www.thegarden.com/"
end
def test_google_places_details_result_contains_phone_number
assert_equal madison_square_garden.phone_number, "+1 212-465-6741"
end
def test_google_places_details_query_url_contains_placeid
url = lookup.query_url(Geocoder::Query.new("some-place-id"))
assert_match(/placeid=some-place-id/, url)
end
def test_google_places_details_query_url_contains_language
url = lookup.query_url(Geocoder::Query.new("some-place-id", language: "de"))
assert_match(/language=de/, url)
end
def test_google_places_details_query_url_always_uses_https
url = lookup.query_url(Geocoder::Query.new("some-place-id"))
assert_match(%r(^https://), url)
end
def test_google_places_details_result_with_no_reviews_shows_empty_reviews
assert_equal no_reviews_result.reviews, []
end
def test_google_places_details_result_with_no_types_shows_empty_types
assert_equal no_types_result.types, []
end
def test_google_places_details_result_with_invalid_place_id_empty
assert_equal Geocoder.search("invalid request"), []
end
def test_raises_exception_on_google_places_details_invalid_request
Geocoder.configure(always_raise: [Geocoder::InvalidRequest])
assert_raises Geocoder::InvalidRequest do
Geocoder.search("invalid request")
end
end
private
def lookup
Geocoder::Lookup::GooglePlacesDetails.new
end
def madison_square_garden
Geocoder.search("ChIJhRwB-yFawokR5Phil-QQ3zM").first
end
def no_reviews_result
Geocoder.search("no reviews").first
end
def no_types_result
Geocoder.search("no types").first
end
end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment