geocoder 1.1.3 → 1.1.6

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 (98) hide show
  1. data/.travis.yml +4 -0
  2. data/{CHANGELOG.rdoc → CHANGELOG.md} +109 -35
  3. data/README.md +645 -0
  4. data/examples/autoexpire_cache.rb +28 -0
  5. data/lib/generators/geocoder/config/templates/initializer.rb +18 -22
  6. data/lib/geocoder/cache.rb +2 -1
  7. data/lib/geocoder/calculations.rb +13 -13
  8. data/lib/geocoder/cli.rb +17 -19
  9. data/lib/geocoder/configuration.rb +68 -42
  10. data/lib/geocoder/configuration_hash.rb +11 -0
  11. data/lib/geocoder/exceptions.rb +3 -0
  12. data/lib/geocoder/lookup.rb +81 -0
  13. data/lib/geocoder/lookups/base.rb +106 -56
  14. data/lib/geocoder/lookups/bing.rb +25 -11
  15. data/lib/geocoder/lookups/freegeoip.rb +11 -7
  16. data/lib/geocoder/lookups/geocoder_ca.rb +17 -9
  17. data/lib/geocoder/lookups/google.rb +29 -8
  18. data/lib/geocoder/lookups/google_premier.rb +20 -11
  19. data/lib/geocoder/lookups/mapquest.rb +29 -19
  20. data/lib/geocoder/lookups/maxmind.rb +72 -0
  21. data/lib/geocoder/lookups/nominatim.rb +21 -13
  22. data/lib/geocoder/lookups/test.rb +12 -6
  23. data/lib/geocoder/lookups/yahoo.rb +63 -13
  24. data/lib/geocoder/lookups/yandex.rb +26 -11
  25. data/lib/geocoder/models/mongoid.rb +1 -2
  26. data/lib/geocoder/query.rb +103 -0
  27. data/lib/geocoder/request.rb +7 -1
  28. data/lib/geocoder/results/base.rb +8 -2
  29. data/lib/geocoder/results/google.rb +6 -0
  30. data/lib/geocoder/results/mapquest.rb +19 -34
  31. data/lib/geocoder/results/maxmind.rb +136 -0
  32. data/lib/geocoder/results/nominatim.rb +14 -3
  33. data/lib/geocoder/results/yahoo.rb +9 -2
  34. data/lib/geocoder/sql.rb +106 -0
  35. data/lib/geocoder/stores/active_record.rb +75 -142
  36. data/lib/geocoder/stores/base.rb +3 -2
  37. data/lib/geocoder/stores/mongo_base.rb +2 -2
  38. data/lib/geocoder/version.rb +1 -1
  39. data/lib/geocoder.rb +11 -89
  40. data/lib/hash_recursive_merge.rb +74 -0
  41. data/lib/oauth_util.rb +112 -0
  42. data/test/active_record_test.rb +15 -0
  43. data/test/cache_test.rb +19 -0
  44. data/test/calculations_test.rb +11 -4
  45. data/test/configuration_test.rb +26 -51
  46. data/test/error_handling_test.rb +15 -11
  47. data/test/fixtures/bing_invalid_key +1 -0
  48. data/test/fixtures/mapquest_madison_square_garden +52 -0
  49. data/test/fixtures/mapquest_no_results +7 -0
  50. data/test/fixtures/maxmind_24_24_24_21 +1 -0
  51. data/test/fixtures/maxmind_24_24_24_22 +1 -0
  52. data/test/fixtures/maxmind_24_24_24_23 +1 -0
  53. data/test/fixtures/maxmind_24_24_24_24 +1 -0
  54. data/test/fixtures/maxmind_74_200_247_59 +1 -0
  55. data/test/fixtures/maxmind_invalid_key +1 -0
  56. data/test/fixtures/maxmind_no_results +1 -0
  57. data/test/fixtures/yahoo_error +1 -0
  58. data/test/fixtures/yahoo_invalid_key +2 -0
  59. data/test/fixtures/yahoo_madison_square_garden +52 -0
  60. data/test/fixtures/yahoo_no_results +10 -0
  61. data/test/fixtures/yahoo_over_limit +2 -0
  62. data/test/https_test.rb +3 -3
  63. data/test/integration/smoke_test.rb +8 -6
  64. data/test/lookup_test.rb +92 -13
  65. data/test/near_test.rb +11 -0
  66. data/test/oauth_util_test.rb +30 -0
  67. data/test/proxy_test.rb +2 -2
  68. data/test/query_test.rb +42 -0
  69. data/test/result_test.rb +4 -3
  70. data/test/services_test.rb +164 -26
  71. data/test/test_helper.rb +93 -112
  72. data/test/test_mode_test.rb +3 -4
  73. metadata +52 -35
  74. data/README.rdoc +0 -552
  75. data/test/fixtures/mapquest_madison_square_garden.json +0 -27
  76. data/test/fixtures/mapquest_no_results.json +0 -1
  77. data/test/fixtures/yahoo_garbage.json +0 -50
  78. data/test/fixtures/yahoo_madison_square_garden.json +0 -46
  79. data/test/fixtures/yahoo_no_results.json +0 -10
  80. data/test/input_handling_test.rb +0 -43
  81. /data/test/fixtures/{bing_madison_square_garden.json → bing_madison_square_garden} +0 -0
  82. /data/test/fixtures/{bing_no_results.json → bing_no_results} +0 -0
  83. /data/test/fixtures/{bing_reverse.json → bing_reverse} +0 -0
  84. /data/test/fixtures/{freegeoip_74_200_247_59.json → freegeoip_74_200_247_59} +0 -0
  85. /data/test/fixtures/{freegeoip_no_results.json → freegeoip_no_results} +0 -0
  86. /data/test/fixtures/{geocoder_ca_madison_square_garden.json → geocoder_ca_madison_square_garden} +0 -0
  87. /data/test/fixtures/{geocoder_ca_no_results.json → geocoder_ca_no_results} +0 -0
  88. /data/test/fixtures/{geocoder_ca_reverse.json → geocoder_ca_reverse} +0 -0
  89. /data/test/fixtures/{google_garbage.json → google_garbage} +0 -0
  90. /data/test/fixtures/{google_madison_square_garden.json → google_madison_square_garden} +0 -0
  91. /data/test/fixtures/{google_no_city_data.json → google_no_city_data} +0 -0
  92. /data/test/fixtures/{google_no_locality.json → google_no_locality} +0 -0
  93. /data/test/fixtures/{google_no_results.json → google_no_results} +0 -0
  94. /data/test/fixtures/{nominatim_madison_square_garden.json → nominatim_madison_square_garden} +0 -0
  95. /data/test/fixtures/{nominatim_no_results.json → nominatim_no_results} +0 -0
  96. /data/test/fixtures/{yandex_invalid_key.json → yandex_invalid_key} +0 -0
  97. /data/test/fixtures/{yandex_kremlin.json → yandex_kremlin} +0 -0
  98. /data/test/fixtures/{yandex_no_results.json → yandex_no_results} +0 -0
