geocoder2 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +5 -0
- data/.travis.yml +27 -0
- data/CHANGELOG.md +329 -0
- data/LICENSE +20 -0
- data/README.md +796 -0
- data/Rakefile +25 -0
- data/bin/geocode2 +5 -0
- data/examples/autoexpire_cache_dalli.rb +62 -0
- data/examples/autoexpire_cache_redis.rb +28 -0
- data/examples/cache_bypass.rb +48 -0
- data/gemfiles/Gemfile.mongoid-2.4.x +15 -0
- data/json?address=26+leonard+street%2C+Belmont&key=AIzaSyDoltU6YL8XeIQrSLFGk6ZfpKaWkPukwYQ&language=en +68 -0
- data/lib/generators/geocoder2/config/config_generator.rb +14 -0
- data/lib/generators/geocoder2/config/templates/initializer.rb +21 -0
- data/lib/geocoder2/cache.rb +89 -0
- data/lib/geocoder2/calculations.rb +389 -0
- data/lib/geocoder2/cli.rb +121 -0
- data/lib/geocoder2/configuration.rb +130 -0
- data/lib/geocoder2/configuration_hash.rb +11 -0
- data/lib/geocoder2/exceptions.rb +21 -0
- data/lib/geocoder2/lookup.rb +86 -0
- data/lib/geocoder2/lookups/baidu.rb +54 -0
- data/lib/geocoder2/lookups/base.rb +266 -0
- data/lib/geocoder2/lookups/bing.rb +47 -0
- data/lib/geocoder2/lookups/dstk.rb +20 -0
- data/lib/geocoder2/lookups/esri.rb +48 -0
- data/lib/geocoder2/lookups/freegeoip.rb +43 -0
- data/lib/geocoder2/lookups/geocoder_ca.rb +54 -0
- data/lib/geocoder2/lookups/geocoder_us.rb +39 -0
- data/lib/geocoder2/lookups/google.rb +69 -0
- data/lib/geocoder2/lookups/google_premier.rb +47 -0
- data/lib/geocoder2/lookups/mapquest.rb +59 -0
- data/lib/geocoder2/lookups/maxmind.rb +88 -0
- data/lib/geocoder2/lookups/nominatim.rb +44 -0
- data/lib/geocoder2/lookups/ovi.rb +62 -0
- data/lib/geocoder2/lookups/test.rb +44 -0
- data/lib/geocoder2/lookups/yahoo.rb +86 -0
- data/lib/geocoder2/lookups/yandex.rb +54 -0
- data/lib/geocoder2/models/active_record.rb +46 -0
- data/lib/geocoder2/models/base.rb +42 -0
- data/lib/geocoder2/models/mongo_base.rb +60 -0
- data/lib/geocoder2/models/mongo_mapper.rb +26 -0
- data/lib/geocoder2/models/mongoid.rb +32 -0
- data/lib/geocoder2/query.rb +107 -0
- data/lib/geocoder2/railtie.rb +26 -0
- data/lib/geocoder2/request.rb +23 -0
- data/lib/geocoder2/results/baidu.rb +79 -0
- data/lib/geocoder2/results/base.rb +67 -0
- data/lib/geocoder2/results/bing.rb +48 -0
- data/lib/geocoder2/results/dstk.rb +6 -0
- data/lib/geocoder2/results/esri.rb +51 -0
- data/lib/geocoder2/results/freegeoip.rb +45 -0
- data/lib/geocoder2/results/geocoder_ca.rb +60 -0
- data/lib/geocoder2/results/geocoder_us.rb +39 -0
- data/lib/geocoder2/results/google.rb +124 -0
- data/lib/geocoder2/results/google_premier.rb +6 -0
- data/lib/geocoder2/results/mapquest.rb +51 -0
- data/lib/geocoder2/results/maxmind.rb +135 -0
- data/lib/geocoder2/results/nominatim.rb +94 -0
- data/lib/geocoder2/results/ovi.rb +62 -0
- data/lib/geocoder2/results/test.rb +16 -0
- data/lib/geocoder2/results/yahoo.rb +55 -0
- data/lib/geocoder2/results/yandex.rb +80 -0
- data/lib/geocoder2/sql.rb +106 -0
- data/lib/geocoder2/stores/active_record.rb +272 -0
- data/lib/geocoder2/stores/base.rb +120 -0
- data/lib/geocoder2/stores/mongo_base.rb +89 -0
- data/lib/geocoder2/stores/mongo_mapper.rb +13 -0
- data/lib/geocoder2/stores/mongoid.rb +13 -0
- data/lib/geocoder2/version.rb +3 -0
- data/lib/geocoder2.rb +55 -0
- data/lib/hash_recursive_merge.rb +74 -0
- data/lib/oauth_util.rb +112 -0
- data/lib/tasks/geocoder2.rake +27 -0
- data/test/active_record_test.rb +15 -0
- data/test/cache_test.rb +35 -0
- data/test/calculations_test.rb +211 -0
- data/test/configuration_test.rb +78 -0
- data/test/custom_block_test.rb +32 -0
- data/test/error_handling_test.rb +43 -0
- data/test/fixtures/baidu_invalid_key +1 -0
- data/test/fixtures/baidu_no_results +1 -0
- data/test/fixtures/baidu_reverse +1 -0
- data/test/fixtures/baidu_shanghai_pearl_tower +12 -0
- data/test/fixtures/bing_invalid_key +1 -0
- data/test/fixtures/bing_madison_square_garden +40 -0
- data/test/fixtures/bing_no_results +16 -0
- data/test/fixtures/bing_reverse +42 -0
- data/test/fixtures/esri_madison_square_garden +59 -0
- data/test/fixtures/esri_no_results +8 -0
- data/test/fixtures/esri_reverse +21 -0
- data/test/fixtures/freegeoip_74_200_247_59 +12 -0
- data/test/fixtures/freegeoip_no_results +1 -0
- data/test/fixtures/geocoder_ca_madison_square_garden +1 -0
- data/test/fixtures/geocoder_ca_no_results +1 -0
- data/test/fixtures/geocoder_ca_reverse +34 -0
- data/test/fixtures/geocoder_us_madison_square_garden +1 -0
- data/test/fixtures/geocoder_us_no_results +1 -0
- data/test/fixtures/google_garbage +456 -0
- data/test/fixtures/google_madison_square_garden +57 -0
- data/test/fixtures/google_no_city_data +44 -0
- data/test/fixtures/google_no_locality +51 -0
- data/test/fixtures/google_no_results +4 -0
- data/test/fixtures/google_over_limit +4 -0
- data/test/fixtures/mapquest_error +16 -0
- data/test/fixtures/mapquest_invalid_api_key +16 -0
- data/test/fixtures/mapquest_invalid_request +16 -0
- data/test/fixtures/mapquest_madison_square_garden +52 -0
- data/test/fixtures/mapquest_no_results +16 -0
- data/test/fixtures/maxmind_24_24_24_21 +1 -0
- data/test/fixtures/maxmind_24_24_24_22 +1 -0
- data/test/fixtures/maxmind_24_24_24_23 +1 -0
- data/test/fixtures/maxmind_24_24_24_24 +1 -0
- data/test/fixtures/maxmind_74_200_247_59 +1 -0
- data/test/fixtures/maxmind_invalid_key +1 -0
- data/test/fixtures/maxmind_no_results +1 -0
- data/test/fixtures/nominatim_madison_square_garden +150 -0
- data/test/fixtures/nominatim_no_results +1 -0
- data/test/fixtures/ovi_madison_square_garden +72 -0
- data/test/fixtures/ovi_no_results +8 -0
- data/test/fixtures/yahoo_error +1 -0
- data/test/fixtures/yahoo_invalid_key +2 -0
- data/test/fixtures/yahoo_madison_square_garden +52 -0
- data/test/fixtures/yahoo_no_results +10 -0
- data/test/fixtures/yahoo_over_limit +2 -0
- data/test/fixtures/yandex_invalid_key +1 -0
- data/test/fixtures/yandex_kremlin +48 -0
- data/test/fixtures/yandex_no_city_and_town +112 -0
- data/test/fixtures/yandex_no_results +16 -0
- data/test/geocoder_test.rb +59 -0
- data/test/https_test.rb +16 -0
- data/test/integration/smoke_test.rb +26 -0
- data/test/lookup_test.rb +117 -0
- data/test/method_aliases_test.rb +25 -0
- data/test/mongoid_test.rb +46 -0
- data/test/mongoid_test_helper.rb +43 -0
- data/test/near_test.rb +61 -0
- data/test/oauth_util_test.rb +30 -0
- data/test/proxy_test.rb +36 -0
- data/test/query_test.rb +52 -0
- data/test/request_test.rb +29 -0
- data/test/result_test.rb +42 -0
- data/test/services_test.rb +393 -0
- data/test/test_helper.rb +289 -0
- data/test/test_mode_test.rb +59 -0
- metadata +213 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
require 'geocoder2/lookups/base'
|
|
2
|
+
require "geocoder2/results/google"
|
|
3
|
+
|
|
4
|
+
module Geocoder2::Lookup
|
|
5
|
+
class Google < Base
|
|
6
|
+
|
|
7
|
+
def name
|
|
8
|
+
"Google"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def map_link_url(coordinates)
|
|
12
|
+
"http://maps.google.com/maps?q=#{coordinates.join(',')}"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def query_url(query)
|
|
16
|
+
"#{configuration.api_key ? 'https' : protocol}://maps.googleapis.com/maps/api/geocode/json?" + url_query_string(query)
|
|
17
|
+
"#{protocol}://maps.googleapis.com/maps/api/geocode/json?" + url_query_string(query)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private # ---------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
def valid_response?(response)
|
|
23
|
+
json = parse_json(response.body)
|
|
24
|
+
status = json["status"] if json
|
|
25
|
+
super(response) and ['OK', 'ZERO_RESULTS'].include?(status)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def results(query)
|
|
29
|
+
return [] unless doc = fetch_data(query)
|
|
30
|
+
case doc['status']; when "OK" # OK status implies >0 results
|
|
31
|
+
return doc['results']
|
|
32
|
+
when "OVER_QUERY_LIMIT"
|
|
33
|
+
raise_error(Geocoder2::OverQueryLimitError) ||
|
|
34
|
+
warn("Google Geocoding API error: over query limit.")
|
|
35
|
+
when "REQUEST_DENIED"
|
|
36
|
+
raise_error(Geocoder2::RequestDenied) ||
|
|
37
|
+
warn("Google Geocoding API error: request denied.")
|
|
38
|
+
when "INVALID_REQUEST"
|
|
39
|
+
raise_error(Geocoder2::InvalidRequest) ||
|
|
40
|
+
warn("Google Geocoding API error: invalid request.")
|
|
41
|
+
end
|
|
42
|
+
return []
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def query_url_google_params(query)
|
|
46
|
+
params = {
|
|
47
|
+
(query.reverse_geocode? ? :latlng : :address) => query.sanitized_text,
|
|
48
|
+
#:sensor => "false",
|
|
49
|
+
:language => configuration.language
|
|
50
|
+
}
|
|
51
|
+
unless (bounds = query.options[:bounds]).nil?
|
|
52
|
+
params[:bounds] = bounds.map{ |point| "%f,%f" % point }.join('|')
|
|
53
|
+
end
|
|
54
|
+
unless (region = query.options[:region]).nil?
|
|
55
|
+
params[:region] = region
|
|
56
|
+
end
|
|
57
|
+
unless (components = query.options[:components]).nil?
|
|
58
|
+
params[:components] = components.is_a?(Array) ? components.join("|") : components
|
|
59
|
+
end
|
|
60
|
+
params
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def query_url_params(query)
|
|
64
|
+
query_url_google_params(query).merge(
|
|
65
|
+
:key => configuration.api_key
|
|
66
|
+
).merge(super)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
require 'openssl'
|
|
2
|
+
require 'base64'
|
|
3
|
+
require 'geocoder2/lookups/google'
|
|
4
|
+
require 'geocoder2/results/google_premier'
|
|
5
|
+
|
|
6
|
+
module Geocoder2::Lookup
|
|
7
|
+
class GooglePremier < Google
|
|
8
|
+
|
|
9
|
+
def name
|
|
10
|
+
"Google Premier"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def required_api_key_parts
|
|
14
|
+
["private key", "client", "channel"]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def query_url(query)
|
|
18
|
+
path = "/maps/api/geocode/json?" + url_query_string(query)
|
|
19
|
+
"#{protocol}://maps.googleapis.com#{path}&signature=#{sign(path)}"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private # ---------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
def query_url_params(query)
|
|
25
|
+
query_url_google_params(query).merge(super).merge(
|
|
26
|
+
:key => nil, # don't use param inherited from Google lookup
|
|
27
|
+
:client => configuration.api_key[1],
|
|
28
|
+
:channel => configuration.api_key[2]
|
|
29
|
+
)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def sign(string)
|
|
33
|
+
raw_private_key = url_safe_base64_decode(configuration.api_key[0])
|
|
34
|
+
digest = OpenSSL::Digest::Digest.new('sha1')
|
|
35
|
+
raw_signature = OpenSSL::HMAC.digest(digest, raw_private_key, string)
|
|
36
|
+
url_safe_base64_encode(raw_signature)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def url_safe_base64_decode(base64_string)
|
|
40
|
+
Base64.decode64(base64_string.tr('-_', '+/'))
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def url_safe_base64_encode(raw)
|
|
44
|
+
Base64.encode64(raw).tr('+/', '-_').strip
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
require 'cgi'
|
|
2
|
+
require 'geocoder2/lookups/base'
|
|
3
|
+
require "geocoder2/results/mapquest"
|
|
4
|
+
|
|
5
|
+
module Geocoder2::Lookup
|
|
6
|
+
class Mapquest < Base
|
|
7
|
+
|
|
8
|
+
def name
|
|
9
|
+
"Mapquest"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def required_api_key_parts
|
|
13
|
+
["key"]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def query_url(query)
|
|
17
|
+
domain = configuration[:licensed] ? "www" : "open"
|
|
18
|
+
version = configuration[:version] || 1
|
|
19
|
+
url = "#{protocol}://#{domain}.mapquestapi.com/geocoding/v#{version}/#{search_type(query)}?"
|
|
20
|
+
url + url_query_string(query)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private # ---------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
def search_type(query)
|
|
26
|
+
query.reverse_geocode? ? "reverse" : "address"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def query_url_params(query)
|
|
30
|
+
params = { :location => query.sanitized_text }
|
|
31
|
+
if key = configuration.api_key
|
|
32
|
+
params[:key] = CGI.unescape(key)
|
|
33
|
+
end
|
|
34
|
+
params.merge(super)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# http://www.mapquestapi.com/geocoding/status_codes.html
|
|
38
|
+
# http://open.mapquestapi.com/geocoding/status_codes.html
|
|
39
|
+
def results(query)
|
|
40
|
+
return [] unless doc = fetch_data(query)
|
|
41
|
+
return doc["results"][0]['locations'] if doc['info']['statuscode'] == 0 # A successful geocode call
|
|
42
|
+
|
|
43
|
+
messages = doc['info']['messages'].join
|
|
44
|
+
|
|
45
|
+
case doc['info']['statuscode']
|
|
46
|
+
when 400 # Error with input
|
|
47
|
+
raise_error(Geocoder2::InvalidRequest, messages) ||
|
|
48
|
+
warn("Mapquest Geocoding API error: #{messages}")
|
|
49
|
+
when 403 # Key related error
|
|
50
|
+
raise_error(Geocoder2::InvalidApiKey, messages) ||
|
|
51
|
+
warn("Mapquest Geocoding API error: #{messages}")
|
|
52
|
+
when 500 # Unknown error
|
|
53
|
+
raise_error(Geocoder2::Error, messages) ||
|
|
54
|
+
warn("Mapquest Geocoding API error: #{messages}")
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
require 'geocoder2/lookups/base'
|
|
2
|
+
require 'geocoder2/results/maxmind'
|
|
3
|
+
require 'csv'
|
|
4
|
+
|
|
5
|
+
module Geocoder2::Lookup
|
|
6
|
+
class Maxmind < Base
|
|
7
|
+
|
|
8
|
+
def name
|
|
9
|
+
"MaxMind"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def query_url(query)
|
|
13
|
+
"#{protocol}://geoip.maxmind.com/#{service_code}?" + url_query_string(query)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private # ---------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
##
|
|
19
|
+
# Return the name of the configured service, or raise an exception.
|
|
20
|
+
#
|
|
21
|
+
def configured_service!
|
|
22
|
+
if s = configuration[:service] and services.keys.include?(s)
|
|
23
|
+
return s
|
|
24
|
+
else
|
|
25
|
+
raise(
|
|
26
|
+
Geocoder2::ConfigurationError,
|
|
27
|
+
"When using MaxMind you MUST specify a service name: " +
|
|
28
|
+
"Geocoder2.configure(:maxmind => {:service => ...}), " +
|
|
29
|
+
"where '...' is one of: #{services.keys.inspect}"
|
|
30
|
+
)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def service_code
|
|
35
|
+
services[configured_service!]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def service_response_fields_count
|
|
39
|
+
Geocoder2::Result::Maxmind.field_names[configured_service!].size
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def data_contains_error?(parsed_data)
|
|
43
|
+
# if all fields given then there is an error
|
|
44
|
+
parsed_data.size == service_response_fields_count and !parsed_data.last.nil?
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
##
|
|
48
|
+
# Service names mapped to code used in URL.
|
|
49
|
+
#
|
|
50
|
+
def services
|
|
51
|
+
{
|
|
52
|
+
:country => "a",
|
|
53
|
+
:city => "b",
|
|
54
|
+
:city_isp_org => "f",
|
|
55
|
+
:omni => "e"
|
|
56
|
+
}
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def results(query)
|
|
60
|
+
# don't look up a loopback address, just return the stored result
|
|
61
|
+
return [reserved_result] if query.loopback_ip_address?
|
|
62
|
+
doc = fetch_data(query)
|
|
63
|
+
if doc and doc.is_a?(Array)
|
|
64
|
+
if !data_contains_error?(doc)
|
|
65
|
+
return [doc]
|
|
66
|
+
elsif doc.last == "INVALID_LICENSE_KEY"
|
|
67
|
+
raise_error(Geocoder2::InvalidApiKey) || warn("Invalid MaxMind API key.")
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
return []
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def parse_raw_data(raw_data)
|
|
74
|
+
CSV.parse_line raw_data
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def reserved_result
|
|
78
|
+
",,,,0,0,0,0,,,".split(",")
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def query_url_params(query)
|
|
82
|
+
{
|
|
83
|
+
:l => configuration.api_key,
|
|
84
|
+
:i => query.sanitized_text
|
|
85
|
+
}.merge(super)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
require 'geocoder2/lookups/base'
|
|
2
|
+
require "geocoder2/results/nominatim"
|
|
3
|
+
|
|
4
|
+
module Geocoder2::Lookup
|
|
5
|
+
class Nominatim < Base
|
|
6
|
+
|
|
7
|
+
def name
|
|
8
|
+
"Nominatim"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def map_link_url(coordinates)
|
|
12
|
+
"http://www.openstreetmap.org/?lat=#{coordinates[0]}&lon=#{coordinates[1]}&zoom=15&layers=M"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def query_url(query)
|
|
16
|
+
method = query.reverse_geocode? ? "reverse" : "search"
|
|
17
|
+
host = configuration[:host] || "nominatim.openstreetmap.org"
|
|
18
|
+
"#{protocol}://#{host}/#{method}?" + url_query_string(query)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private # ---------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
def results(query)
|
|
24
|
+
return [] unless doc = fetch_data(query)
|
|
25
|
+
doc.is_a?(Array) ? doc : [doc]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def query_url_params(query)
|
|
29
|
+
params = {
|
|
30
|
+
:format => "json",
|
|
31
|
+
:addressdetails => "1",
|
|
32
|
+
:"accept-language" => configuration.language
|
|
33
|
+
}.merge(super)
|
|
34
|
+
if query.reverse_geocode?
|
|
35
|
+
lat,lon = query.coordinates
|
|
36
|
+
params[:lat] = lat
|
|
37
|
+
params[:lon] = lon
|
|
38
|
+
else
|
|
39
|
+
params[:q] = query.sanitized_text
|
|
40
|
+
end
|
|
41
|
+
params
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
require 'geocoder2/lookups/base'
|
|
2
|
+
require 'geocoder2/results/ovi'
|
|
3
|
+
|
|
4
|
+
module Geocoder2::Lookup
|
|
5
|
+
class Ovi < Base
|
|
6
|
+
|
|
7
|
+
def name
|
|
8
|
+
"Ovi"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def required_api_key_parts
|
|
12
|
+
[]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def query_url(query)
|
|
16
|
+
"#{protocol}://lbs.ovi.com/search/6.2/#{if query.reverse_geocode? then 'reverse' end}geocode.json?" + url_query_string(query)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private # ---------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
def results(query)
|
|
22
|
+
return [] unless doc = fetch_data(query)
|
|
23
|
+
return [] unless doc['Response'] && doc['Response']['View']
|
|
24
|
+
if r=doc['Response']['View']
|
|
25
|
+
return [] if r.nil? || !r.is_a?(Array) || r.empty?
|
|
26
|
+
return r.first['Result']
|
|
27
|
+
end
|
|
28
|
+
[]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def query_url_params(query)
|
|
32
|
+
options = {
|
|
33
|
+
:gen=>1,
|
|
34
|
+
:app_id=>api_key,
|
|
35
|
+
:app_code=>api_code
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if query.reverse_geocode?
|
|
39
|
+
super.merge(options).merge(
|
|
40
|
+
:prox=>query.sanitized_text,
|
|
41
|
+
:mode=>:retrieveAddresses
|
|
42
|
+
)
|
|
43
|
+
else
|
|
44
|
+
super.merge(options).merge(
|
|
45
|
+
:searchtext=>query.sanitized_text
|
|
46
|
+
)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def api_key
|
|
51
|
+
if a=configuration.api_key
|
|
52
|
+
return a.first if a.is_a?(Array)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def api_code
|
|
57
|
+
if a=configuration.api_key
|
|
58
|
+
return a.last if a.is_a?(Array)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
require 'geocoder2/lookups/base'
|
|
2
|
+
require 'geocoder2/results/test'
|
|
3
|
+
|
|
4
|
+
module Geocoder2
|
|
5
|
+
module Lookup
|
|
6
|
+
class Test < Base
|
|
7
|
+
|
|
8
|
+
def name
|
|
9
|
+
"Test"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.add_stub(query_text, results)
|
|
13
|
+
stubs[query_text] = results
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.set_default_stub(results)
|
|
17
|
+
@default_stub = results
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.read_stub(query_text)
|
|
21
|
+
stubs.fetch(query_text) {
|
|
22
|
+
return @default_stub unless @default_stub.nil?
|
|
23
|
+
raise ArgumentError, "unknown stub request #{query_text}"
|
|
24
|
+
}
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.stubs
|
|
28
|
+
@stubs ||= {}
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.reset
|
|
32
|
+
@stubs = {}
|
|
33
|
+
@default_stub = nil
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def results(query)
|
|
39
|
+
Geocoder2::Lookup::Test.read_stub(query.text)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
require 'geocoder2/lookups/base'
|
|
2
|
+
require "geocoder2/results/yahoo"
|
|
3
|
+
require 'oauth_util'
|
|
4
|
+
|
|
5
|
+
module Geocoder2::Lookup
|
|
6
|
+
class Yahoo < Base
|
|
7
|
+
|
|
8
|
+
def name
|
|
9
|
+
"Yahoo BOSS"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def map_link_url(coordinates)
|
|
13
|
+
"http://maps.yahoo.com/#lat=#{coordinates[0]}&lon=#{coordinates[1]}"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def required_api_key_parts
|
|
17
|
+
["consumer key", "consumer secret"]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def query_url(query)
|
|
21
|
+
parsed_url = URI.parse(raw_url(query))
|
|
22
|
+
o = OauthUtil.new
|
|
23
|
+
o.consumer_key = configuration.api_key[0]
|
|
24
|
+
o.consumer_secret = configuration.api_key[1]
|
|
25
|
+
base_url + o.sign(parsed_url).query_string
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private # ---------------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
def results(query)
|
|
31
|
+
return [] unless doc = fetch_data(query)
|
|
32
|
+
doc = doc['bossresponse']
|
|
33
|
+
if doc['responsecode'].to_i == 200
|
|
34
|
+
if doc['placefinder']['count'].to_i > 0
|
|
35
|
+
return doc['placefinder']['results']
|
|
36
|
+
else
|
|
37
|
+
return []
|
|
38
|
+
end
|
|
39
|
+
else
|
|
40
|
+
warn "Yahoo Geocoding API error: #{doc['responsecode']} (#{doc['reason']})."
|
|
41
|
+
return []
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
##
|
|
46
|
+
# Yahoo returns errors as XML even when JSON format is specified.
|
|
47
|
+
# Handle that here, without parsing the XML
|
|
48
|
+
# (which would add unnecessary complexity).
|
|
49
|
+
# Yahoo auth errors can also be cryptic, so add raw error desc
|
|
50
|
+
# to warning message.
|
|
51
|
+
#
|
|
52
|
+
def parse_raw_data(raw_data)
|
|
53
|
+
if raw_data.match /^<\?xml/
|
|
54
|
+
if raw_data.include?("Rate Limit Exceeded")
|
|
55
|
+
raise_error(Geocoder2::OverQueryLimitError) || warn("Over API query limit.")
|
|
56
|
+
elsif raw_data =~ /<yahoo:description>(Please provide valid credentials.*)<\/yahoo:description>/i
|
|
57
|
+
raise_error(Geocoder2::InvalidApiKey) || warn("Invalid API key. Error response: #{$1}")
|
|
58
|
+
end
|
|
59
|
+
else
|
|
60
|
+
super(raw_data)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def query_url_params(query)
|
|
65
|
+
{
|
|
66
|
+
:location => query.sanitized_text,
|
|
67
|
+
:flags => "JXTSR",
|
|
68
|
+
:gflags => "AC#{'R' if query.reverse_geocode?}",
|
|
69
|
+
:locale => "#{configuration.language}_US",
|
|
70
|
+
:appid => configuration.api_key
|
|
71
|
+
}.merge(super)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def cache_key(query)
|
|
75
|
+
raw_url(query)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def base_url
|
|
79
|
+
"#{protocol}://yboss.yahooapis.com/geo/placefinder?"
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def raw_url(query)
|
|
83
|
+
base_url + url_query_string(query)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
require 'geocoder2/lookups/base'
|
|
2
|
+
require "geocoder2/results/yandex"
|
|
3
|
+
|
|
4
|
+
module Geocoder2::Lookup
|
|
5
|
+
class Yandex < Base
|
|
6
|
+
|
|
7
|
+
def name
|
|
8
|
+
"Yandex"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def map_link_url(coordinates)
|
|
12
|
+
"http://maps.yandex.ru/?ll=#{coordinates.reverse.join(',')}"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def query_url(query)
|
|
16
|
+
"#{protocol}://geocode-maps.yandex.ru/1.x/?" + url_query_string(query)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private # ---------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
def results(query)
|
|
22
|
+
return [] unless doc = fetch_data(query)
|
|
23
|
+
if err = doc['error']
|
|
24
|
+
if err["status"] == 401 and err["message"] == "invalid key"
|
|
25
|
+
raise_error(Geocoder2::InvalidApiKey) || warn("Invalid API key.")
|
|
26
|
+
else
|
|
27
|
+
warn "Yandex Geocoding API error: #{err['status']} (#{err['message']})."
|
|
28
|
+
end
|
|
29
|
+
return []
|
|
30
|
+
end
|
|
31
|
+
if doc = doc['response']['GeoObjectCollection']
|
|
32
|
+
meta = doc['metaDataProperty']['Geocoder2ResponseMetaData']
|
|
33
|
+
return meta['found'].to_i > 0 ? doc['featureMember'] : []
|
|
34
|
+
else
|
|
35
|
+
warn "Yandex Geocoding API error: unexpected response format."
|
|
36
|
+
return []
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def query_url_params(query)
|
|
41
|
+
if query.reverse_geocode?
|
|
42
|
+
q = query.coordinates.reverse.join(",")
|
|
43
|
+
else
|
|
44
|
+
q = query.sanitized_text
|
|
45
|
+
end
|
|
46
|
+
{
|
|
47
|
+
:geocode => q,
|
|
48
|
+
:format => "json",
|
|
49
|
+
:plng => "#{configuration.language}", # supports ru, uk, be
|
|
50
|
+
:key => configuration.api_key
|
|
51
|
+
}.merge(super)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
require 'geocoder2/models/base'
|
|
2
|
+
|
|
3
|
+
module Geocoder2
|
|
4
|
+
module Model
|
|
5
|
+
module ActiveRecord
|
|
6
|
+
include Base
|
|
7
|
+
|
|
8
|
+
##
|
|
9
|
+
# Set attribute names and include the Geocoder2 module.
|
|
10
|
+
#
|
|
11
|
+
def geocoded_by(address_attr, options = {}, &block)
|
|
12
|
+
geocoder2_init(
|
|
13
|
+
:geocode => true,
|
|
14
|
+
:user_address => address_attr,
|
|
15
|
+
:latitude => options[:latitude] || :latitude,
|
|
16
|
+
:longitude => options[:longitude] || :longitude,
|
|
17
|
+
:geocode_block => block,
|
|
18
|
+
:units => options[:units],
|
|
19
|
+
:method => options[:method]
|
|
20
|
+
)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
##
|
|
24
|
+
# Set attribute names and include the Geocoder2 module.
|
|
25
|
+
#
|
|
26
|
+
def reverse_geocoded_by(latitude_attr, longitude_attr, options = {}, &block)
|
|
27
|
+
geocoder2_init(
|
|
28
|
+
:reverse_geocode => true,
|
|
29
|
+
:fetched_address => options[:address] || :address,
|
|
30
|
+
:latitude => latitude_attr,
|
|
31
|
+
:longitude => longitude_attr,
|
|
32
|
+
:reverse_block => block,
|
|
33
|
+
:units => options[:units],
|
|
34
|
+
:method => options[:method]
|
|
35
|
+
)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
private # --------------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
def geocoder2_file_name; "active_record"; end
|
|
42
|
+
def geocoder2_module_name; "ActiveRecord"; end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
require 'geocoder2'
|
|
2
|
+
|
|
3
|
+
module Geocoder2
|
|
4
|
+
|
|
5
|
+
##
|
|
6
|
+
# Methods for invoking Geocoder2 in a model.
|
|
7
|
+
#
|
|
8
|
+
module Model
|
|
9
|
+
module Base
|
|
10
|
+
|
|
11
|
+
def geocoder2_options
|
|
12
|
+
if defined?(@geocoder2_options)
|
|
13
|
+
@geocoder2_options
|
|
14
|
+
elsif superclass.respond_to?(:geocoder2_options)
|
|
15
|
+
superclass.geocoder2_options || { }
|
|
16
|
+
else
|
|
17
|
+
{ }
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def geocoded_by
|
|
22
|
+
fail
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def reverse_geocoded_by
|
|
26
|
+
fail
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private # ----------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
def geocoder2_init(options)
|
|
32
|
+
unless @geocoder2_options
|
|
33
|
+
@geocoder2_options = {}
|
|
34
|
+
require "geocoder2/stores/#{geocoder2_file_name}"
|
|
35
|
+
include Geocoder2::Store.const_get(geocoder2_module_name)
|
|
36
|
+
end
|
|
37
|
+
@geocoder2_options.merge! options
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|