geocoder 1.1.4 → 1.1.8

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 (110) hide show
  1. data/.travis.yml +4 -0
  2. data/{CHANGELOG.rdoc → CHANGELOG.md} +120 -39
  3. data/README.md +200 -54
  4. data/examples/autoexpire_cache_dalli.rb +62 -0
  5. data/examples/{autoexpire_cache.rb → autoexpire_cache_redis.rb} +4 -6
  6. data/lib/generators/geocoder/config/templates/initializer.rb +19 -23
  7. data/lib/geocoder/cache.rb +6 -1
  8. data/lib/geocoder/calculations.rb +45 -13
  9. data/lib/geocoder/cli.rb +10 -11
  10. data/lib/geocoder/configuration.rb +67 -43
  11. data/lib/geocoder/configuration_hash.rb +11 -0
  12. data/lib/geocoder/exceptions.rb +3 -0
  13. data/lib/geocoder/lookup.rb +3 -1
  14. data/lib/geocoder/lookups/base.rb +89 -27
  15. data/lib/geocoder/lookups/bing.rb +21 -11
  16. data/lib/geocoder/lookups/esri.rb +48 -0
  17. data/lib/geocoder/lookups/freegeoip.rb +8 -4
  18. data/lib/geocoder/lookups/geocoder_ca.rb +11 -7
  19. data/lib/geocoder/lookups/google.rb +18 -9
  20. data/lib/geocoder/lookups/google_premier.rb +16 -8
  21. data/lib/geocoder/lookups/mapquest.rb +33 -5
  22. data/lib/geocoder/lookups/maxmind.rb +88 -0
  23. data/lib/geocoder/lookups/nominatim.rb +13 -8
  24. data/lib/geocoder/lookups/ovi.rb +52 -0
  25. data/lib/geocoder/lookups/test.rb +10 -0
  26. data/lib/geocoder/lookups/yahoo.rb +51 -38
  27. data/lib/geocoder/lookups/yandex.rb +17 -9
  28. data/lib/geocoder/models/mongoid.rb +1 -1
  29. data/lib/geocoder/query.rb +20 -5
  30. data/lib/geocoder/request.rb +7 -1
  31. data/lib/geocoder/results/base.rb +8 -2
  32. data/lib/geocoder/results/esri.rb +51 -0
  33. data/lib/geocoder/results/google.rb +34 -0
  34. data/lib/geocoder/results/mapquest.rb +46 -2
  35. data/lib/geocoder/results/maxmind.rb +135 -0
  36. data/lib/geocoder/results/nominatim.rb +0 -10
  37. data/lib/geocoder/results/ovi.rb +62 -0
  38. data/lib/geocoder/results/test.rb +2 -1
  39. data/lib/geocoder/results/yahoo.rb +9 -2
  40. data/lib/geocoder/results/yandex.rb +12 -2
  41. data/lib/geocoder/sql.rb +6 -4
  42. data/lib/geocoder/stores/active_record.rb +57 -30
  43. data/lib/geocoder/stores/base.rb +3 -2
  44. data/lib/geocoder/stores/mongo_base.rb +2 -1
  45. data/lib/geocoder/version.rb +1 -1
  46. data/lib/geocoder.rb +2 -4
  47. data/lib/hash_recursive_merge.rb +74 -0
  48. data/lib/oauth_util.rb +112 -0
  49. data/test/cache_test.rb +19 -0
  50. data/test/calculations_test.rb +10 -4
  51. data/test/configuration_test.rb +26 -51
  52. data/test/error_handling_test.rb +8 -4
  53. data/test/fixtures/bing_invalid_key +1 -0
  54. data/test/fixtures/esri_madison_square_garden +59 -0
  55. data/test/fixtures/esri_no_results +8 -0
  56. data/test/fixtures/esri_reverse +21 -0
  57. data/test/fixtures/mapquest_madison_square_garden +52 -0
  58. data/test/fixtures/mapquest_no_results +7 -0
  59. data/test/fixtures/maxmind_24_24_24_21 +1 -0
  60. data/test/fixtures/maxmind_24_24_24_22 +1 -0
  61. data/test/fixtures/maxmind_24_24_24_23 +1 -0
  62. data/test/fixtures/maxmind_24_24_24_24 +1 -0
  63. data/test/fixtures/maxmind_74_200_247_59 +1 -0
  64. data/test/fixtures/maxmind_invalid_key +1 -0
  65. data/test/fixtures/maxmind_no_results +1 -0
  66. data/test/fixtures/ovi_madison_square_garden +72 -0
  67. data/test/fixtures/ovi_no_results +8 -0
  68. data/test/fixtures/yahoo_error +1 -0
  69. data/test/fixtures/yahoo_invalid_key +2 -0
  70. data/test/fixtures/yahoo_madison_square_garden +52 -0
  71. data/test/fixtures/yahoo_no_results +10 -0
  72. data/test/fixtures/yahoo_over_limit +2 -0
  73. data/test/fixtures/yandex_no_city_and_town +112 -0
  74. data/test/https_test.rb +3 -3
  75. data/test/integration/smoke_test.rb +2 -2
  76. data/test/lookup_test.rb +84 -11
  77. data/test/near_test.rb +43 -0
  78. data/test/oauth_util_test.rb +30 -0
  79. data/test/proxy_test.rb +2 -2
  80. data/test/query_test.rb +13 -0
  81. data/test/request_test.rb +29 -0
  82. data/test/result_test.rb +12 -2
  83. data/test/services_test.rb +208 -57
  84. data/test/test_helper.rb +66 -100
  85. data/test/test_mode_test.rb +35 -26
  86. metadata +58 -35
  87. data/test/fixtures/mapquest_madison_square_garden.json +0 -27
  88. data/test/fixtures/mapquest_no_results.json +0 -1
  89. data/test/fixtures/yahoo_madison_square_garden.json +0 -52
  90. data/test/fixtures/yahoo_no_results.json +0 -10
  91. data/test/fixtures/yahoo_v1_madison_square_garden.json +0 -46
  92. data/test/fixtures/yahoo_v1_no_results.json +0 -10
  93. /data/test/fixtures/{bing_madison_square_garden.json → bing_madison_square_garden} +0 -0
  94. /data/test/fixtures/{bing_no_results.json → bing_no_results} +0 -0
  95. /data/test/fixtures/{bing_reverse.json → bing_reverse} +0 -0
  96. /data/test/fixtures/{freegeoip_74_200_247_59.json → freegeoip_74_200_247_59} +0 -0
  97. /data/test/fixtures/{freegeoip_no_results.json → freegeoip_no_results} +0 -0
  98. /data/test/fixtures/{geocoder_ca_madison_square_garden.json → geocoder_ca_madison_square_garden} +0 -0
  99. /data/test/fixtures/{geocoder_ca_no_results.json → geocoder_ca_no_results} +0 -0
  100. /data/test/fixtures/{geocoder_ca_reverse.json → geocoder_ca_reverse} +0 -0
  101. /data/test/fixtures/{google_garbage.json → google_garbage} +0 -0
  102. /data/test/fixtures/{google_madison_square_garden.json → google_madison_square_garden} +0 -0
  103. /data/test/fixtures/{google_no_city_data.json → google_no_city_data} +0 -0
  104. /data/test/fixtures/{google_no_locality.json → google_no_locality} +0 -0
  105. /data/test/fixtures/{google_no_results.json → google_no_results} +0 -0
  106. /data/test/fixtures/{nominatim_madison_square_garden.json → nominatim_madison_square_garden} +0 -0
  107. /data/test/fixtures/{nominatim_no_results.json → nominatim_no_results} +0 -0
  108. /data/test/fixtures/{yandex_invalid_key.json → yandex_invalid_key} +0 -0
  109. /data/test/fixtures/{yandex_kremlin.json → yandex_kremlin} +0 -0
  110. /data/test/fixtures/{yandex_no_results.json → yandex_no_results} +0 -0
