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.
Files changed (146) hide show
  1. data/.gitignore +5 -0
  2. data/.travis.yml +27 -0
  3. data/CHANGELOG.md +329 -0
  4. data/LICENSE +20 -0
  5. data/README.md +796 -0
  6. data/Rakefile +25 -0
  7. data/bin/geocode2 +5 -0
  8. data/examples/autoexpire_cache_dalli.rb +62 -0
  9. data/examples/autoexpire_cache_redis.rb +28 -0
  10. data/examples/cache_bypass.rb +48 -0
  11. data/gemfiles/Gemfile.mongoid-2.4.x +15 -0
  12. data/json?address=26+leonard+street%2C+Belmont&key=AIzaSyDoltU6YL8XeIQrSLFGk6ZfpKaWkPukwYQ&language=en +68 -0
  13. data/lib/generators/geocoder2/config/config_generator.rb +14 -0
  14. data/lib/generators/geocoder2/config/templates/initializer.rb +21 -0
  15. data/lib/geocoder2/cache.rb +89 -0
  16. data/lib/geocoder2/calculations.rb +389 -0
  17. data/lib/geocoder2/cli.rb +121 -0
  18. data/lib/geocoder2/configuration.rb +130 -0
  19. data/lib/geocoder2/configuration_hash.rb +11 -0
  20. data/lib/geocoder2/exceptions.rb +21 -0
  21. data/lib/geocoder2/lookup.rb +86 -0
  22. data/lib/geocoder2/lookups/baidu.rb +54 -0
  23. data/lib/geocoder2/lookups/base.rb +266 -0
  24. data/lib/geocoder2/lookups/bing.rb +47 -0
  25. data/lib/geocoder2/lookups/dstk.rb +20 -0
  26. data/lib/geocoder2/lookups/esri.rb +48 -0
  27. data/lib/geocoder2/lookups/freegeoip.rb +43 -0
  28. data/lib/geocoder2/lookups/geocoder_ca.rb +54 -0
  29. data/lib/geocoder2/lookups/geocoder_us.rb +39 -0
  30. data/lib/geocoder2/lookups/google.rb +69 -0
  31. data/lib/geocoder2/lookups/google_premier.rb +47 -0
  32. data/lib/geocoder2/lookups/mapquest.rb +59 -0
  33. data/lib/geocoder2/lookups/maxmind.rb +88 -0
  34. data/lib/geocoder2/lookups/nominatim.rb +44 -0
  35. data/lib/geocoder2/lookups/ovi.rb +62 -0
  36. data/lib/geocoder2/lookups/test.rb +44 -0
  37. data/lib/geocoder2/lookups/yahoo.rb +86 -0
  38. data/lib/geocoder2/lookups/yandex.rb +54 -0
  39. data/lib/geocoder2/models/active_record.rb +46 -0
  40. data/lib/geocoder2/models/base.rb +42 -0
  41. data/lib/geocoder2/models/mongo_base.rb +60 -0
  42. data/lib/geocoder2/models/mongo_mapper.rb +26 -0
  43. data/lib/geocoder2/models/mongoid.rb +32 -0
  44. data/lib/geocoder2/query.rb +107 -0
  45. data/lib/geocoder2/railtie.rb +26 -0
  46. data/lib/geocoder2/request.rb +23 -0
  47. data/lib/geocoder2/results/baidu.rb +79 -0
  48. data/lib/geocoder2/results/base.rb +67 -0
  49. data/lib/geocoder2/results/bing.rb +48 -0
  50. data/lib/geocoder2/results/dstk.rb +6 -0
  51. data/lib/geocoder2/results/esri.rb +51 -0
  52. data/lib/geocoder2/results/freegeoip.rb +45 -0
  53. data/lib/geocoder2/results/geocoder_ca.rb +60 -0
  54. data/lib/geocoder2/results/geocoder_us.rb +39 -0
  55. data/lib/geocoder2/results/google.rb +124 -0
  56. data/lib/geocoder2/results/google_premier.rb +6 -0
  57. data/lib/geocoder2/results/mapquest.rb +51 -0
  58. data/lib/geocoder2/results/maxmind.rb +135 -0
  59. data/lib/geocoder2/results/nominatim.rb +94 -0
  60. data/lib/geocoder2/results/ovi.rb +62 -0
  61. data/lib/geocoder2/results/test.rb +16 -0
  62. data/lib/geocoder2/results/yahoo.rb +55 -0
  63. data/lib/geocoder2/results/yandex.rb +80 -0
  64. data/lib/geocoder2/sql.rb +106 -0
  65. data/lib/geocoder2/stores/active_record.rb +272 -0
  66. data/lib/geocoder2/stores/base.rb +120 -0
  67. data/lib/geocoder2/stores/mongo_base.rb +89 -0
  68. data/lib/geocoder2/stores/mongo_mapper.rb +13 -0
  69. data/lib/geocoder2/stores/mongoid.rb +13 -0
  70. data/lib/geocoder2/version.rb +3 -0
  71. data/lib/geocoder2.rb +55 -0
  72. data/lib/hash_recursive_merge.rb +74 -0
  73. data/lib/oauth_util.rb +112 -0
  74. data/lib/tasks/geocoder2.rake +27 -0
  75. data/test/active_record_test.rb +15 -0
  76. data/test/cache_test.rb +35 -0
  77. data/test/calculations_test.rb +211 -0
  78. data/test/configuration_test.rb +78 -0
  79. data/test/custom_block_test.rb +32 -0
  80. data/test/error_handling_test.rb +43 -0
  81. data/test/fixtures/baidu_invalid_key +1 -0
  82. data/test/fixtures/baidu_no_results +1 -0
  83. data/test/fixtures/baidu_reverse +1 -0
  84. data/test/fixtures/baidu_shanghai_pearl_tower +12 -0
  85. data/test/fixtures/bing_invalid_key +1 -0
  86. data/test/fixtures/bing_madison_square_garden +40 -0
  87. data/test/fixtures/bing_no_results +16 -0
  88. data/test/fixtures/bing_reverse +42 -0
  89. data/test/fixtures/esri_madison_square_garden +59 -0
  90. data/test/fixtures/esri_no_results +8 -0
  91. data/test/fixtures/esri_reverse +21 -0
  92. data/test/fixtures/freegeoip_74_200_247_59 +12 -0
  93. data/test/fixtures/freegeoip_no_results +1 -0
  94. data/test/fixtures/geocoder_ca_madison_square_garden +1 -0
  95. data/test/fixtures/geocoder_ca_no_results +1 -0
  96. data/test/fixtures/geocoder_ca_reverse +34 -0
  97. data/test/fixtures/geocoder_us_madison_square_garden +1 -0
  98. data/test/fixtures/geocoder_us_no_results +1 -0
  99. data/test/fixtures/google_garbage +456 -0
  100. data/test/fixtures/google_madison_square_garden +57 -0
  101. data/test/fixtures/google_no_city_data +44 -0
  102. data/test/fixtures/google_no_locality +51 -0
  103. data/test/fixtures/google_no_results +4 -0
  104. data/test/fixtures/google_over_limit +4 -0
  105. data/test/fixtures/mapquest_error +16 -0
  106. data/test/fixtures/mapquest_invalid_api_key +16 -0
  107. data/test/fixtures/mapquest_invalid_request +16 -0
  108. data/test/fixtures/mapquest_madison_square_garden +52 -0
  109. data/test/fixtures/mapquest_no_results +16 -0
  110. data/test/fixtures/maxmind_24_24_24_21 +1 -0
  111. data/test/fixtures/maxmind_24_24_24_22 +1 -0
  112. data/test/fixtures/maxmind_24_24_24_23 +1 -0
  113. data/test/fixtures/maxmind_24_24_24_24 +1 -0
  114. data/test/fixtures/maxmind_74_200_247_59 +1 -0
  115. data/test/fixtures/maxmind_invalid_key +1 -0
  116. data/test/fixtures/maxmind_no_results +1 -0
  117. data/test/fixtures/nominatim_madison_square_garden +150 -0
  118. data/test/fixtures/nominatim_no_results +1 -0
  119. data/test/fixtures/ovi_madison_square_garden +72 -0
  120. data/test/fixtures/ovi_no_results +8 -0
  121. data/test/fixtures/yahoo_error +1 -0
  122. data/test/fixtures/yahoo_invalid_key +2 -0
  123. data/test/fixtures/yahoo_madison_square_garden +52 -0
  124. data/test/fixtures/yahoo_no_results +10 -0
  125. data/test/fixtures/yahoo_over_limit +2 -0
  126. data/test/fixtures/yandex_invalid_key +1 -0
  127. data/test/fixtures/yandex_kremlin +48 -0
  128. data/test/fixtures/yandex_no_city_and_town +112 -0
  129. data/test/fixtures/yandex_no_results +16 -0
  130. data/test/geocoder_test.rb +59 -0
  131. data/test/https_test.rb +16 -0
  132. data/test/integration/smoke_test.rb +26 -0
  133. data/test/lookup_test.rb +117 -0
  134. data/test/method_aliases_test.rb +25 -0
  135. data/test/mongoid_test.rb +46 -0
  136. data/test/mongoid_test_helper.rb +43 -0
  137. data/test/near_test.rb +61 -0
  138. data/test/oauth_util_test.rb +30 -0
  139. data/test/proxy_test.rb +36 -0
  140. data/test/query_test.rb +52 -0
  141. data/test/request_test.rb +29 -0
  142. data/test/result_test.rb +42 -0
  143. data/test/services_test.rb +393 -0
  144. data/test/test_helper.rb +289 -0
  145. data/test/test_mode_test.rb +59 -0
  146. 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
+