diff --git a/README.md b/README.md index 92cfbc28b94fc9699424ab8deafb6248edad3dc5..bf14c8f521627d9140dc4483f89016bcb5708a29 100644 --- a/README.md +++ b/README.md @@ -578,6 +578,31 @@ The [Google Places Details API](https://developers.google.com/places/documentati * **Limitations**: ? * **Notes**: You can specify which projection you want to use by setting, for example: `Geocoder.configure(:esri => {:outSR => 102100})`. +#### Mapzen (`:mapzen`) + +* **About**: Mapzen is the primary author of pelias and offers Pelias-as-a-service in free and paid versions https://mapzen.com/pelias. +* **API key**: required +* **Quota**: 6/sec, up to 30k per day, paid plan info at https://mapzen.com/documentation/search/api-keys-rate-limits/#rate-limits +* **Region**: World +* **SSL support**: yes +* **Languages**: en +* **Documentation**: https://mapzen.com/documentation/search/search/ +* **Terms of Service**: http://mapzen.com/terms +* **Limitations**: See terms + +#### Pelias (`:pelias`) + +* **About**: Pelias is a modular open-source geocoder using ElasticSearch for fast geocoding https://github.com/pelias/pelias. +* **API key**: required +* **Quota**: None, self-hosted service. +* **Region**: World +* **SSL support**: yes +* **Languages**: en +* **Documentation**: https://mapzen.com/documentation/search/search/ +* **Terms of Service**: http://mapzen.com/terms +* **Limitations**: See terms +* **Notes**: Configure your self-hosted pelias with the `endpoint` option: `Geocoder.configure(:lookup => :pelias, :api_key => 'your_api_key', :pelias => {:endpoint => 'self.hosted/pelias'})`. Defaults to `localhost`. + #### Data Science Toolkit (`:dstk`) Data Science Toolkit provides an API whose reponse format is like Google's but which can be set up as a privately hosted service. diff --git a/Rakefile b/Rakefile index 4651cf1a5715f6d3bd3ade845dd54f94cf78500f..c7d1063269103798fe90403ad75bf51af73d6a06 100644 --- a/Rakefile +++ b/Rakefile @@ -60,13 +60,11 @@ Rake::TestTask.new(:test) do |test| Rake::Task['db:reset'].invoke if ACCEPTED_DB_VALUES.include? ENV['DB'] test.libs << 'lib' << 'test' test.pattern = 'test/unit/**/*_test.rb' - test.verbose = true end Rake::TestTask.new(:integration) do |test| test.libs << 'lib' << 'test' test.pattern = 'test/integration/*_test.rb' - test.verbose = true end task :default => [:test] diff --git a/lib/geocoder/lookup.rb b/lib/geocoder/lookup.rb index 962f5a79c74bfe6f05db49aeeb9207ad9168109f..35ebc15e36ee31d969f6dba4106997656c9f740a 100644 --- a/lib/geocoder/lookup.rb +++ b/lib/geocoder/lookup.rb @@ -36,8 +36,10 @@ module Geocoder :nominatim, :mapbox, :mapquest, + :mapzen, :opencagedata, :ovi, + :pelias, :here, :baidu, :geocodio, diff --git a/lib/geocoder/lookups/mapzen.rb b/lib/geocoder/lookups/mapzen.rb new file mode 100644 index 0000000000000000000000000000000000000000..0bb70d3095cb4429ca0c3b46bf4737eaf9adcee6 --- /dev/null +++ b/lib/geocoder/lookups/mapzen.rb @@ -0,0 +1,15 @@ +require 'geocoder/lookups/pelias' +require 'geocoder/results/mapzen' + +# https://mapzen.com/documentation/search/search/ for more information +module Geocoder::Lookup + class Mapzen < Pelias + def name + 'Mapzen' + end + + def endpoint + configuration[:endpoint] || 'search.mapzen.com' + end + end +end diff --git a/lib/geocoder/lookups/pelias.rb b/lib/geocoder/lookups/pelias.rb new file mode 100644 index 0000000000000000000000000000000000000000..31c89696881a85a679e5f96b2a41b8221a1affa6 --- /dev/null +++ b/lib/geocoder/lookups/pelias.rb @@ -0,0 +1,56 @@ +require 'geocoder/lookups/base' +require 'geocoder/results/pelias' + +module Geocoder::Lookup + class Pelias < Base + def name + 'Pelias' + end + + def endpoint + configuration[:endpoint] || 'localhost' + end + + def query_url(query) + query_type = query.reverse_geocode? ? 'reverse' : 'search' + "#{protocol}://#{endpoint}/v1/#{query_type}?" + url_query_string(query) + end + + def required_api_key_parts + ['search-XXXX'] + end + + private + + def query_url_params(query) + { + api_key: configuration.api_key, + text: query.text, + size: 1 + }.merge(super(query)) + end + + def results(query) + return [] unless doc = fetch_data(query) + + # not all responses include a meta + if doc['meta'] + error = doc.fetch('results', {}).fetch('error', {}) + message = error.fetch('type', 'Unknown Error') + ': ' + error.fetch('message', 'No message') + log_message = 'Pelias Geocoding API error - ' + message + case doc['meta']['status_code'] + when '200' + # nothing to see here + when '403' + raise_error(Geocoder::RequestDenied, message) || Geocoder.log(:warn, log_message) + when '429' + raise_error(Geocoder::OverQueryLimitError, message) || Geocoder.log(:warn, log_message) + else + raise_error(Geocoder::Error, message) || Geocoder.log(:warn, log_message) + end + end + + doc['features'] || [] + end + end +end diff --git a/lib/geocoder/results/mapzen.rb b/lib/geocoder/results/mapzen.rb new file mode 100644 index 0000000000000000000000000000000000000000..125f4da860a2e4b8ec05a8773f6508b50e1fa3f8 --- /dev/null +++ b/lib/geocoder/results/mapzen.rb @@ -0,0 +1,5 @@ +require 'geocoder/results/pelias' + +module Geocoder::Result + class Mapzen < Pelias; end +end diff --git a/lib/geocoder/results/pelias.rb b/lib/geocoder/results/pelias.rb new file mode 100644 index 0000000000000000000000000000000000000000..f5311b046010af81be4b55539a76a4d9ff7993e4 --- /dev/null +++ b/lib/geocoder/results/pelias.rb @@ -0,0 +1,58 @@ +require 'geocoder/results/base' + +module Geocoder::Result + class Pelias < Base + def address(format = :full) + properties['label'] + end + + def city + locality + end + + def coordinates + geometry['coordinates'].reverse + end + + def country_code + properties['country_a'] + end + + def postal_code + properties['postalcode'].to_s + end + + def province + state + end + + def state + properties['region'] + end + + def state_code + properties['region_a'] + end + + def self.response_attributes + %w[county confidence country gid id layer localadmin locality neighborhood] + end + + response_attributes.each do |a| + define_method a do + properties[a] + end + end + + private + + def geometry + @data.fetch('geometry', {}) + end + + def properties + @data.fetch('properties', {}) + end + end +end + diff --git a/test/fixtures/pelias_madison_square_garden b/test/fixtures/pelias_madison_square_garden new file mode 100644 index 0000000000000000000000000000000000000000..261d0fb2037af0073d5e82a8a28fed7013d4dd08 --- /dev/null +++ b/test/fixtures/pelias_madison_square_garden @@ -0,0 +1,326 @@ +{ + "bbox": [ + -85.693709, + 39.942189, + -73.9897, + 40.75066 + ], + "features": [ + { + "geometry": { + "coordinates": [ + -73.99347, + 40.75066 + ], + "type": "Point" + }, + "properties": { + "label": "Madison Square Garden Center, Manhattan, NY", + "confidence": 0.896, + "neighbourhood": "Garment District", + "locality": "New York", + "localadmin": "Manhattan", + "county": "New York County", + "region_a": "NY", + "region": "New York", + "country": "United States", + "country_a": "USA", + "name": "Madison Square Garden Center", + "source": "gn", + "layer": "venue", + "gid": "gn:venue:5125640", + "id": "5125640" + }, + "type": "Feature" + }, + { + "geometry": { + "coordinates": [ + -73.993392, + 40.750497 + ], + "type": "Point" + }, + "properties": { + "label": "Madison Square Garden, Manhattan, NY", + "confidence": 0.896, + "neighbourhood": "Garment District", + "locality": "New York", + "localadmin": "Manhattan", + "county": "New York County", + "region_a": "NY", + "region": "New York", + "country": "United States", + "country_a": "USA", + "street": "Pennsylvania Plaza", + "housenumber": "46", + "name": "Madison Square Garden", + "source": "osm", + "layer": "venue", + "gid": "osm:venue:138141251", + "id": "138141251" + }, + "type": "Feature" + }, + { + "geometry": { + "coordinates": [ + -73.9897, + 40.7478 + ], + "type": "Point" + }, + "properties": { + "label": "Hampton Inn Madison Square Garden, Manhattan, NY", + "confidence": 0.885, + "neighbourhood": "Koreatown", + "locality": "New York", + "localadmin": "Manhattan", + "county": "New York County", + "region_a": "NY", + "region": "New York", + "country": "United States", + "country_a": "USA", + "name": "Hampton Inn Madison Square Garden", + "source": "gn", + "layer": "venue", + "gid": "gn:venue:6466291", + "id": "6466291" + }, + "type": "Feature" + }, + { + "geometry": { + "coordinates": [ + -73.99541, + 40.74882 + ], + "type": "Point" + }, + "properties": { + "label": "Holiday Inn Express NYC Madison Square Garden, Manhattan, NY", + "confidence": 0.673, + "neighbourhood": "Garment District", + "locality": "New York", + "localadmin": "Manhattan", + "county": "New York County", + "region_a": "NY", + "region": "New York", + "country": "United States", + "country_a": "USA", + "name": "Holiday Inn Express NYC Madison Square Garden", + "source": "gn", + "layer": "venue", + "gid": "gn:venue:7645793", + "id": "7645793" + }, + "type": "Feature" + }, + { + "geometry": { + "coordinates": [ + -73.99, + 40.748 + ], + "type": "Point" + }, + "properties": { + "label": "Hampton Inn Madison Square Garden Area Hotel, Manhattan, NY", + "confidence": 0.673, + "neighbourhood": "Garment District", + "locality": "New York", + "localadmin": "Manhattan", + "county": "New York County", + "region_a": "NY", + "region": "New York", + "country": "United States", + "country_a": "USA", + "name": "Hampton Inn Madison Square Garden Area Hotel", + "source": "gn", + "layer": "venue", + "gid": "gn:venue:6499758", + "id": "6499758" + }, + "type": "Feature" + }, + { + "geometry": { + "coordinates": [ + -75.181686, + 39.942425 + ], + "type": "Point" + }, + "properties": { + "label": "2315 Madison Square, Philadelphia, PA", + "confidence": 0.5, + "neighbourhood": "Schuylkill", + "locality": "Philadelphia", + "localadmin": "Philadelphia", + "county": "Philadelphia County", + "region_a": "PA", + "region": "Pennsylvania", + "country": "United States", + "country_a": "USA", + "postalcode": "19146", + "street": "Madison Square", + "housenumber": "2315", + "name": "2315 Madison Square", + "source": "oa", + "layer": "address", + "gid": "oa:address:0eef7b448f064f90869b1b4610fb2ccd", + "id": "0eef7b448f064f90869b1b4610fb2ccd" + }, + "type": "Feature" + }, + { + "geometry": { + "coordinates": [ + -75.183109, + 39.942643 + ], + "type": "Point" + }, + "properties": { + "label": "2419 Madison Square, Philadelphia, PA", + "confidence": 0.5, + "neighbourhood": "Devil's Pocket", + "locality": "Philadelphia", + "localadmin": "Philadelphia", + "county": "Philadelphia County", + "region_a": "PA", + "region": "Pennsylvania", + "country": "United States", + "country_a": "USA", + "postalcode": "19146", + "street": "Madison Square", + "housenumber": "2419", + "name": "2419 Madison Square", + "source": "oa", + "layer": "address", + "gid": "oa:address:e7c287fb14f0459f8a1b4d938f57feb9", + "id": "e7c287fb14f0459f8a1b4d938f57feb9" + }, + "type": "Feature" + }, + { + "geometry": { + "coordinates": [ + -75.181266, + 39.942212 + ], + "type": "Point" + }, + "properties": { + "label": "2304 Madison Square, Philadelphia, PA", + "confidence": 0.5, + "neighbourhood": "Schuylkill", + "locality": "Philadelphia", + "localadmin": "Philadelphia", + "county": "Philadelphia County", + "region_a": "PA", + "region": "Pennsylvania", + "country": "United States", + "country_a": "USA", + "postalcode": "19146", + "street": "Madison Square", + "housenumber": "2304", + "name": "2304 Madison Square", + "source": "oa", + "layer": "address", + "gid": "oa:address:fd605b0d1b3a422f9a9b8df9f136a80b", + "id": "fd605b0d1b3a422f9a9b8df9f136a80b" + }, + "type": "Feature" + }, + { + "geometry": { + "coordinates": [ + -85.693709, + 40.131756 + ], + "type": "Point" + }, + "properties": { + "label": "2200 Madison Square, Anderson, IN", + "confidence": 0.5, + "neighbourhood": "North Anderson", + "locality": "Anderson", + "localadmin": "Anderson", + "county": "Madison County", + "region_a": "IN", + "region": "Indiana", + "country": "United States", + "country_a": "USA", + "postalcode": "46011", + "street": "Madison Square", + "housenumber": "2200", + "name": "2200 Madison Square", + "source": "oa", + "layer": "address", + "gid": "oa:address:acd7f29cc4bc44f69c719cf5cdad39e8", + "id": "acd7f29cc4bc44f69c719cf5cdad39e8" + }, + "type": "Feature" + }, + { + "geometry": { + "coordinates": [ + -75.181085, + 39.942189 + ], + "type": "Point" + }, + "properties": { + "label": "2300 Madison Square, Philadelphia, PA", + "confidence": 0.5, + "neighbourhood": "Schuylkill", + "locality": "Philadelphia", + "localadmin": "Philadelphia", + "county": "Philadelphia County", + "region_a": "PA", + "region": "Pennsylvania", + "country": "United States", + "country_a": "USA", + "postalcode": "19146", + "street": "Madison Square", + "housenumber": "2300", + "name": "2300 Madison Square", + "source": "oa", + "layer": "address", + "gid": "oa:address:c1d20ac81ac54f35af822359dc70171e", + "id": "c1d20ac81ac54f35af822359dc70171e" + }, + "type": "Feature" + } + ], + "type": "FeatureCollection", + "geocoding": { + "timestamp": 1457996697682, + "engine": { + "version": "1.0", + "author": "Mapzen", + "name": "Pelias" + }, + "query": { + "querySize": 20, + "private": false, + "size": 10, + "parsed_text": { + "admin_parts": "New York, NY 10001, United States\"", + "regions": [ + "Garden", + "New York", + "10001", + "United States\"" + ], + "state": "NY", + "street": "\"Madison Square", + "name": "\"Madison Square Garden" + }, + "text": "\"Madison Square Garden, New York, NY 10001, United States\"" + }, + "attribution": "https:\/\/search.mapzen.com\/v1\/attribution", + "version": "0.1" + } +} diff --git a/test/fixtures/pelias_no_results b/test/fixtures/pelias_no_results new file mode 100644 index 0000000000000000000000000000000000000000..7d3c12e72c0c0fed7b811739c436b79064b132fc --- /dev/null +++ b/test/fixtures/pelias_no_results @@ -0,0 +1,38 @@ +{ + "bbox": [ + -85.693709, + 39.942189, + -73.9897, + 40.75066 + ], + "features": [], + "type": "FeatureCollection", + "geocoding": { + "timestamp": 1457996697682, + "engine": { + "version": "1.0", + "author": "Mapzen", + "name": "Pelias" + }, + "query": { + "querySize": 20, + "private": false, + "size": 0, + "parsed_text": { + "admin_parts": "New York, NY 10001, United States\"", + "regions": [ + "Garden", + "New York", + "10001", + "United States\"" + ], + "state": "NY", + "street": "\"Madison Square", + "name": "\"Madison Square Garden" + }, + "text": "\"Madison Square Garden, New York, NY 10001, United States\"" + }, + "attribution": "https:\/\/search.mapzen.com\/v1\/attribution", + "version": "0.1" + } +} diff --git a/test/test_helper.rb b/test/test_helper.rb index 73c9cac423684bfd364eb4633d6d31920d27e295..4a9c4d62c1fe549b51ef1865312d46cd02ffe939 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -321,6 +321,13 @@ module Geocoder end end + require 'geocoder/lookups/mapzen' + class Mapzen + def fixture_prefix + 'pelias' + end + end + require 'geocoder/lookups/ipinfo_io' class IpinfoIo private diff --git a/test/unit/lookups/geoportail_lu_test.rb b/test/unit/lookups/geoportail_lu_test.rb index 303e664a12705c37ba0d9a5afe82535baa6a3b00..d81949b65b1a2568e848f75b3484f7d9b8f35a8e 100644 --- a/test/unit/lookups/geoportail_lu_test.rb +++ b/test/unit/lookups/geoportail_lu_test.rb @@ -52,7 +52,7 @@ class GeoportailLuTest < GeocoderTestCase end def test_no_results - results = Geocoder.search('no results') + results = Geocoder.search('') assert_equal 0, results.length end diff --git a/test/unit/lookups/mapzen_test.rb b/test/unit/lookups/mapzen_test.rb new file mode 100644 index 0000000000000000000000000000000000000000..83ad909d1266b614af6d761a710995e680dd7b83 --- /dev/null +++ b/test/unit/lookups/mapzen_test.rb @@ -0,0 +1,17 @@ +# encoding: utf-8 +require 'test_helper' + +class MapzenTest < GeocoderTestCase + def setup + Geocoder.configure(lookup: :mapzen, api_key: 'abc123') + end + + def test_configure_default_endpoint + query = Geocoder::Query.new('Madison Square Garden, New York, NY') + assert_true query.url.start_with?('http://search.mapzen.com/v1/search'), query.url + end + + def test_inherits_from_pelias + assert_true Geocoder::Lookup::Mapzen.new.is_a?(Geocoder::Lookup::Pelias) + end +end diff --git a/test/unit/lookups/pelias_test.rb b/test/unit/lookups/pelias_test.rb new file mode 100644 index 0000000000000000000000000000000000000000..ba209d6e1d379946b9bafde5ac281fcd96007687 --- /dev/null +++ b/test/unit/lookups/pelias_test.rb @@ -0,0 +1,24 @@ +# encoding: utf-8 +require 'test_helper' + +class PeliasTest < GeocoderTestCase + def setup + Geocoder.configure(lookup: :pelias, api_key: 'abc123', pelias: {}) # Empty pelias hash only for test (pollution control) + end + + def test_configure_default_endpoint + query = Geocoder::Query.new('Madison Square Garden, New York, NY') + assert_true query.url.start_with?('http://localhost/v1/search'), query.url + end + + def test_configure_custom_endpoint + Geocoder.configure(lookup: :pelias, api_key: 'abc123', pelias: {endpoint: 'self.hosted.pelias/proxy'}) + query = Geocoder::Query.new('Madison Square Garden, New York, NY') + assert_true query.url.start_with?('http://self.hosted.pelias/proxy/v1/search'), query.url + end + + def test_query_url_defaults_to_one + query = Geocoder::Query.new('Madison Square Garden, New York, NY') + assert_match 'size=1', query.url + end +end