@@ -21,6 +21,11 @@ module Geocoder
21
21
  #
22
22
  KM_IN_MI = 0.621371192
23
23
 
24
+ ##
25
+ # Conversion factor: multiply by nautical miles to get miles.
26
+ #
27
+ KM_IN_NM = 0.539957
28
+
24
29
  # Not a number constant
25
30
  NAN = defined?(::Float::NAN) ? ::Float::NAN : 0 / 0.0
26
31
 
@@ -40,7 +45,7 @@ module Geocoder
40
45
  # Distance spanned by one degree of latitude in the given units.
41
46
  #
42
47
  def latitude_degree_distance(units = nil)
43
- units ||= Geocoder::Configuration.units
48
+ units ||= Geocoder.config.units
44
49
  2 * Math::PI * earth_radius(units) / 360
45
50
  end
46
51
 
@@ -49,7 +54,7 @@ module Geocoder
49
54
  # This ranges from around 69 miles at the equator to zero at the poles.
50
55
  #
51
56
  def longitude_degree_distance(latitude, units = nil)
52
- units ||= Geocoder::Configuration.units
57
+ units ||= Geocoder.config.units
53
58
  latitude_degree_distance(units) * Math.cos(to_radians(latitude))
54
59
  end
55
60
 
@@ -67,12 +72,12 @@ module Geocoder
67
72
  # The options hash supports:
