From a3518a4a600ddaac10c45bedf517e127192512ee Mon Sep 17 00:00:00 2001
From: Taufiq Muhammadi <taufiqm@outlook.com>
Date: Wed, 1 Feb 2017 01:00:58 +0700
Subject: [PATCH] Google Places Search Lookup (#1143)

---
 README.md                                     |  7 ++-
 lib/geocoder/lookup.rb                        |  1 +
 lib/geocoder/lookups/google.rb                |  6 +--
 lib/geocoder/lookups/google_places_search.rb  | 33 ++++++++++++
 lib/geocoder/results/google_places_search.rb  | 52 +++++++++++++++++++
 ...google_places_search_madison_square_garden | 42 +++++++++++++++
 test/fixtures/google_places_search_no_results |  5 ++
 .../unit/lookups/google_places_search_test.rb | 50 ++++++++++++++++++
 test/unit/result_test.rb                      |  2 +-
 9 files changed, 193 insertions(+), 5 deletions(-)
 create mode 100644 lib/geocoder/lookups/google_places_search.rb
 create mode 100644 lib/geocoder/results/google_places_search.rb
 create mode 100644 test/fixtures/google_places_search_madison_square_garden
 create mode 100644 test/fixtures/google_places_search_no_results
 create mode 100644 test/unit/lookups/google_places_search_test.rb

diff --git a/README.md b/README.md
index 6a9bae9c..a5b07baa 100644
--- a/README.md
+++ b/README.md
@@ -422,7 +422,7 @@ Similar to `:google`, with the following differences:
 
 #### Google Places Details (`:google_places_details`)
 
-The [Google Places Details API](https://developers.google.com/places/documentation/details) is not, strictly speaking, a geocoding service. It accepts a Google `place_id` and returns address information, ratings and reviews. A `place_id` can be obtained from 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)`.
+The [Google Places Details API](https://developers.google.com/places/documentation/details) is not, strictly speaking, a geocoding service. It accepts a Google `place_id` and returns address information, ratings and reviews. A `place_id` can be obtained from the Google Places Search lookup or 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/
@@ -434,6 +434,11 @@ The [Google Places Details API](https://developers.google.com/places/documentati
 * **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."
 
+#### Google Places Search (`:google_places_search`)
+The [Google Places Search API](https://developers.google.com/places/web-service/search) is the geocoding service of Google Places API.
+It only returns limited location data, however it also returns corresponding `place_id` for a given location. You can use Google Place Details if you need more detailed information using obtained `place_id`. You should use this service instead of regular Google Geocoding API if your queries are ambiguos or incomplete addresses. For more comparison between this and regular Google Geocoding API, see https://maps-apis.googleblog.com/2016/11/address-geocoding-in-google-maps-apis.html
+* See points in the previous Google Places Details section.
+
 #### Bing (`:bing`)
 
 * **API key**: required (set `Geocoder.configure(:lookup => :bing, :api_key => key)`)
diff --git a/lib/geocoder/lookup.rb b/lib/geocoder/lookup.rb
index b5e9275a..04a6ef44 100644
--- a/lib/geocoder/lookup.rb
+++ b/lib/geocoder/lookup.rb
@@ -29,6 +29,7 @@ module Geocoder
         :google,
         :google_premier,
         :google_places_details,
+        :google_places_search,
         :bing,
         :geocoder_ca,
         :geocoder_us,
diff --git a/lib/geocoder/lookups/google.rb b/lib/geocoder/lookups/google.rb
index 4c815706..4b1b3cbb 100644
--- a/lib/geocoder/lookups/google.rb
+++ b/lib/geocoder/lookups/google.rb
@@ -50,13 +50,13 @@ module Geocoder::Lookup
         return doc['results']
       when "OVER_QUERY_LIMIT"
         raise_error(Geocoder::OverQueryLimitError) ||
-          Geocoder.log(:warn, "Google Geocoding API error: over query limit.")
+          Geocoder.log(:warn, "#{name} API error: over query limit.")
       when "REQUEST_DENIED"
         raise_error(Geocoder::RequestDenied) ||
-          Geocoder.log(:warn, "Google Geocoding API error: request denied.")
+          Geocoder.log(:warn, "#{name} API error: request denied.")
       when "INVALID_REQUEST"
         raise_error(Geocoder::InvalidRequest) ||
-          Geocoder.log(:warn, "Google Geocoding API error: invalid request.")
+          Geocoder.log(:warn, "#{name} API error: invalid request.")
       end
       return []
     end
diff --git a/lib/geocoder/lookups/google_places_search.rb b/lib/geocoder/lookups/google_places_search.rb
new file mode 100644
index 00000000..c8761d9a
--- /dev/null
+++ b/lib/geocoder/lookups/google_places_search.rb
@@ -0,0 +1,33 @@
+require "geocoder/lookups/google"
+require "geocoder/results/google_places_search"
+
+module Geocoder
+  module Lookup
+    class GooglePlacesSearch < Google
+      def name
+        "Google Places Search"
+      end
+
+      def required_api_key_parts
+        ["key"]
+      end
+
+      def supported_protocols
+        [:https]
+      end
+
+      def query_url(query)
+        "#{protocol}://maps.googleapis.com/maps/api/place/textsearch/json?#{url_query_string(query)}"
+      end
+
+      private
+
+      def query_url_google_params(query)
+        {
+          query: query.text,
+          language: query.language || configuration.language
+        }
+      end
+    end
+  end
+end
diff --git a/lib/geocoder/results/google_places_search.rb b/lib/geocoder/results/google_places_search.rb
new file mode 100644
index 00000000..77e658bd
--- /dev/null
+++ b/lib/geocoder/results/google_places_search.rb
@@ -0,0 +1,52 @@
+require "geocoder/results/google"
+
+module Geocoder
+  module Result
+    class GooglePlacesSearch < Google
+
+      def types
+        @data["types"] || []
+      end
+
+      def rating
+        @data["rating"]
+      end
+
+      def photos
+        @data["photos"]
+      end
+
+      def city
+        ""
+      end
+
+      def state
+        ""
+      end
+
+      def state_code
+        ""
+      end
+
+      def province
+        ""
+      end
+
+      def province_code
+        ""
+      end
+
+      def postal_code
+        ""
+      end
+
+      def country
+        ""
+      end
+
+      def country_code
+        ""
+      end
+    end
+  end
+end
diff --git a/test/fixtures/google_places_search_madison_square_garden b/test/fixtures/google_places_search_madison_square_garden
new file mode 100644
index 00000000..e037ac3e
--- /dev/null
+++ b/test/fixtures/google_places_search_madison_square_garden
@@ -0,0 +1,42 @@
+{
+   "html_attributions" : [],
+   "results" : [
+      {
+         "formatted_address" : "4 Pennsylvania Plaza, New York, NY 10001, United States",
+         "geometry" : {
+            "location" : {
+               "lat" : 40.75050450000001,
+               "lng" : -73.9934387
+            },
+            "viewport" : {
+               "northeast" : {
+                  "lat" : 40.7523981,
+                  "lng" : -73.98975269999997
+               },
+               "southwest" : {
+                  "lat" : 40.7485721,
+                  "lng" : -73.9963951
+               }
+            }
+         },
+         "icon" : "https://maps.gstatic.com/mapfiles/place_api/icons/generic_business-71.png",
+         "id" : "55e3174d410b31da010030a7dfc0c9819027445a",
+         "name" : "Madison Square Garden",
+         "photos" : [
+            {
+               "height" : 3904,
+               "html_attributions" : [
+                  "\u003ca href=\"https://maps.google.com/maps/contrib/117796018192827964147/photos\"\u003eAntonio Vera\u003c/a\u003e"
+               ],
+               "photo_reference" : "CoQBdwAAAJSRc-oTDFp3lkRwkyDN85h49Inw9YRC8U2sPUDeV0FSKzQNMxKfswp27o4Eh8gt1U6ZLPYES3RCP-2zJPswcVMtQOE9NxxM9Yg7SJllFYcC1GR_yjCJw6OGTP3_OCaL-gFI1_r54-04veyCc-UNtzjF836se6yQlSGd643zBy3_EhCYKtc3tY024iyO-6xPZUzzGhRcgC0A-itlFq-U2qmYbPde4gJU7Q",
+               "width" : 3152
+            }
+         ],
+         "place_id" : "ChIJhRwB-yFawokR5Phil-QQ3zM",
+         "rating" : 4.5,
+         "reference" : "CmRRAAAAqb-Y-BFyZh54ipS97aLZCfQYVVI_l87_7HQxJAXMx3rI29XzscexUUiwt7kLbr4YeDaggMQ78coK-V_yGztNvhsGbq2OsrdR-BmVpecrNGbiE9fNDPsPGvdxKcB3SPbAEhDTgnyzjLY1p7IPh2M4L9KnGhS7ZZMAtvVKPnjoCnaWP9IzdzRC4w",
+         "types" : [ "stadium", "point_of_interest", "establishment" ]
+      }
+   ],
+   "status" : "OK"
+}
diff --git a/test/fixtures/google_places_search_no_results b/test/fixtures/google_places_search_no_results
new file mode 100644
index 00000000..6a1ce6ac
--- /dev/null
+++ b/test/fixtures/google_places_search_no_results
@@ -0,0 +1,5 @@
+{
+   "html_attributions" : [],
+   "results" : [],
+   "status" : "ZERO_RESULTS"
+}
diff --git a/test/unit/lookups/google_places_search_test.rb b/test/unit/lookups/google_places_search_test.rb
new file mode 100644
index 00000000..908c70ad
--- /dev/null
+++ b/test/unit/lookups/google_places_search_test.rb
@@ -0,0 +1,50 @@
+# encoding: utf-8
+require 'test_helper'
+
+class GooglePlacesSearchTest < GeocoderTestCase
+
+  def setup
+    Geocoder.configure(lookup: :google_places_search)
+    set_api_key!(:google_places_search)
+  end
+
+  def test_google_places_search_result_contains_place_id
+    assert_equal "ChIJhRwB-yFawokR5Phil-QQ3zM", madison_square_garden.place_id
+  end
+
+  def test_google_places_search_result_contains_latitude
+    assert_equal madison_square_garden.latitude, 40.75050450000001
+  end
+
+  def test_google_places_search_result_contains_longitude
+    assert_equal madison_square_garden.longitude, -73.9934387
+  end
+
+  def test_google_places_search_result_contains_rating
+    assert_equal 4.5, madison_square_garden.rating
+  end
+
+  def test_google_places_search_result_contains_types
+    assert_equal madison_square_garden.types, %w(stadium point_of_interest establishment)
+  end
+
+  def test_google_places_search_query_url_contains_language
+    url = lookup.query_url(Geocoder::Query.new("some-address", language: "de"))
+    assert_match(/language=de/, url)
+  end
+
+  def test_google_places_search_query_url_always_uses_https
+    url = lookup.query_url(Geocoder::Query.new("some-address"))
+    assert_match(%r(^https://), url)
+  end
+
+  private
+
+  def lookup
+    Geocoder::Lookup::GooglePlacesSearch.new
+  end
+
+  def madison_square_garden
+    Geocoder.search("Madison Square Garden").first
+  end
+end
diff --git a/test/unit/result_test.rb b/test/unit/result_test.rb
index f33e1efa..ecb31377 100644
--- a/test/unit/result_test.rb
+++ b/test/unit/result_test.rb
@@ -26,7 +26,7 @@ class ResultTest < GeocoderTestCase
   def test_result_accepts_reverse_coords_in_reasonable_range_for_madison_square_garden
     Geocoder::Lookup.street_services.each do |l|
       next unless File.exist?(File.join("test", "fixtures", "#{l.to_s}_madison_square_garden"))
-      next if [:bing, :esri, :geocoder_ca, :geocoder_us, :geoportail_lu].include? l # Reverse fixture does not match forward
+      next if [:bing, :esri, :geocoder_ca, :google_places_search, :geocoder_us, :geoportail_lu].include? l # Reverse fixture does not match forward
       Geocoder.configure(:lookup => l)
       set_api_key!(l)
       result = Geocoder.search([40.750354, -73.993371]).first
-- 
GitLab