@@ -13,8 +13,24 @@ end
13
13
 
14
14
  module Geocoder
15
15
  module Lookup
16
+
16
17
  class Base
17
18
 
19
+ ##
20
+ # Human-readable name of the geocoding API.
21
+ #
22
+ def name
23
+ fail
24
+ end
25
+
26
+ ##
27
+ # Symbol which is used in configuration to refer to this Lookup.
28
+ #
29
+ def handle
30
+ str = self.class.to_s
31
+ str[str.rindex(':')+1..-1].gsub(/([a-z\d]+)([A-Z])/,'\1_\2').downcase.to_sym
32
+ end
33
+
18
34
  ##
19
35
  # Query the geocoding API and return a Geocoder::Result object.
20
36
  # Returns +nil+ on timeout or error.
@@ -23,18 +39,9 @@ module Geocoder
23
39
  # "205.128.54.202") for geocoding, or coordinates (latitude, longitude)
24
40
  # for reverse geocoding. Returns an array of <tt>Geocoder::Result</tt>s.
25
41
  #
26
- def search(query)
27
-
28
- # if coordinates given as string, turn into array
29
- query = query.split(/\s*,\s*/) if coordinates?(query)
30
-
31
- if query.is_a?(Array)
32
- reverse = true
33
- query = query.join(',')
34
- else
35
- reverse = false
36
- end
37
- results(query, reverse).map{ |r|
42
+ def search(query, options = {})
43
+ query = Geocoder::Query.new(query, options) unless query.is_a?(Geocoder::Query)
44
+ results(query).map{ |r|
38
45
  result = result_class.new(r)
39
46
  result.cache_hit = @cache_hit if cache
40
47
  result
@@ -51,16 +58,37 @@ module Geocoder
51
58
  nil
52
59
  end
53
60
 
61
+ ##
62
+ # Array containing string descriptions of keys required by the API.
63
+ # Empty array if keys are optional or not required.
64
+ #
65
+ def required_api_key_parts
66
+ []
67
+ end
68
+
69
+ ##
70
+ # URL to use for querying the geocoding engine.
71
+ #
72
+ def query_url(query)
73
+ fail
74
+ end
54
75
 
55
76
  private # -------------------------------------------------------------
56
77
 
78
+ ##
79
+ # An object with configuration data for this particular lookup.
80
+ #
81
+ def configuration
82
+ Geocoder.config_for_lookup(handle)
83
+ end
84
+
57
85
  ##
58
86
  # Object used to make HTTP requests.
59
87
  #
60
88
  def http_client
61
- protocol = "http#{'s' if Geocoder::Configuration.use_https}"
89
+ protocol = "http#{'s' if configuration.use_https}"
62
90
  proxy_name = "#{protocol}_proxy"
63
- if proxy = Geocoder::Configuration.send(proxy_name)
91
+ if proxy = configuration.send(proxy_name)
64
92
  proxy_url = protocol + '://' + proxy
65
93
  begin
66
94
  uri = URI.parse(proxy_url)
@@ -77,15 +105,28 @@ module Geocoder
77
105
  ##
78
106
  # Geocoder::Result object or nil on timeout or other error.
79
107
  #
80
- def results(query, reverse = false)
108
+ def results(query)
81
109
  fail
82
110
  end
83
111
 
112
+ def query_url_params(query)
113
+ query.options[:params] || {}
114
+ end
115
+
116
+ def url_query_string(query)
117
+ hash_to_query(
118
+ query_url_params(query).reject{ |key,value| value.nil? }
119
+ )
120
+ end
121
+
84
122
  ##
85
- # URL to use for querying the geocoding engine.
123
+ # Key to use for caching a geocoding result. Usually this will be the
124
+ # request URL, but in cases where OAuth is used and the nonce,
125
+ # timestamp, etc varies from one request to another, we need to use
126
+ # something else (like the URL before OAuth encoding).
86
127
  #
87
- def query_url(query, reverse = false)
88
- fail
128
+ def cache_key(query)
129
+ query_url(query)
89
130
  end
90
131
 
91
132
  ##
@@ -100,7 +141,7 @@ module Geocoder
100
141
  # Return false if exception not raised.
101
142
  #
102
143
  def raise_error(error, message = nil)
103
- exceptions = Geocoder::Configuration.always_raise
144
+ exceptions = configuration.always_raise
104
145
  if exceptions == :all or exceptions.include?( error.is_a?(Class) ? error : error.class )
105
146
  raise error, message
106
147
  else
@@ -111,13 +152,13 @@ module Geocoder
111
152
  ##
112
153
  # Returns a parsed search result (Ruby hash).
113
154
  #
114
- def fetch_data(query, reverse = false)
115
- parse_raw_data fetch_raw_data(query, reverse)
155
+ def fetch_data(query)
156
+ parse_raw_data fetch_raw_data(query)
116
157
  rescue SocketError => err
117
158
  raise_error(err) or warn "Geocoding API connection cannot be established."
118
159
  rescue TimeoutError => err
119
160
  raise_error(err) or warn "Geocoding API not responding fast enough " +
120
- "(see Geocoder::Configuration.timeout to set limit)."
161
+ "(use Geocoder.configure(:timeout => ...) to set limit)."
121
162
  end
122
163
 
123
164
  ##
@@ -138,51 +179,60 @@ module Geocoder
138
179
  # Set in configuration but not available for every service.
139
180
  #
140
181
  def protocol
141
- "http" + (Geocoder::Configuration.use_https ? "s" : "")
142
- end
143
-
144
- ##
145
- # Fetches a raw search result (JSON string).
146
- #
147
- def fetch_raw_data(query, reverse = false)
148
- timeout(Geocoder::Configuration.timeout) do
149
- url = query_url(query, reverse)
150
- uri = URI.parse(url)
151
- if cache and body = cache[url]
152
- @cache_hit = true
153
- else
154
- client = http_client.new(uri.host, uri.port)
155
- client.use_ssl = true if Geocoder::Configuration.use_https
156
- response = client.get(uri.request_uri, Geocoder::Configuration.http_headers)
157
- body = response.body
158
- if cache and (200..399).include?(response.code.to_i)
159
- cache[url] = body
160
- end
161
- @cache_hit = false
162
- end
163
- body
164
- end
182
+ "http" + (configuration.use_https ? "s" : "")
165
183
  end
166
184
 
167
185
  ##
168
- # The working Cache object.
186
+ # Fetch a raw geocoding result (JSON string).
187
+ # The result might or might not be cached.
169
188
  #
170
- def cache
171
- Geocoder.cache
189
+ def fetch_raw_data(query)
190
+ key = cache_key(query)
191
+ if cache and body = cache[key]
192
+ @cache_hit = true
193
+ else
194
+ check_api_key_configuration!(query)
195
+ response = make_api_request(query)
196
+ body = response.body
197
+ if cache and (200..399).include?(response.code.to_i)
198
+ cache[key] = body
199
+ end
200
+ @cache_hit = false
201
+ end
202
+ body
172
203
  end
173
204
 
174
205
  ##
175
- # Is the given string a loopback IP address?
176
- #
177
- def loopback_address?(ip)
178
- !!(ip == "0.0.0.0" or ip.to_s.match(/^127/))
206
+ # Make an HTTP(S) request to a geocoding API and
207
+ # return the response object.
208
+ #
209
+ def make_api_request(query)
210
+ timeout(configuration.timeout) do
211
+ uri = URI.parse(query_url(query))
212
+ client = http_client.new(uri.host, uri.port)
213
+ client.use_ssl = true if configuration.use_https
214
+ client.get(uri.request_uri, configuration.http_headers)
215
+ end
216
+ end
217
+
218
+ def check_api_key_configuration!(query)
219
+ key_parts = query.lookup.required_api_key_parts
220
+ if key_parts.size > Array(configuration.api_key).size
221
+ parts_string = key_parts.size == 1 ? key_parts.first : key_parts
222
+ raise Geocoder::ConfigurationError,
223
+ "The #{query.lookup.name} API requires a key to be configured: " +
224
+ parts_string.inspect
225
+ end
179
226
  end
180
227
 
181
228
  ##
182
- # Does the given string look like latitude/longitude coordinates?
229
+ # The working Cache object.
183
230
  #
184
- def coordinates?(value)
185
- value.is_a?(String) and !!value.to_s.match(/^-?[0-9\.]+, *-?[0-9\.]+$/)
231
+ def cache
232
+ if @cache.nil? and store = configuration.cache
233
+ @cache = Cache.new(store, configuration.cache_prefix)
234
+ end
235
+ @cache
186
236
  end
187
237
 
188
238
  ##
@@ -4,30 +4,44 @@ require "geocoder/results/bing"
4
4
  module Geocoder::Lookup
5
5
  class Bing < Base
6
6
 
7
+ def name
8
+ "Bing"
9
+ end
10
+
7
11
  def map_link_url(coordinates)
8
12
  "http://www.bing.com/maps/default.aspx?cp=#{coordinates.join('~')}"
9
13
  end
10
14
 
15
+ def required_api_key_parts
16
+ ["key"]
17
+ end
18
+
19
+ def query_url(query)
20
+ "#{protocol}://dev.virtualearth.net/REST/v1/Locations" +
21
+ (query.reverse_geocode? ? "/#{query.sanitized_text}?" : "?") +
22
+ url_query_string(query)
23
+ end
24
+
11
25
  private # ---------------------------------------------------------------
12
26
 
13
- def results(query, reverse = false)
14
- return [] unless doc = fetch_data(query, reverse)
27
+ def results(query)
28
+ return [] unless doc = fetch_data(query)
15
29
 
16
- if doc['statusDescription'] == "OK"
30
+ if doc['statusCode'] == 200
17
31
  return doc['resourceSets'].first['estimatedTotal'] > 0 ? doc['resourceSets'].first['resources'] : []
32
+ elsif doc['statusCode'] == 401 and doc["authenticationResultCode"] == "InvalidCredentials"
33
+ raise_error(Geocoder::InvalidApiKey) || warn("Invalid Bing API key.")
18
34
  else
19
35
  warn "Bing Geocoding API error: #{doc['statusCode']} (#{doc['statusDescription']})."
20
- return []
21
36
  end
37
+ return []
22
38
  end
23
39
 
24
- def query_url(query, reverse = false)
25
- params = {:key => Geocoder::Configuration.api_key}
26
- params[:query] = query unless reverse
27
-
28
- base_url = "http://dev.virtualearth.net/REST/v1/Locations"
29
- url_tail = reverse ? "/#{query}?" : "?"
30
- base_url + url_tail + hash_to_query(params)
40
+ def query_url_params(query)
41
+ {
42
+ :key => configuration.api_key,
43
+ :query => query.reverse_geocode? ? nil : query.sanitized_text
44
+ }.merge(super)
31
45
  end
32
46
  end
33
47
  end
@@ -4,17 +4,25 @@ require 'geocoder/results/freegeoip'
4
4
  module Geocoder::Lookup
5
5
  class Freegeoip < Base
6
6
 
7
+ def name
8
+ "FreeGeoIP"
9
+ end
10
+
11
+ def query_url(query)
12
+ "#{protocol}://freegeoip.net/json/#{query.sanitized_text}"
13
+ end
14
+
7
15
  private # ---------------------------------------------------------------
8
16
 
9
17
  def parse_raw_data(raw_data)
10
18
  raw_data.match(/^<html><title>404/) ? nil : super(raw_data)
11
19
  end
12
20
 
13
- def results(query, reverse = false)
21
+ def results(query)
14
22
  # don't look up a loopback address, just return the stored result
15
- return [reserved_result(query)] if loopback_address?(query)
23
+ return [reserved_result(query.text)] if query.loopback_ip_address?
16
24
  begin
17
- return (doc = fetch_data(query, reverse)) ? [doc] : []
25
+ return (doc = fetch_data(query)) ? [doc] : []
18
26
  rescue StandardError => err # Freegeoip.net returns HTML on bad request
19
27
  raise_error(err)
20
28
  return []
@@ -35,9 +43,5 @@ module Geocoder::Lookup
35
43
  "country_code" => "RD"
36
44
  }
37
45
  end
38
-
39
- def query_url(query, reverse = false)
40
- "http://freegeoip.net/json/#{query}"
41
- end
42
46
  end
43
47
  end
@@ -4,10 +4,18 @@ require "geocoder/results/geocoder_ca"
4
4
  module Geocoder::Lookup
5
5
  class GeocoderCa < Base
6
6
 
7
+ def name
8
+ "Geocoder.ca"
9
+ end
10
+
11
+ def query_url(query)
12
+ "#{protocol}://geocoder.ca/?" + url_query_string(query)
13
+ end
14
+
7
15
  private # ---------------------------------------------------------------
8
16
 
9
- def results(query, reverse = false)
10
- return [] unless doc = fetch_data(query, reverse)
17
+ def results(query)
18
+ return [] unless doc = fetch_data(query)
11
19
  if doc['error'].nil?
12
20
  return [doc]
13
21
  elsif doc['error']['code'] == "005"
@@ -18,24 +26,24 @@ module Geocoder::Lookup
18
26
  return []
19
27
  end
20
28
 
21
- def query_url(query, reverse = false)
29
+ def query_url_params(query)
22
30
  params = {
23
31
  :geoit => "xml",
24
32
  :jsonp => 1,
25
33
  :callback => "test",
26
- :auth => Geocoder::Configuration.api_key
27
- }
28
- if reverse
29
- lat,lon = query.split(',')
34
+ :auth => configuration.api_key
35
+ }.merge(super)
36
+ if query.reverse_geocode?
37
+ lat,lon = query.coordinates
30
38
  params[:latt] = lat