68
73
  #
69
74
  # * <tt>:units</tt> - <tt>:mi</tt> or <tt>:km</tt>
70
- # See Geocoder::Configuration to know how configure default units.
75
+ # Use Geocoder.configure(:units => ...) to configure default units.
71
76
  #
72
77
  def distance_between(point1, point2, options = {})
73
78
 
74
79
  # set default options
75
- options[:units] ||= Geocoder::Configuration.units
80
+ options[:units] ||= Geocoder.config.units
76
81
 
77
82
  # convert to coordinate arrays
78
83
  point1 = extract_coordinates(point1)
@@ -103,14 +108,14 @@ module Geocoder
103
108
  # the spherical method is "correct" in that it returns the shortest path
104
109
  # (one along a great circle) but the linear method is less confusing
105
110
  # (returns due east or west when given two points with the same latitude).
106
- # See Geocoder::Configuration to know how configure default method.
111
+ # Use Geocoder.configure(:distances => ...) to configure calculation method.
107
112
  #
108
113
  # Based on: http://www.movable-type.co.uk/scripts/latlong.html
109
114
  #
110
115
  def bearing_between(point1, point2, options = {})
111
116
 
112
117
  # set default options
113
- options[:method] ||= Geocoder::Configuration.distances
118
+ options[:method] ||= Geocoder.config.distances
114
119
  options[:method] = :linear unless options[:method] == :spherical
115
120
 
116
121
  # convert to coordinate arrays
@@ -197,12 +202,12 @@ module Geocoder
197
202
  # ways of specifying the point. Also accepts an options hash:
198
203
  #
199
204
  # * <tt>:units</tt> - <tt>:mi</tt> or <tt>:km</tt>.
200
- # See Geocoder::Configuration to know how configure default units.
205
+ # Use Geocoder.configure(:units => ...) to configure default units.
201
206
  #
202
207
  def bounding_box(point, radius, options = {})
203
208
  lat,lon = extract_coordinates(point)
204
209
  radius = radius.to_f
205
- units = options[:units] || Geocoder::Configuration.units
210
+ units = options[:units] || Geocoder.config.units
206
211
  [
207
212
  lat - (radius / latitude_degree_distance(units)),
208
213
  lon - (radius / longitude_degree_distance(lat, units)),
@@ -240,12 +245,12 @@ module Geocoder
240
245
  end
241
246
 
242
247
  def distance_to_radians(distance, units = nil)
243
- units ||= Geocoder::Configuration.units
248
+ units ||= Geocoder.config.units
244
249
  distance.to_f / earth_radius(units)
245
250
  end
246
251
 
247
252
  def radians_to_distance(radians, units = nil)
248
- units ||= Geocoder::Configuration.units
253
+ units ||= Geocoder.config.units
249
254
  radians * earth_radius(units)
250
255
  end
251
256
 
@@ -263,13 +268,24 @@ module Geocoder
263
268
  km * km_in_mi
264
269
  end
265
270
 
271
+ ##
272
+ # Convert kilometers to nautical miles.
273
+ #
274
+ def to_nautical_miles(km)
275
+ km * km_in_nm
276
+ end
277
+
266
278
  ##
267
279
  # Radius of the Earth in the given units (:mi or :km).
268
- # See Geocoder::Configuration to know how configure default units.
280
+ # Use Geocoder.configure(:units => ...) to configure default units.
269
281
  #
270
282
  def earth_radius(units = nil)
271
- units ||= Geocoder::Configuration.units
272
- units == :km ? EARTH_RADIUS : to_miles(EARTH_RADIUS)
283
+ units ||= Geocoder.config.units
284
+ case units
285
+ when :km; EARTH_RADIUS
286
+ when :mi; to_miles(EARTH_RADIUS)
287
+ when :nm; to_nautical_miles(EARTH_RADIUS)
288
+ end
273
289
  end
274
290
 
275
291
  ##
@@ -279,6 +295,15 @@ module Geocoder
279
295
  KM_IN_MI
280
296
  end
281
297
 
298
+ ##
299
+ # Conversion factor: km to nm.
300
+ #
301
+ def km_in_nm
302
+ KM_IN_NM
303
+ end
304
+
305
+
306
+
282
307
  ##
283
308
  # Conversion factor: mi to km.
284
309
  #
@@ -286,6 +311,13 @@ module Geocoder
286
311
  1.0 / KM_IN_MI
287
312
  end
288
313
 
314
+ ##
315
+ # Conversion factor: nm to km.
316
+ #
317
+ def nm_in_km
318
+ 1.0 / KM_IN_NM
319
+ end
320
+
289
321
  ##
290
322
  # Takes an object which is a [lat,lon] array, a geocodable string,
291
323
  # or an object that implements +to_coordinates+ and returns a
data/lib/geocoder/cli.rb CHANGED
@@ -13,34 +13,33 @@ module Geocoder
13
13
  opts.separator "\nOptions: "
14
14
 
15
15
  opts.on("-k <key>", "--key <key>",
16
- "Key for geocoding API (optional for most). For Google Premier use 'key client channel' separated by spaces") do |key|
17
- premier_key = key.split(' ')
18
- if premier_key.length == 3
19
- Geocoder::Configuration.api_key = premier_key
16
+ "Key for geocoding API (usually optional). Enclose multi-part keys in quotes and separate parts by spaces") do |key|
17
+ if (key_parts = key.split(/\s+/)).size > 1
18
+ Geocoder.configure(:api_key => key_parts)
20
19
  else
