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