diff --git a/README.md b/README.md index 9f1049464c3e53fb798d7d9b5f8ae0a8ef2a9bc4..9c4883a22b7474313f7d6a4f3e91615bac40d171 100644 --- a/README.md +++ b/README.md @@ -176,11 +176,11 @@ Some utility methods are also available: # look up coordinates of some location (like searching Google Maps) Geocoder.coordinates("25 Main St, Cooperstown, NY") => [42.700149, -74.922767] - + # distance between Eiffel Tower and Empire State Building Geocoder::Calculations.distance_between([47.858205,2.294359], [40.748433,-73.985655]) => 3619.77359999382 # in configured units (default miles) - + # find the geographic center (aka center of gravity) of objects or points Geocoder::Calculations.geographic_center([city1, city2, [40.22,-73.99], city4]) => [35.14968, -90.048929] @@ -333,26 +333,26 @@ Some common configuration options are: # config/initializers/geocoder.rb Geocoder.configure( - + # geocoding service (see below for supported options): :lookup => :yandex, - + # IP address geocoding service (see below for supported options): :ip_lookup => :maxmind, - + # to use an API key: :api_key => "...", - + # geocoding service request timeout, in seconds (default 3): :timeout => 5, - + # set default units to kilometers: :units => :km, - + # caching (see below for details): :cache => Redis.new, :cache_prefix => "..." - + ) Please see `lib/geocoder/configuration.rb` for a complete list of configuration options. Additionally, some lookups have their own configuration options, some of which are directly supported by Geocoder. For example, to specify a value for Google's `bounds` parameter: @@ -375,21 +375,21 @@ You can also configure multiple geocoding services at once, like this: :timeout => 2, :cache => Redis.new, - + :yandex => { :api_key => "...", :timeout => 5 }, - + :baidu => { :api_key => "..." }, - + :maxmind => { :api_key => "...", :service => :omni } - + ) The above combines global and service-specific options and could be useful if you specify different geocoding services for different models or under different conditions. Lookup-specific settings override global settings. In the above example, the timeout for all lookups would be 2 seconds, except for Yandex which would be 5. @@ -713,6 +713,18 @@ This uses the PostcodeAnywhere UK Geocode service, this will geocode any string * **Terms of Service**: https://adresse.data.gouv.fr/faq/ (in french) * **Limitations**: [Data licensed under Open Database License (ODbL) (you must provide attribution).](http://openstreetmap.fr/ban) +#### AMap (`:amap`) + +- **API key**: required +- **Quota**: 2000/day and 2000/minute for personal developer, 4000000/day and 60000/minute for enterprise developer, for geocoding requests +- **Region**: China +- **SSL support**: yes +- **Languages**: Chinese (Simplified) +- **Documentation**: http://lbs.amap.com/api/webservice/guide/api/georegeo +- **Terms of Service**: http://lbs.amap.com/home/terms/ +- **Limitations**: Only good for non-commercial use. For commercial usage please check http://lbs.amap.com/home/terms/ +- **Notes**: To use AMap set `Geocoder.configure(:lookup => :amap, :api_key => "your_api_key")`. + ### IP Address Services #### FreeGeoIP (`:freegeoip`) @@ -835,7 +847,7 @@ You can generate ActiveRecord migrations and download and import data via provid # generate migration to create tables rails generate geocoder:maxmind:geolite_city - + # download, unpack, and import data rake geocoder:maxmind:geolite:load PACKAGE=city @@ -923,7 +935,7 @@ For example: # build an address from street, city, and state attributes geocoded_by :address_from_components - + # store the fetched address in the full_address attribute reverse_geocoded_by :latitude, :longitude, :address => :full_address end @@ -935,7 +947,7 @@ However, there can be only one set of latitude/longitude attributes, and whichev geocoded_by :address, :latitude => :fetched_latitude, # this will be overridden by the below :longitude => :fetched_longitude # same here - + reverse_geocoded_by :latitude, :longitude end @@ -963,7 +975,7 @@ For example: after_validation :reverse_geocode, :if => :has_coordinates after_validation :geocode, :if => :has_location, :unless => :has_coordinates - + end Use Outside of Rails @@ -1193,7 +1205,7 @@ Instead of using `includes` to reduce the number of database queries, try using # Pass a :select option to the near scope to get the columns you want. # Instead of City.near(...).includes(:venues), try: City.near("Omaha, NE", 20, :select => "cities.*, venues.*").joins(:venues) - + # This preload call will normally trigger two queries regardless of the # number of results; one query on hotels, and one query on administrators. # Instead of Hotel.near(...).includes(:administrator), try: diff --git a/gemfiles/Gemfile.ruby1.9.3 b/gemfiles/Gemfile.ruby1.9.3 index 961e96fb12d77aa8570c6a14a873c7a291cba8dc..8726c30f5eb7aa83f937f7668a5ee704f28bcd8f 100644 --- a/gemfiles/Gemfile.ruby1.9.3 +++ b/gemfiles/Gemfile.ruby1.9.3 @@ -18,7 +18,7 @@ group :development, :test do gem 'test-unit' # install newer version with omit() method gem 'debugger' - gem 'webmock' + gem 'webmock', '~> 2.3.2' platforms :jruby do gem 'jruby-openssl' diff --git a/lib/geocoder/lookup.rb b/lib/geocoder/lookup.rb index 04a6ef44b0cf39d4a903b35b70a95b90b32c8ac4..31e2b07081139dc3244d8f006d80bf6177a2d262 100644 --- a/lib/geocoder/lookup.rb +++ b/lib/geocoder/lookup.rb @@ -50,7 +50,8 @@ module Geocoder :geoportail_lu, :ban_data_gouv_fr, :test, - :latlon + :latlon, + :amap ] end diff --git a/lib/geocoder/lookups/amap.rb b/lib/geocoder/lookups/amap.rb new file mode 100644 index 0000000000000000000000000000000000000000..6989f1bdc3871ec9af43f0cc100de0afd0f72791 --- /dev/null +++ b/lib/geocoder/lookups/amap.rb @@ -0,0 +1,59 @@ +require 'geocoder/lookups/base' +require "geocoder/results/amap" + +module Geocoder::Lookup + class Amap < Base + + def name + "AMap" + end + + def required_api_key_parts + ["key"] + end + + def query_url(query) + path = query.reverse_geocode? ? 'regeo' : 'geo' + "http://restapi.amap.com/v3/geocode/#{path}?" + url_query_string(query) + end + + private # --------------------------------------------------------------- + + def results(query, reverse = false) + return [] unless doc = fetch_data(query) + case [doc['status'], doc['info']] + when ['1', 'OK'] + return doc['regeocodes'] unless doc['regeocodes'].blank? + return [doc['regeocode']] unless doc['regeocode'].blank? + return doc['geocodes'] unless doc['geocodes'].blank? + when ['0', 'INVALID_USER_KEY'] + raise_error(Geocoder::InvalidApiKey, "invalid api key") || + warn("#{self.name} Geocoding API error: invalid api key.") + else + raise_error(Geocoder::Error, "server error.") || + warn("#{self.name} Geocoding API error: server error - [#{doc['info']}]") + end + return [] + end + + def query_url_params(query) + params = { + :key => configuration.api_key, + :output => "json" + } + if query.reverse_geocode? + params[:location] = revert_coordinates(query.text) + params[:extensions] = "all" + params[:coordsys] = "gps" + else + params[:address] = query.sanitized_text + end + params.merge(super) + end + + def revert_coordinates(text) + [text[1],text[0]].join(",") + end + + end +end diff --git a/lib/geocoder/results/amap.rb b/lib/geocoder/results/amap.rb new file mode 100644 index 0000000000000000000000000000000000000000..1a526849a859fa530964444515ab8fd3bb590745 --- /dev/null +++ b/lib/geocoder/results/amap.rb @@ -0,0 +1,87 @@ +require 'geocoder/results/base' + +module Geocoder::Result + class Amap < Base + + def coordinates + location = @data['location'] || @data['roadinters'].try(:first).try(:[], 'location') \ + || address_components.try(:[], 'streetNumber').try(:[], 'location') + location.to_s.split(",").reverse.map(&:to_f) + end + + def address + formatted_address + end + + def state + province + end + + def province + address_components['province'] + end + + def city + address_components['city'] == [] ? province : address_components["city"] + end + + def district + address_components['district'] + end + + def street + if address_components["neighborhood"]["name"] != [] + return address_components["neighborhood"]["name"] + elsif address_components['township'] != [] + return address_components["township"] + else + return @data['street'] || address_components['streetNumber'].try(:[], 'street') + end + end + + def street_number + @data['number'] || address_components['streetNumber'].try(:[], 'number') + end + + def formatted_address + @data['formatted_address'] + end + + def address_components + @data['addressComponent'] || @data + end + + def state_code + "" + end + + def postal_code + "" + end + + def country + "China" + end + + def country_code + "CN" + end + + ## + # Get address components of a given type. Valid types are defined in + # Baidu's Geocoding API documentation and include (among others): + # + # :business + # :cityCode + # + def self.response_attributes + %w[roads pois roadinters] + end + + response_attributes.each do |a| + define_method a do + @data[a] + end + end + end +end \ No newline at end of file diff --git a/test/fixtures/amap_invalid_key b/test/fixtures/amap_invalid_key new file mode 100644 index 0000000000000000000000000000000000000000..590b88b79752e702fb965f34b45b9c6f5536ce02 --- /dev/null +++ b/test/fixtures/amap_invalid_key @@ -0,0 +1 @@ +{"status":"0","info":"INVALID_USER_KEY","infocode":"10001"} \ No newline at end of file diff --git a/test/fixtures/amap_no_results b/test/fixtures/amap_no_results new file mode 100644 index 0000000000000000000000000000000000000000..295f888776e14bee0064777144d337f9dde274b4 --- /dev/null +++ b/test/fixtures/amap_no_results @@ -0,0 +1,7 @@ +{ + "status": "1", + "info": "OK", + "infocode": "10000", + "count": "0", + "geocodes": [] +} \ No newline at end of file diff --git a/test/fixtures/amap_reverse b/test/fixtures/amap_reverse new file mode 100644 index 0000000000000000000000000000000000000000..6728ee849f6c27d4ac07286a840c4d986ede51cf --- /dev/null +++ b/test/fixtures/amap_reverse @@ -0,0 +1,38 @@ +{ + "status": "1", + "info": "OK", + "infocode": "10000", + "regeocode": { + "formatted_address": "Canada Ontario University Private ", + "addressComponent": { + "country": "Canada", + "province": "Ontario", + "city": [], + "citycode": [], + "district": [], + "adcode": [], + "township": "University Private", + "towncode": [], + "neighborhood": { + "name": [], + "type": [] + }, + "building": { + "name": [], + "type": [] + }, + "streetNumber": { + "street": "University Private", + "number": [], + "location": "-75.680763,45.426723", + "direction": [], + "distance": [] + }, + "businessAreas": [] + }, + "pois": [], + "roads": [], + "roadinters": [], + "aois": [] + } +} \ No newline at end of file diff --git a/test/fixtures/amap_shanghai_pearl_tower b/test/fixtures/amap_shanghai_pearl_tower new file mode 100644 index 0000000000000000000000000000000000000000..7d153af61bfce42682a972fc059dd7988624ef66 --- /dev/null +++ b/test/fixtures/amap_shanghai_pearl_tower @@ -0,0 +1,29 @@ +{ + "status": "1", + "info": "OK", + "infocode": "10000", + "count": "1", + "geocodes": [ + { + "formatted_address": "上海市浦东新区明ç 电视塔", + "province": "上海市", + "citycode": "021", + "city": "上海市", + "district": "浦东新区", + "township": [], + "neighborhood": { + "name": [], + "type": [] + }, + "building": { + "name": [], + "type": [] + }, + "adcode": "310115", + "street": [], + "number": [], + "location": "121.499567,31.239950", + "level": "兴趣点" + } + ] +} \ No newline at end of file diff --git a/test/test_helper.rb b/test/test_helper.rb index abb3efa09726e8f78db1df3c6394b678ae3f553d..dfec77beb7b537c633e6df4e662e1f0a39a9854b 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -373,6 +373,13 @@ module Geocoder end end + require 'geocoder/lookups/amap' + class Amap + private + def default_fixture_filename + "amap_shanghai_pearl_tower" + end + end end end diff --git a/test/unit/lookup_test.rb b/test/unit/lookup_test.rb index 5a91ad573596da1fbf2a8acda5b3772ff882a721..4fa3a28daa49bb1e427f3e7e5548094f4201cb98 100644 --- a/test/unit/lookup_test.rb +++ b/test/unit/lookup_test.rb @@ -74,7 +74,7 @@ class LookupTest < GeocoderTestCase def test_raises_exception_on_invalid_key Geocoder.configure(:always_raise => [Geocoder::InvalidApiKey]) #Geocoder::Lookup.all_services_except_test.each do |l| - [:bing, :yandex, :maxmind, :baidu, :baidu_ip].each do |l| + [:bing, :yandex, :maxmind, :baidu, :baidu_ip, :amap].each do |l| lookup = Geocoder::Lookup.get(l) assert_raises Geocoder::InvalidApiKey do lookup.send(:results, Geocoder::Query.new("invalid key")) @@ -85,7 +85,7 @@ class LookupTest < GeocoderTestCase def test_returns_empty_array_on_invalid_key silence_warnings do #Geocoder::Lookup.all_services_except_test.each do |l| - [:bing, :yandex, :maxmind, :baidu, :baidu_ip].each do |l| + [:bing, :yandex, :maxmind, :baidu, :baidu_ip, :amap].each do |l| Geocoder.configure(:lookup => l) set_api_key!(l) assert_equal [], Geocoder.search("invalid key") @@ -147,8 +147,14 @@ class LookupTest < GeocoderTestCase assert_match "token=MY_KEY", g.query_url(Geocoder::Query.new("232.65.123.94")) end + def test_amap_api_key + Geocoder.configure(:api_key => "MY_KEY") + g = Geocoder::Lookup::Amap.new + assert_match "key=MY_KEY", g.query_url(Geocoder::Query.new("202.198.16.3")) + end + def test_raises_configuration_error_on_missing_key - [:bing, :baidu].each do |l| + [:bing, :baidu, :amap].each do |l| assert_raises Geocoder::ConfigurationError do Geocoder.configure(:lookup => l, :api_key => nil) Geocoder.search("Madison Square Garden, New York, NY 10001, United States")