21
- Geocoder::Configuration.api_key = key
20
+ Geocoder.configure(:api_key => key)
22
21
  end
23
22
  end
24
23
 
25
24
  opts.on("-l <language>", "--language <language>",
26
25
  "Language of output (see API docs for valid choices)") do |language|
27
- Geocoder::Configuration.language = language
26
+ Geocoder.configure(:language => language)
28
27
  end
29
28
 
30
29
  opts.on("-p <proxy>", "--proxy <proxy>",
31
30
  "HTTP proxy server to use (user:pass@host:port)") do |proxy|
32
- Geocoder::Configuration.http_proxy = proxy
31
+ Geocoder.configure(:http_proxy => proxy)
33
32
  end
34
33
 
35
34
  opts.on("-s <service>", Geocoder::Lookup.all_services_except_test, "--service <service>",
36
35
  "Geocoding service: #{Geocoder::Lookup.all_services_except_test * ', '}") do |service|
37
- Geocoder::Configuration.lookup = service.to_sym
38
- Geocoder::Configuration.ip_lookup = service.to_sym
36
+ Geocoder.configure(:lookup => service.to_sym)
37
+ Geocoder.configure(:ip_lookup => service.to_sym)
39
38
  end
40
39
 
41
40
  opts.on("-t <seconds>", "--timeout <seconds>",
42
41
  "Maximum number of seconds to wait for API response") do |timeout|
43
- Geocoder::Configuration.timeout = timeout.to_i
42
+ Geocoder.configure(:timeout => timeout.to_i)
44
43
  end
45
44
 
46
45
  opts.on("-j", "--json", "Print API's raw JSON response") do
@@ -80,7 +79,7 @@ module Geocoder
80
79
 
81
80
  if show_url
82
81
  q = Geocoder::Query.new(query)
83
- out << q.lookup.send(:query_url, q) + "\n"
82
+ out << q.url + "\n"
84
83
  exit 0
85
84
  end
86
85
 
@@ -1,39 +1,49 @@
1
1
  require 'singleton'
2
+ require 'geocoder/configuration_hash'
2
3
 
3
4
  module Geocoder
4
5
 
5
6
  ##
6
- # Provides convenient access to the Configuration singleton.
7
+ # Configuration options should be set by passing a hash:
7
8
  #
8
- def self.configure(&block)
9
+ # Geocoder.configure(
10
+ # :timeout => 5,
11
+ # :lookup => :yandex,
12
+ # :api_key => "2a9fsa983jaslfj982fjasd",
13
+ # :units => :km
14
+ # )
15
+ #
16
+ def self.configure(options = nil, &block)
9
17
  if block_given?