31
39
  params[:longt] = lon
32
40
  params[:corner] = 1
33
41
  params[:reverse] = 1
34
42
  else
35
- params[:locate] = query
43
+ params[:locate] = query.sanitized_text
36
44
  params[:showpostal] = 1
37
45
  end
38
- "http://geocoder.ca/?" + hash_to_query(params)
46
+ params
39
47
  end
40
48
 
41
49
  def parse_raw_data(raw_data)
@@ -4,14 +4,22 @@ require "geocoder/results/google"
4
4
  module Geocoder::Lookup
5
5
  class Google < Base
6
6
 
7
+ def name
8
+ "Google"
9
+ end
10
+
7
11
  def map_link_url(coordinates)
8
12
  "http://maps.google.com/maps?q=#{coordinates.join(',')}"
9
13
  end
10
14
 
15
+ def query_url(query)
16
+ "#{protocol}://maps.googleapis.com/maps/api/geocode/json?" + url_query_string(query)
17
+ end
18
+
11
19
  private # ---------------------------------------------------------------
12
20
 
13
- def results(query, reverse = false)
14
- return [] unless doc = fetch_data(query, reverse)
21
+ def results(query)
22
+ return [] unless doc = fetch_data(query)
15
23
  case doc['status']; when "OK" # OK status implies >0 results
