diff --git a/README.md b/README.md index e749b272b9a945e28ada3a37cf1127522e80e992..2dc1605edebef4984e0ef3e1228eb56e35368cfc 100644 --- a/README.md +++ b/README.md @@ -427,6 +427,17 @@ The following is a comparison of the supported geocoding APIs. The "Limitations" * **Terms of Service**: http://wiki.openstreetmap.org/wiki/Nominatim_usage_policy * **Limitations**: Please limit request rate to 1 per second and include your contact information in User-Agent headers (eg: `Geocoder.configure(:http_headers => { "User-Agent" => "your contact info" })`). Data licensed under CC-BY-SA (you must provide attribution). +#### OpenCageData (`:opencagedata`) + +* **API key**: required +* **Key signup**: http://geocoder.opencagedata.com +* **Quota**: 2500 requests / day, then ability to purchase more (free during beta) +* **Region**: world +* **SSL support**: yes +* **Languages**: worldwide +* **Documentation**: http://geocoder.opencagedata.com/api.html +* **Limitations**: Data licensed under CC-BY-SA or (you must provide attribution). + #### Yandex (`:yandex`) * **API key**: none diff --git a/lib/geocoder/lookup.rb b/lib/geocoder/lookup.rb index 9d380e3f3b76a2c9c7d887ecfda76172288fd413..eb1d4e5e004e7302ed6dc0e427ba862a58981aab 100644 --- a/lib/geocoder/lookup.rb +++ b/lib/geocoder/lookup.rb @@ -32,6 +32,7 @@ module Geocoder :yandex, :nominatim, :mapquest, + :opencagedata, :ovi, :here, :baidu, diff --git a/lib/geocoder/lookups/opencagedata.rb b/lib/geocoder/lookups/opencagedata.rb new file mode 100644 index 0000000000000000000000000000000000000000..8cb24b4189dd5691914b14c39dd1191c11890713 --- /dev/null +++ b/lib/geocoder/lookups/opencagedata.rb @@ -0,0 +1,45 @@ +require 'geocoder/lookups/base' +require 'geocoder/results/opencagedata' + +module Geocoder::Lookup + class Opencagedata < Base + + def name + "OpenCageData" + end + + def query_url(query) + "#{protocol}://api.opencagedata.com/geocode/v1/json?key=#{configuration.api_key}&q=#{url_query_string(query)}" + end + + def required_api_key_parts + ["key"] + end + + private + + def valid_response?(response) + status = parse_json(response.body)["status"] + super(response) and status['message'] == 'OK' + end + + def results(query) + data = fetch_data(query) + (data && data['results']) || [] + end + + def query_url_params(query) + params = { + :query => query.sanitized_text, + :language => (query.language || configuration.language) + }.merge(super) + + unless (bounds = query.options[:bounds]).nil? + params[:bounds] = bounds.map{ |point| "%f,%f" % point }.join(',') + end + + params + end + + end +end diff --git a/lib/geocoder/results/opencagedata.rb b/lib/geocoder/results/opencagedata.rb new file mode 100644 index 0000000000000000000000000000000000000000..1dcf9582b03b82e98f9d39b6cef775e84772df07 --- /dev/null +++ b/lib/geocoder/results/opencagedata.rb @@ -0,0 +1,82 @@ +require 'geocoder/results/base' + +module Geocoder::Result + class Opencagedata < Base + + def poi + %w[stadium bus_stop tram_stop].each do |key| + return @data['components'][key] if @data['components'].key?(key) + end + return nil + end + + def house_number + @data['components']['house_number'] + end + + def address + @data['formatted'] + end + + def street + %w[road pedestrian highway].each do |key| + return @data['components'][key] if @data['components'].key?(key) + end + return nil + end + + def city + %w[city town village hamlet].each do |key| + return @data['components'][key] if @data['components'].key?(key) + end + return nil + end + + def village + @data['components']['village'] + end + + + def state + @data['components']['state'] + end + + alias_method :state_code, :state + + def postal_code + @data['components']['postcode'].to_s + end + + def county + @data['components']['county'] + end + + def country + @data['components']['country'] + end + + def country_code + @data['components']['country_code'] + end + + def suburb + @data['components']['suburb'] + end + + def coordinates + [@data['lat'].to_f, @data['lon'].to_f] + end + def self.response_attributes + %w[boundingbox license + formatted stadium] + end + + response_attributes.each do |a| + unless method_defined?(a) + define_method a do + @data[a] + end + end + end + end +end diff --git a/test/fixtures/opencagedata_madison_square_garden b/test/fixtures/opencagedata_madison_square_garden new file mode 100644 index 0000000000000000000000000000000000000000..f5ff5df9d252d1dae1abdd12a222d1355d82f618 --- /dev/null +++ b/test/fixtures/opencagedata_madison_square_garden @@ -0,0 +1,73 @@ +{ + "licenses" : [ + { + "name" : "CC-BY-SA", + "url" : "http://creativecommons.org/licenses/by-sa/3.0/" + }, + { + "name" : "ODbL", + "url" : "http://opendatacommons.org/licenses/odbl/summary/" + } + ], + "rate" : { + "limit" : 2500, + "remaining" : 2488, + "reset" : 1407369600 + }, + "results" : [ + { + "annotations" : { + "OSM" : { + "url" : "http://www.openstreetmap.org/?mlat=40.75052&mlon=-73.99355#map=17/40.75052/-73.99355" + }, + "timezone" : { + "name" : "America/New_York", + "now_in_dst" : 1, + "offset_sec" : -14400, + "offset_string" : -400, + "short_name" : "EDT" + } + }, + "bounds" : { + "northeast" : { + "lat" : 40.751161, + "lng" : -73.9925922 + }, + "southwest" : { + "lat" : 40.7498531, + "lng" : -73.9944444 + } + }, + "components" : { + "country" : "United States of America", + "country_code" : "US", + "county" : "New York County", + "house_number" : 46, + "neighbourhood" : "Koreatown", + "postcode" : 10011, + "road" : "West 31st Street", + "city": "New York City", + "stadium" : "Madison Square Garden", + "state" : "New York", + "state_district" : "New York City" + }, + "confidence" : 10, + "formatted" : "46, West 31st Street, Koreatown, New York County, 10011, New York City, New York, United States of America, Madison Square Garden", + "geometry" : { + "lat" : 40.7505247, + "lng" : -73.9935500942432 + } + } + ], + "status" : { + "code" : 200, + "message" : "OK" + }, + "thanks" : "For using an OpenCage Data API", + "timestamp" : { + "created_http" : "Wed, 06 Aug 2014 12:53:59 GMT", + "created_unix" : 1407329639 + }, + "total_results" : 1, + "we_are_hiring" : "http://lokku.com/#jobs" +} \ No newline at end of file diff --git a/test/fixtures/opencagedata_no_results b/test/fixtures/opencagedata_no_results new file mode 100644 index 0000000000000000000000000000000000000000..16fdf7cb194eb37e7007bcd0d1badc71a1a7f18e --- /dev/null +++ b/test/fixtures/opencagedata_no_results @@ -0,0 +1,29 @@ +{ + "licenses" : [ + { + "name" : "CC-BY-SA", + "url" : "http://creativecommons.org/licenses/by-sa/3.0/" + }, + { + "name" : "ODbL", + "url" : "http://opendatacommons.org/licenses/odbl/summary/" + } + ], + "rate" : { + "limit" : 2500, + "remaining" : 2487, + "reset" : 1407369600 + }, + "results" : [], + "status" : { + "code" : 200, + "message" : "OK" + }, + "thanks" : "For using an OpenCage Data API", + "timestamp" : { + "created_http" : "Wed, 06 Aug 2014 12:56:03 GMT", + "created_unix" : 1407329763 + }, + "total_results" : 0, + "we_are_hiring" : "http://lokku.com/#jobs" +} diff --git a/test/unit/lookups/opencagedata_test.rb b/test/unit/lookups/opencagedata_test.rb new file mode 100644 index 0000000000000000000000000000000000000000..b856a2efcb8f2d2be1c0f04758b931bd1e3a12c5 --- /dev/null +++ b/test/unit/lookups/opencagedata_test.rb @@ -0,0 +1,50 @@ +# encoding: utf-8 +$: << File.join(File.dirname(__FILE__), "..", "..") +require 'test_helper' + +class OpencagedataTest < GeocoderTestCase + + def setup + Geocoder.configure(lookup: :opencagedata) + set_api_key!(:opencagedata) + end + + def test_result_components + result = Geocoder.search("Madison Square Garden, New York, NY").first + assert_equal "West 31st Street", result.street + assert_match /46, West 31st Street, Koreatown, New York County, 10011, New York City, New York, United States of America/, result.address + + end + + def test_opencagedata_query_url_contains_bounds + lookup = Geocoder::Lookup::Opencagedata.new + url = lookup.query_url(Geocoder::Query.new( + "Some street", + :bounds => [[40.0, -120.0], [39.0, -121.0]] + )) + assert_match(/bounds=40.0+%2C-120.0+%2C39.0+%2C-121.0+/, url) + end + + + def test_no_results + results = Geocoder.search("no results") + assert_equal 0, results.length + end + + + def test_opencagedata_reverse_url + query = Geocoder::Query.new([45.423733, -75.676333]) + assert_match /\bquery=45.423733%2C-75.676333\b/, query.url + end + + + + + # def test_raises_exception_when_over_query_limit + # Geocoder.configure(:always_raise => [Geocoder::OverQueryLimitError]) + # l = Geocoder::Lookup.get(:opencagedata) + # assert_raises Geocoder::OverQueryLimitError do + # l.send(:results, Geocoder::Query.new("over limit")) + # end + # end +end