18
+ warn "WARNING: Passing a block to Geocoder.configure is DEPRECATED. Please pass a hash instead (eg: Geocoder.configure(:units => ..., :api_key => ...))."
10
19
  block.call(Configuration.instance)
20
+ elsif !options.nil?
21
+ Configuration.instance.configure(options)
11
22
  else
23
+ warn "WARNING: Use of Geocoder.configure to read or write single config options is DEPRECATED. To write to the config please pass a hash (eg: Geocoder.configure(:units => ...)). To read config options please use the Geocoder.config object (eg: Geocoder.config.units)."
12
24
  Configuration.instance
13
25
  end
14
26
  end
15
27
 
16
28
  ##
17
- # This class handles geocoder Geocoder configuration
18
- # (geocoding service provider, caching, units of measurement, etc).
19
- # Configuration can be done in two ways:
20
- #
21
- # 1) Using Geocoder.configure and passing a block
22
- # (useful for configuring multiple things at once):
23
- #
24
- # Geocoder.configure do |config|
25
- # config.timeout = 5
26
- # config.lookup = :yahoo
27
- # config.api_key = "2a9fsa983jaslfj982fjasd"
28
- # config.units = :km
29
- # end
29
+ # Read-only access to the singleton's config data.
30
30
  #
31
- # 2) Using the Geocoder::Configuration singleton directly:
32
- #
33
- # Geocoder::Configuration.language = 'pt-BR'
34
- #
35
- # Default values are defined in Configuration#set_defaults.
31
+ def self.config
32
+ Configuration.instance.data
33
+ end
34
+
35
+ ##
36
+ # Read-only access to lookup-specific config data.
36
37
  #
38
+ def self.config_for_lookup(lookup_name)
39
+ data = config.clone
40
+ data.reject!{ |key,value| !Configuration::OPTIONS.include?(key) }
41
+ if config.has_key?(lookup_name)
42
+ data.merge!(config[lookup_name])
43
+ end
44
+ data
45
+ end
46
+
37
47
  class Configuration
38
48
  include Singleton
39
49
 
@@ -54,53 +64,67 @@ module Geocoder
54
64
  :distances
55
65
  ]
56
66
 
57
- attr_accessor *OPTIONS
67
+ attr_accessor :data
68
+
69
+ def self.set_defaults
70
+ instance.set_defaults
71
+ end
72
+
73
+ OPTIONS.each do |o|
74
+ define_method o do
75
+ @data[o]
76
+ end
77
+ define_method "#{o}=" do |value|
78
+ @data[o] = value
79
+ end
80
+ end
81
+
82
+ def configure(options)
83
+ @data.rmerge!(options)
84
+ end
58
85
 
59
86
  def initialize # :nodoc
87
+ @data = Geocoder::ConfigurationHash.new
60
88
  set_defaults
61
89
  end
62
90
 
63
91
  def set_defaults
64
- @timeout = 3 # geocoding service timeout (secs)
65
- @lookup = :google # name of street address geocoding service (symbol)
66
- @ip_lookup = :freegeoip # name of IP address geocoding service (symbol)
67
- @language = :en # ISO-639 language code
68
- @http_headers = {} # HTTP headers for lookup
69
- @use_https = false # use HTTPS for lookup requests? (if supported)
70
- @http_proxy = nil # HTTP proxy server (user:pass@host:port)
71
- @https_proxy = nil # HTTPS proxy server (user:pass@host:port)
72
- @api_key = nil # API key for geocoding service
73
- @cache = nil # cache object (must respond to #[], #[]=, and #keys)
74
- @cache_prefix = "geocoder:" # prefix (string) to use for all cache keys
92
+
93
+ # geocoding options
94
+ @data[:timeout] = 3 # geocoding service timeout (secs)
95
+ @data[:lookup] = :google # name of street address geocoding service (symbol)
96
+ @data[:ip_lookup] = :freegeoip # name of IP address geocoding service (symbol)
97
+ @data[:language] = :en # ISO-639 language code
98
+ @data[:http_headers] = {} # HTTP headers for lookup
99
+ @data[:use_https] = false # use HTTPS for lookup requests? (if supported)
100
+ @data[:http_proxy] = nil # HTTP proxy server (user:pass@host:port)
101
+ @data[:https_proxy] = nil # HTTPS proxy server (user:pass@host:port)
102
+ @data[:api_key] = nil # API key for geocoding service
103
+ @data[:cache] = nil # cache object (must respond to #[], #[]=, and #keys)
104
+ @data[:cache_prefix] = "geocoder:" # prefix (string) to use for all cache keys
75
105
 