16
24
  return doc['results']
17
25
  when "OVER_QUERY_LIMIT"
@@ -27,15 +35,28 @@ module Geocoder::Lookup
27
35
  return []
28
36
  end
29
37
 
30
- def query_url(query, reverse = false)
38
+ def query_url_google_params(query)
31
39
  params = {
32
- (reverse ? :latlng : :address) => query,
40
+ (query.reverse_geocode? ? :latlng : :address) => query.sanitized_text,
33
41
  :sensor => "false",
34
- :language => Geocoder::Configuration.language,
35
- :key => Geocoder::Configuration.api_key
42
+ :language => configuration.language
36
43
  }
37
- "#{protocol}://maps.googleapis.com/maps/api/geocode/json?" + hash_to_query(params)
44
+ unless (bounds = query.options[:bounds]).nil?
45
+ params[:bounds] = bounds.map{ |point| "%f,%f" % point }.join('|')
46
+ end
47
+ unless (region = query.options[:region]).nil?
48
+ params[:region] = region
49
+ end
50
+ unless (components = query.options[:components]).nil?
51
+ params[:components] = components.is_a?(Array) ? components.join("|") : components
52
+ end
53
+ params
54
+ end
55
+
56
+ def query_url_params(query)
57
+ query_url_google_params(query).merge(
58
+ :key => configuration.api_key
59
+ ).merge(super)
38
60
  end
