diff --git a/Gemfile b/Gemfile index f75921f123d..8fb53421ffd 100644 --- a/Gemfile +++ b/Gemfile @@ -111,6 +111,9 @@ gem "secure_headers" # Load canonical-rails to generate canonical URLs gem "canonical-rails" +# Load plus_codes for plus code searching +gem "plus_codes" + # Used to generate logstash friendly log files gem "logstasher" diff --git a/Gemfile.lock b/Gemfile.lock index 2aba9c21bd4..a9043a6cfdc 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -246,6 +246,7 @@ GEM parser (2.5.3.0) ast (~> 2.4.0) pg (0.21.0) + plus_codes (0.2.1) poltergeist (1.18.1) capybara (>= 2.1, < 4) cliver (~> 0.3.1) @@ -427,6 +428,7 @@ DEPENDENCIES openstreetmap-deadlock_retry (>= 1.3.0) paperclip (~> 5.2) pg (~> 0.18) + plus_codes poltergeist psych puma (~> 3.7) diff --git a/app/controllers/geocoder_controller.rb b/app/controllers/geocoder_controller.rb index b9cf8d096f3..e451047b2ef 100644 --- a/app/controllers/geocoder_controller.rb +++ b/app/controllers/geocoder_controller.rb @@ -2,6 +2,7 @@ class GeocoderController < ApplicationController require "cgi" require "uri" require "rexml/document" + require "plus_codes/open_location_code" before_action :authorize_web before_action :set_locale @@ -17,7 +18,9 @@ def search @sources.push "osm_nominatim_reverse" @sources.push "geonames_reverse" if defined?(GEONAMES_USERNAME) elsif @params[:query] - if @params[:query] =~ /^\d{5}(-\d{4})?$/ + if olc.full?(@params[:query]) + @sources.push "plus_code" + elsif @params[:query] =~ /^\d{5}(-\d{4})?$/ @sources.push "osm_nominatim" elsif @params[:query] =~ /^(GIR 0AA|[A-PR-UWYZ]([0-9]{1,2}|([A-HK-Y][0-9]|[A-HK-Y][0-9]([0-9]|[ABEHMNPRV-Y]))|[0-9][A-HJKS-UW])\s*[0-9][ABD-HJLNP-UW-Z]{2})$/i @sources.push "osm_nominatim" @@ -81,6 +84,20 @@ def search_latlon end end + def search_plus_code + code_area = olc.decode(params[:query]) + + @results = [{ :lat => code_area.latitude_center, + :lon => code_area.longitude_center, + :min_lat => code_area.south_latitude, + :max_lat => code_area.north_latitude, + :min_lon => code_area.west_longitude, + :max_lon => code_area.east_longitude, + :name => params[:query] }] + + render :action => "results" + end + def search_ca_postcode # get query parameters query = params[:query] @@ -350,4 +367,8 @@ def dms_to_decdeg(captures) end { :lat => lat, :lon => lon } end + + def olc + @olc ||= PlusCodes::OpenLocationCode.new + end end diff --git a/config/locales/en.yml b/config/locales/en.yml index e87e8f8ee2a..18083041cb7 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -334,6 +334,7 @@ en: search: title: latlon: 'Results from Internal' + plus_code: 'Results from Plus Codes' ca_postcode: 'Results from Geocoder.CA' osm_nominatim: 'Results from OpenStreetMap Nominatim' geonames: 'Results from GeoNames' diff --git a/config/routes.rb b/config/routes.rb index b245e8fec0c..3a9b48dfe5c 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -248,6 +248,7 @@ # geocoder get "/search" => "geocoder#search" get "/geocoder/search_latlon" => "geocoder#search_latlon" + get "/geocoder/search_plus_code" => "geocoder#search_plus_code" get "/geocoder/search_ca_postcode" => "geocoder#search_ca_postcode" get "/geocoder/search_osm_nominatim" => "geocoder#search_osm_nominatim" get "/geocoder/search_geonames" => "geocoder#search_geonames" diff --git a/test/controllers/geocoder_controller_test.rb b/test/controllers/geocoder_controller_test.rb index 1a143518241..aed25366780 100644 --- a/test/controllers/geocoder_controller_test.rb +++ b/test/controllers/geocoder_controller_test.rb @@ -12,6 +12,10 @@ def test_routes { :path => "/geocoder/search_latlon", :method => :get }, { :controller => "geocoder", :action => "search_latlon" } ) + assert_routing( + { :path => "/geocoder/search_plus_code", :method => :get }, + { :controller => "geocoder", :action => "search_plus_code" } + ) assert_routing( { :path => "/geocoder/search_ca_postcode", :method => :get }, { :controller => "geocoder", :action => "search_ca_postcode" } @@ -233,6 +237,23 @@ def test_identify_latlon_sw_dms end end + ## + # Test identification of plus codes + def test_identify_plus_code + [ + "6PH57VP3+PQ", + "6GCRPR6C+24", + "8FWC2345+G6", + "8FWC2345+G6G", + "8fwc2345+", + "8FWCX400+" + ].each do |code| + post :search, :params => { :query => code } + assert_response :success + assert_equal %w[plus_code], assigns(:sources) + end + end + ## # Test identification of US zipcodes def test_identify_us_postcode @@ -317,6 +338,64 @@ def test_search_latlon_digits results_check_error "Latitude or longitude are out of range" end + ## + # Test the plus code search + def test_search_plus_code + get :search_plus_code, :xhr => true, + :params => { :query => "6PH57VP3+PQ", :zoom => 10, + :minlon => -0.559, :minlat => 51.217, + :maxlon => 0.836, :maxlat => 51.766 } + results_check "name" => "6PH57VP3+PQ", + "lat" => 1.2868125, "lon" => 103.85443749999999, + "min-lat" => 1.2867499999999998, "max-lat" => 1.2868749999999998, + "min-lon" => 103.85437499999999, "max-lon" => 103.85449999999999 + + get :search_plus_code, :xhr => true, + :params => { :query => "6GCRPR6C+24", :zoom => 10, + :minlon => -0.559, :minlat => 51.217, + :maxlon => 0.836, :maxlat => 51.766 } + results_check "name" => "6GCRPR6C+24", + "lat" => -1.2899375, "lon" => 36.8203125, + "min-lat" => -1.29, "max-lat" => -1.289875, + "min-lon" => 36.82025, "max-lon" => 36.820375 + + get :search_plus_code, :xhr => true, + :params => { :query => "8FWC2345+G6", :zoom => 10, + :minlon => -0.559, :minlat => 51.217, + :maxlon => 0.836, :maxlat => 51.766 } + results_check "name" => "8FWC2345+G6", + "lat" => 48.0063125, "lon" => 8.058062500000002, + "min-lat" => 48.00625, "max-lat" => 48.006375, + "min-lon" => 8.058000000000002, "max-lon" => 8.058125000000002 + + get :search_plus_code, :xhr => true, + :params => { :query => "8FWC2345+G6G", :zoom => 10, + :minlon => -0.559, :minlat => 51.217, + :maxlon => 0.836, :maxlat => 51.766 } + results_check "name" => "8FWC2345+G6G", + "lat" => 48.0063125, "lon" => 8.058078125000002, + "min-lat" => 48.0063, "max-lat" => 48.006325000000004, + "min-lon" => 8.058062500000002, "max-lon" => 8.058093750000001 + + get :search_plus_code, :xhr => true, + :params => { :query => "8fwc2345+", :zoom => 10, + :minlon => -0.559, :minlat => 51.217, + :maxlon => 0.836, :maxlat => 51.766 } + results_check "name" => "8fwc2345+", + "lat" => 48.00625, "lon" => 8.058750000000002, + "min-lat" => 48.005, "max-lat" => 48.0075, + "min-lon" => 8.057500000000001, "max-lon" => 8.06 + + get :search_plus_code, :xhr => true, + :params => { :query => "8FWCX400+", :zoom => 10, + :minlon => -0.559, :minlat => 51.217, + :maxlon => 0.836, :maxlat => 51.766 } + results_check "name" => "8FWCX400+", + "lat" => 48.975, "lon" => 8.125, + "min-lat" => 48.95, "max-lat" => 49.0, + "min-lon" => 8.1, "max-lon" => 8.15 + end + ## # Test the Canadian postcode search def test_search_ca_postcode