76
106
  # exceptions that should not be rescued by default
77
107
  # (if you want to implement custom error handling);
78
108
  # supports SocketError and TimeoutError
79
- @always_raise = []
109
+ @data[:always_raise] = []
80
110
 
81
111
  # calculation options
82
- @units = :mi # :mi or :km
83
- @distances = :linear # :linear or :spherical
112
+ @data[:units] = :mi # :mi or :km
113
+ @data[:distances] = :linear # :linear or :spherical
84
114
  end
85
115
 
86
116
  instance_eval(OPTIONS.map do |option|
87
117
  o = option.to_s
88
118
  <<-EOS
89
119
  def #{o}
90
- instance.#{o}
120
+ instance.data[:#{o}]
91
121
  end
92
122
 
93
123
  def #{o}=(value)
94
- instance.#{o} = value
124
+ instance.data[:#{o}] = value
95
125
  end
96
126
  EOS
97
127
  end.join("\n\n"))
98
128
 
99
- class << self
100
- def set_defaults
101
- instance.set_defaults
102
- end
103
- end
104
-
105
129
  end
106
130
  end
@@ -0,0 +1,11 @@
1
+ require 'hash_recursive_merge'
2
+
3
+ module Geocoder
4
+ class ConfigurationHash < Hash
5
+ include HashRecursiveMerge
6
+
7
+ def method_missing(meth, *args, &block)
8
+ has_key?(meth) ? self[meth] : super
9
+ end
10
+ end
11
+ end
@@ -15,4 +15,7 @@ module Geocoder
15
15
  class InvalidRequest < Error
16
16
  end
17
17
 
18
+ class InvalidApiKey < Error
19
+ end
20
+
18
21
  end
@@ -21,6 +21,7 @@ module Geocoder
21
21
  #
22
22
  def street_services
23
23
  [
24
+ :esri,
24
25
  :google,
25
26
  :google_premier,
26
27
  :yahoo,
@@ -29,6 +30,7 @@ module Geocoder
29
30
  :yandex,
30
31
  :nominatim,
31
32
  :mapquest,
33
+ :ovi,
32
34
  :test
33
35
  ]
34
36
  end
@@ -37,7 +39,7 @@ module Geocoder
37
39
  # All IP address lookup services, default first.
38
40
  #
39
41
  def ip_services
40
- [:freegeoip]
42
+ [:freegeoip, :maxmind]
41
43
  end
42
44
 
43
45
  ##
@@ -16,6 +16,21 @@ module Geocoder
16
16
 
17
17
  class Base
18
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
+
19
34
  ##
20
35
  # Query the geocoding API and return a Geocoder::Result object.
21
36
  # Returns +nil+ on timeout or error.
@@ -43,16 +58,47 @@ module Geocoder
43
58
  nil
44
59
  end
45
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
75
+
76
+ ##
77
+ # The working Cache object.
78
+ #
79
+ def cache
80
+ if @cache.nil? and store = configuration.cache
81
+ @cache = Cache.new(store, configuration.cache_prefix)
82
+ end
83
+ @cache
84
+ end
46
85
 
47
86
  private # -------------------------------------------------------------
48
87
 
88
+ ##
89
+ # An object with configuration data for this particular lookup.
90
+ #
91
+ def configuration
92
+ Geocoder.config_for_lookup(handle)
93
+ end
94
+
49
95
  ##
50
96
  # Object used to make HTTP requests.
51
97
  #
52
98
  def http_client
53
- protocol = "http#{'s' if Geocoder::Configuration.use_https}"
99
+ protocol = "http#{'s' if configuration.use_https}"
54
100
  proxy_name = "#{protocol}_proxy"
55
- if proxy = Geocoder::Configuration.send(proxy_name)
101
+ if proxy = configuration.send(proxy_name)
56
102
  proxy_url = protocol + '://' + proxy
57
103
  begin
58
104
  uri = URI.parse(proxy_url)
@@ -84,10 +130,13 @@ module Geocoder
84
130
  end
85
131
 
86
132
  ##
87
- # URL to use for querying the geocoding engine.
133
+ # Key to use for caching a geocoding result. Usually this will be the
134
+ # request URL, but in cases where OAuth is used and the nonce,
135
+ # timestamp, etc varies from one request to another, we need to use
136
+ # something else (like the URL before OAuth encoding).
88
137
  #
89
- def query_url(query)
90
- fail
138
+ def cache_key(query)
139
+ query_url(query)
91
140
  end
92
141
 
93
142
  ##
@@ -102,7 +151,7 @@ module Geocoder
102
151
  # Return false if exception not raised.
103
152
  #
104
153
  def raise_error(error, message = nil)
105
- exceptions = Geocoder::Configuration.always_raise
154
+ exceptions = configuration.always_raise
106
155
  if exceptions == :all or exceptions.include?( error.is_a?(Class) ? error : error.class )
107
156
  raise error, message
108
157
  else
@@ -119,7 +168,7 @@ module Geocoder
119
168
  raise_error(err) or warn "Geocoding API connection cannot be established."
120
169
  rescue TimeoutError => err
121
170
  raise_error(err) or warn "Geocoding API not responding fast enough " +
122
- "(see Geocoder::Configuration.timeout to set limit)."
171
+ "(use Geocoder.configure(:timeout => ...) to set limit)."
123
172
  end
124
173
 
125
174
  ##
@@ -140,37 +189,50 @@ module Geocoder
140
189
  # Set in configuration but not available for every service.
141
190
  #
142
191
  def protocol
143
- "http" + (Geocoder::Configuration.use_https ? "s" : "")
192
+ "http" + (configuration.use_https ? "s" : "")
144
193
  end
145
194
 
146
195
  ##
147
- # Fetches a raw search result (JSON string).
196
+ # Fetch a raw geocoding result (JSON string).
197
+ # The result might or might not be cached.
148
198
  #
149
199
  def fetch_raw_data(query)
150
- timeout(Geocoder::Configuration.timeout) do
151
- url = query_url(query)
152
- uri = URI.parse(url)
153
- if cache and body = cache[url]
154
- @cache_hit = true
155
- else
156
- client = http_client.new(uri.host, uri.port)
157
- client.use_ssl = true if Geocoder::Configuration.use_https
158
- response = client.get(uri.request_uri, Geocoder::Configuration.http_headers)
159
- body = response.body
160
- if cache and (200..399).include?(response.code.to_i)
161
- cache[url] = body
162
- end
163
- @cache_hit = false
200
+ key = cache_key(query)
201
+ if cache and body = cache[key]
202
+ @cache_hit = true
203
+ else
204
+ check_api_key_configuration!(query)
205
+ response = make_api_request(query)
206
+ body = response.body
207
+ if cache and (200..399).include?(response.code.to_i)
208
+ cache[key] = body
164
209
  end
165
- body
210
+ @cache_hit = false
166
211
  end
212
+ body
167
213
  end
168
214
 
169
215
  ##
170
- # The working Cache object.
216
+ # Make an HTTP(S) request to a geocoding API and
217
+ # return the response object.
171
218
  #
172
- def cache
173
- Geocoder.cache
219
+ def make_api_request(query)
220
+ timeout(configuration.timeout) do
221
+ uri = URI.parse(query_url(query))
222
+ client = http_client.new(uri.host, uri.port)
223
+ client.use_ssl = true if configuration.use_https
224
+ client.get(uri.request_uri, configuration.http_headers)
225
+ end
226
+ end
227
+
228
+ def check_api_key_configuration!(query)
229
+ key_parts = query.lookup.required_api_key_parts
230
+ if key_parts.size > Array(configuration.api_key).size
231
+ parts_string = key_parts.size == 1 ? key_parts.first : key_parts
232
+ raise Geocoder::ConfigurationError,
233
+ "The #{query.lookup.name} API requires a key to be configured: " +
234
+ parts_string.inspect
235
+ end
174
236
  end
175
237
 
176
238
  ##
@@ -4,34 +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
27
  def results(query)
14
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
40
  def query_url_params(query)
25
- super.merge(
26
- :key => Geocoder::Configuration.api_key,
41
+ {
42
+ :key => configuration.api_key,
27
43
  :query => query.reverse_geocode? ? nil : query.sanitized_text
28
- )
29
- end
30
-
31
- def query_url(query)
32
- "http://dev.virtualearth.net/REST/v1/Locations" +
33
- (query.reverse_geocode? ? "/#{query.sanitized_text}?" : "?") +
34
- url_query_string(query)
44
+ }.merge(super)
35
45
  end
36
46
  end
37
47
  end