39
61
  end
40
62
  end
41
-
@@ -6,22 +6,31 @@ require 'geocoder/results/google_premier'
6
6
  module Geocoder::Lookup
7
7
  class GooglePremier < Google
8
8
 
9
- private # ---------------------------------------------------------------
9
+ def name
10
+ "Google Premier"
11
+ end
12
+
13
+ def required_api_key_parts
14
+ ["private key", "client", "channel"]
15
+ end
10
16
 
11
- def query_url(query, reverse = false)
12
- params = {
13
- (reverse ? :latlng : :address) => query,
14
- :sensor => 'false',
15
- :language => Geocoder::Configuration.language,
16
- :client => Geocoder::Configuration.api_key[1],
17
- :channel => Geocoder::Configuration.api_key[2]
18
- }.reject{ |key, value| value.nil? }
19
- path = "/maps/api/geocode/json?#{hash_to_query(params)}"
17
+ def query_url(query)
18
+ path = "/maps/api/geocode/json?" + url_query_string(query)
20
19
  "#{protocol}://maps.googleapis.com#{path}&signature=#{sign(path)}"
21
20
  end
22
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
+
23
32
  def sign(string)
24
- raw_private_key = url_safe_base64_decode(Geocoder::Configuration.api_key[0])
33
+ raw_private_key = url_safe_base64_decode(configuration.api_key[0])
25
34
  digest = OpenSSL::Digest::Digest.new('sha1')
26
35
  raw_signature = OpenSSL::HMAC.digest(digest, raw_private_key, string)
27
36
  url_safe_base64_encode(raw_signature)
@@ -1,33 +1,43 @@
1
+ require 'cgi'
1
2
  require 'geocoder/lookups/base'
2
3
  require "geocoder/results/mapquest"
3
4
 
4
5
  module Geocoder::Lookup
5
6
  class Mapquest < Base
6
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
+ key = configuration.api_key
18
+ domain = key ? "www" : "open"
19
+ url = "#{protocol}://#{domain}.mapquestapi.com/geocoding/v1/#{search_type(query)}?"
20
+ url + url_query_string(query)
21
+ end
22
+
7
23
  private # ---------------------------------------------------------------
8
24
 
9
- def results(query, reverse = false)
10
- return [] unless doc = fetch_data(query, reverse)
11
- doc.is_a?(Array) ? doc : [doc]
25
+ def search_type(query)
26
+ query.reverse_geocode? ? "reverse" : "address"
12
27
  end
13
28
 
14
- def query_url(query, reverse = false)
15
- params = {
16
- :format => "json",
17
- :polygon => "1",
18
- :addressdetails => "1",
19
- :"accept-language" => Geocoder::Configuration.language
20
- }
21
- if (reverse)
22
- method = 'reverse'
23
- parts = query.split(/\s*,\s*/);
24
- params[:lat] = parts[0]
25
- params[:lon] = parts[1]
26
- else
27
- method = 'search'
28
- params[:q] = query
29
+ def query_url_params(query)
30
+ params = { :location => query.sanitized_text }
31
+ if key = configuration.api_key
32
+ params[:key] = CGI.unescape(key)
29
33
  end
30
- "http://open.mapquestapi.com/#{method}?" + hash_to_query(params)
34
+ params.merge(super)
35
+ end
36
+
37
+ def results(query)
38
+ return [] unless doc = fetch_data(query)
39
+ doc["results"][0]['locations']
31
40
  end
41
+
32
42
  end
33
43
  end
@@ -0,0 +1,72 @@
1
+ require 'geocoder/lookups/base'
2
+ require 'geocoder/results/maxmind'
3
+ require 'csv'
4
+
5
+ module Geocoder::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
+ def service_code
19
+ if s = configuration[:service] and services.keys.include?(s)
20
+ services[s]
21
+ else
22
+ raise(
23
+ Geocoder::ConfigurationError,
24
+ "When using MaxMind you MUST specify a service name: " +
25
+ "Geocoder.configure(:maxmind => {:service => ...}), " +
26
+ "where '...' is one of: #{services.keys.inspect}"
27
+ )
28
+ end
29
+ end
30
+
31
+ ##
32
+ # Service names mapped to code used in URL.
33
+ #
34
+ def services
35
+ {
36
+ :country => "a",
37
+ :city => "b",
38
+ :city_isp_org => "f",
39
+ :omni => "e"
40
+ }
41
+ end
42
+
43
+ def results(query)
44
+ # don't look up a loopback address, just return the stored result
45
+ return [reserved_result] if query.loopback_ip_address?
46
+ doc = fetch_data(query)
47
+ if doc and doc.is_a?(Array)
48
+ if doc.last.nil?
49
+ return [doc]
50
+ elsif doc.last == "INVALID_LICENSE_KEY"
51
+ raise_error(Geocoder::InvalidApiKey) || warn("Invalid MaxMind API key.")
52
+ end
53
+ end
54
+ return []
55
+ end
56
+
57
+ def parse_raw_data(raw_data)
58
+ CSV.parse_line raw_data
59
+ end
60
+
61
+ def reserved_result
62
+ ",,,,0,0,0,0,,,"
63
+ end
64
+
65
+ def query_url_params(query)
66
+ {
67
+ :l => configuration.api_key,
68
+ :i => query.sanitized_text
69
+ }.merge(super)
70
+ end
71
+ end
72
+ end
@@ -4,34 +4,42 @@ require "geocoder/results/nominatim"
4
4
  module Geocoder::Lookup
5
5
  class Nominatim < Base
6
6
 
7
+ def name
8
+ "Nominatim"
9
+ end
10
+
7
11
  def map_link_url(coordinates)
8
12
  "http://www.openstreetmap.org/?lat=#{coordinates[0]}&lon=#{coordinates[1]}&zoom=15&layers=M"
9
13
  end
10
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
+
11
21
  private # ---------------------------------------------------------------
12
22
 
13
- def results(query, reverse = false)
14
- return [] unless doc = fetch_data(query, reverse)
23
+ def results(query)
24
+ return [] unless doc = fetch_data(query)
15
25
  doc.is_a?(Array) ? doc : [doc]
16
26
  end
17
27
 
18
- def query_url(query, reverse = false)
28
+ def query_url_params(query)
19
29
  params = {
20
30
  :format => "json",
21
31
  :polygon => "1",
22
32
  :addressdetails => "1",
23
- :"accept-language" => Geocoder::Configuration.language
24
- }
25
- if (reverse)
26
- method = 'reverse'
27
- parts = query.split(/\s*,\s*/);
28
- params[:lat] = parts[0]
29
- params[:lon] = parts[1]
33
+ :"accept-language" => configuration.language
34
+ }.merge(super)
35
+ if query.reverse_geocode?
36
+ lat,lon = query.coordinates
37
+ params[:lat] = lat
38
+ params[:lon] = lon
30
39
  else
31
- method = 'search'
32
- params[:q] = query
40
+ params[:q] = query.sanitized_text
33
41
  end
34
- "http://nominatim.openstreetmap.org/#{method}?" + hash_to_query(params)
42
+ params
35
43
  end
36
44
  end
37
45
  end