geocoder 1.1.6 → 1.1.7

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of geocoder might be problematic. Click here for more details.

Files changed (48) hide show
  1. data/CHANGELOG.md +15 -1
  2. data/README.md +82 -2
  3. data/examples/autoexpire_cache_dalli.rb +62 -0
  4. data/examples/{autoexpire_cache.rb → autoexpire_cache_redis.rb} +4 -4
  5. data/lib/generators/geocoder/config/templates/initializer.rb +1 -1
  6. data/lib/geocoder.rb +1 -1
  7. data/lib/geocoder/cache.rb +4 -0
  8. data/lib/geocoder/calculations.rb +33 -1
  9. data/lib/geocoder/lookup.rb +2 -0
  10. data/lib/geocoder/lookups/base.rb +10 -10
  11. data/lib/geocoder/lookups/esri.rb +52 -0
  12. data/lib/geocoder/lookups/mapquest.rb +1 -1
  13. data/lib/geocoder/lookups/maxmind.rb +19 -3
  14. data/lib/geocoder/lookups/ovi.rb +52 -0
  15. data/lib/geocoder/lookups/test.rb +6 -0
  16. data/lib/geocoder/lookups/yahoo.rb +4 -2
  17. data/lib/geocoder/query.rb +4 -4
  18. data/lib/geocoder/request.rb +1 -1
  19. data/lib/geocoder/results/esri.rb +51 -0
  20. data/lib/geocoder/results/google.rb +28 -0
  21. data/lib/geocoder/results/mapquest.rb +1 -1
  22. data/lib/geocoder/results/maxmind.rb +4 -5
  23. data/lib/geocoder/results/ovi.rb +62 -0
  24. data/lib/geocoder/results/test.rb +2 -1
  25. data/lib/geocoder/results/yandex.rb +12 -2
  26. data/lib/geocoder/stores/active_record.rb +38 -16
  27. data/lib/geocoder/stores/mongo_base.rb +2 -1
  28. data/lib/geocoder/version.rb +1 -1
  29. data/test/calculations_test.rb +6 -0
  30. data/test/fixtures/esri_madison_square_garden +59 -0
  31. data/test/fixtures/esri_no_results +8 -0
  32. data/test/fixtures/esri_reverse +21 -0
  33. data/test/fixtures/maxmind_24_24_24_21 +1 -1
  34. data/test/fixtures/maxmind_24_24_24_22 +1 -1
  35. data/test/fixtures/maxmind_24_24_24_23 +1 -1
  36. data/test/fixtures/maxmind_24_24_24_24 +1 -1
  37. data/test/fixtures/ovi_madison_square_garden +72 -0
  38. data/test/fixtures/ovi_no_results +8 -0
  39. data/test/fixtures/yandex_no_city_and_town +112 -0
  40. data/test/lookup_test.rb +1 -0
  41. data/test/near_test.rb +32 -0
  42. data/test/query_test.rb +5 -0
  43. data/test/request_test.rb +29 -0
  44. data/test/result_test.rb +9 -0
  45. data/test/services_test.rb +59 -5
  46. data/test/test_helper.rb +1 -1
  47. data/test/test_mode_test.rb +32 -23
  48. metadata +15 -3
data/CHANGELOG.md CHANGED
@@ -1,7 +1,21 @@
1
1
  Changelog
2
2
  =========
3
3
 
4
- Per-release changes to Geocoder.
4
+ Per-release changes to Geocoder. Note that only major changes are summarized here. Please see the Git log for all changes.
5
+
6
+ 1.1.7 (2013 Apr 21)
7
+ -------------------
8
+
9
+ * Add support for Ovi/Nokia API (thanks github.com/datenimperator).
10
+ * Add support for ESRI API (thanks github.com/rpepato).
11
+ * Add ability to omit distance and bearing from SQL select clause (thanks github.com/nicolasdespres).
12
+ * Add support for caches that use read/write methods (thanks github.com/eskil).
13
+ * Add support for nautical miles (thanks github.com/vanboom).
14
+ * Fix: bug in parsing of MaxMind responses.
15
+ * Fix: bugs in query regular expressions (thanks github.com/boone).
16
+ * Fix: various bugs in MaxMind implementation.
17
+ * Fix: don't require a key for MapQuest.
18
+ * Fix: bug in handling of HTTP_X_FORWARDED_FOR header (thanks github.com/robdimarco).
5
19
 
6
20
  1.1.6 (2012 Dec 24)
7
21
  -------------------
data/README.md CHANGED
@@ -109,6 +109,8 @@ Geocoder adds a `location` method to the standard `Rack::Request` object so you
109
109
  # returns Geocoder::Result object
110
110
  result = request.location
111
111
 
112
+ Note that this will usually return `nil` in your test and development environments because things like "localhost" and "0.0.0.0" are not an Internet IP addresses.
113
+
112
114
  See _Advanced Geocoding_ below for more information about `Geocoder::Result` objects.
113
115
 
114
116
 
@@ -178,7 +180,7 @@ To calculate accurate distance and bearing with SQLite or MongoDB:
178
180
  obj.bearing_to([43.9,-98.6]) # bearing from obj to point
179
181
  obj.bearing_from(obj2) # bearing from obj2 to obj
180
182
 
181
- The `bearing_from/to` methods take a single argument which can be: a `[lat,lon]` array, a geocoded object, or a geocodable address (string). The `distance_from/to` methods also take a units argument (`:mi` or `:km`).
183
+ The `bearing_from/to` methods take a single argument which can be: a `[lat,lon]` array, a geocoded object, or a geocodable address (string). The `distance_from/to` methods also take a units argument (`:mi`, `:km`, or `:nm` for nautical miles).
182
184
 
183
185
 
184
186
  Model Configuration
@@ -382,6 +384,17 @@ Yahoo BOSS is **not a free service**. As of November 17, 2012 Yahoo no longer of
382
384
  * **Terms of Service**: http://info.mapquest.com/terms-of-use/
383
385
  * **Limitations**: ?
384
386
 
387
+ #### Ovi/Nokia (`:ovi`)
388
+
389
+ * **API key**: not required, but performance restricted without it
390
+ * **Quota**: ?
391
+ * **Region**: world
392
+ * **SSL support**: no
393
+ * **Languages**: English
394
+ * **Documentation**: http://api.maps.ovi.com/devguide/overview.html
395
+ * **Terms of Service**: http://www.developer.nokia.com/Develop/Maps/TC.html
396
+ * **Limitations**: ?
397
+
385
398
  #### FreeGeoIP (`:freegeoip`)
386
399
 
387
400
  * **API key**: none
@@ -403,6 +416,18 @@ Yahoo BOSS is **not a free service**. As of November 17, 2012 Yahoo no longer of
403
416
  * **Documentation**: http://www.maxmind.com/app/web_services
404
417
  * **Terms of Service**: ?
405
418
  * **Limitations**: ?
419
+ * **Notes**: You must specify which MaxMind service you are using in your configuration. For example: `Geocoder.configure(:maxmind => {:service => :omni})`.
420
+
421
+ #### ESRI (`:esri`)
422
+
423
+ * **API key**: none
424
+ * **Quota**: Required for some scenarios (see Terms of Service)
425
+ * **Region**: world
426
+ * **SSL support**: yes
427
+ * **Languages**: English
428
+ * **Documentation**: http://resources.arcgis.com/en/help/arcgis-online-geocoding-rest-api/
429
+ * **Terms of Service**: http://www.esri.com/software/arcgis/arcgisonline/services/geoservices
430
+ * **Limitations**: ?
406
431
 
407
432
 
408
433
  Caching
@@ -471,6 +496,30 @@ However, there can be only one set of latitude/longitude attributes, and whichev
471
496
 
472
497
  The reason for this is that we don't want ambiguity when doing distance calculations. We need a single, authoritative source for coordinates!
473
498
 
499
+ Once both forward and reverse geocoding has been applied, it is possible to call them sequentially.
500
+
501
+ For example:
502
+
503
+ class Venue
504
+
505
+ after_validation :geocode, :reverse_geocode
506
+
507
+ end
508
+
509
+ For certain geolocation services such as Google geolocation API this may cause issues during subsequent updates to database records if the longtitude and latitude coordinates cannot be associated known location address (on a large body of water for example). On subsequent callbacks the following call:
510
+
511
+ after_validation :geocode
512
+
513
+ will alter the longtitude and latitude attributes based on the location field, which would be the closest known location to the original coordinates. In this case it is better to add conditions to each call, as not to override coordinates that do not have known location addresses associated with them.
514
+
515
+ For example:
516
+
517
+ class Venue
518
+
519
+ after_validation :reverse_geocode, :if => :has_coordinates
520
+ after_validation :geocode, :if => :has_location, :unless => :has_coordinates
521
+
522
+ end
474
523
 
475
524
  Use Outside of Rails
476
525
  --------------------
@@ -503,8 +552,25 @@ When writing tests for an app that uses Geocoder it may be useful to avoid netwo
503
552
  ]
504
553
  )
505
554
 
506
- Now, any time Geocoder looks up "New York, NY" its results array will contain one result with the above attributes.
555
+ Now, any time Geocoder looks up "New York, NY" its results array will contain one result with the above attributes. You can also set a default stub:
556
+
557
+ Geocoder.configure(:lookup => :test)
558
+
559
+ Geocoder::Lookup::Test.set_default_stub(
560
+ [
561
+ {
562
+ 'latitude' => 40.7143528,
563
+ 'longitude' => -74.0059731,
564
+ 'address' => 'New York, NY, USA',
565
+ 'state' => 'New York',
566
+ 'state_code' => 'NY',
567
+ 'country' => 'United States',
568
+ 'country_code' => 'US'
569
+ }
570
+ ]
571
+ )
507
572
 
573
+ Any query that hasn't been explicitly stubbed will return that result.
508
574
 
509
575
  Command Line Interface
510
576
  ----------------------
@@ -630,6 +696,20 @@ A lot of debugging time can be saved by understanding how Geocoder works with Ac
630
696
  * using the `pluck` method (selects only a single column)
631
697
  * specifying another model through `includes` (selects columns from other tables)
632
698
 
699
+ ### Unexpected Responses from Geocoding Services
700
+
701
+ Take a look at the server's raw JSON response. You can do this by getting the request URL in an app console:
702
+
703
+ Geocoder::Lookup.get(:google).query_url(Geocoder::Query.new("..."))
704
+
705
+ Replace `:google` with the lookup you are using and replace `...` with the address you are trying to geocode. Then visit the returned URL in your web browser. Often the API will return an error message that helps you resolve the problem. If, after reading the raw response, you believe there is a problem with Geocoder, please post an issue and include both the URL and raw response body.
706
+
707
+
708
+ Reporting Issues
709
+ ----------------
710
+
711
+ When reporting an issue, please list the version of Geocoder you are using and any relevant information about your application (Rails version, database type and version, etc). Also avoid vague language like "it doesn't work." Please describe as specifically as you can what behavior your are actually seeing (eg: an error message? a nil return value?).
712
+
633
713
 
634
714
  Known Issue
635
715
  -----------
@@ -0,0 +1,62 @@
1
+ # This class implements a cache with simple delegation to the the Dalli Memcached client
2
+ # https://github.com/mperham/dalli
3
+ #
4
+ # A TTL is set on initialization
5
+
6
+ class AutoexpireCacheDalli
7
+ def initialize(store, ttl = 86400)
8
+ @store = store
9
+ @keys = 'GeocoderDalliClientKeys'
10
+ @ttl = ttl
11
+ end
12
+
13
+ def [](url)
14
+ res = @store.get(url)
15
+ res = YAML::load(res) if res.present?
16
+ res
17
+ end
18
+
19
+ def []=(url, value)
20
+ if value.nil?
21
+ del(url)
22
+ else
23
+ key_cache_add(url) if @store.add(key, YAML::dump(value), @ttl)
24
+ end
25
+ value
26
+ end
27
+
28
+ def keys
29
+ key_cache
30
+ end
31
+
32
+ def del(url)
33
+ key_cache_delete(url) if @store.delete(key)
34
+ end
35
+
36
+ private
37
+
38
+ def key_cache
39
+ the_keys = @store.get(@keys)
40
+ if the_keys.nil?
41
+ @store.add(@keys, YAML::dump([]))
42
+ []
43
+ else
44
+ YAML::load(the_keys)
45
+ end
46
+ end
47
+
48
+ def key_cache_add(key)
49
+ @store.replace(@keys, YAML::dump(key_cache << key))
50
+ end
51
+
52
+ def key_cache_delete(key)
53
+ tmp = key_cache
54
+ tmp.delete(key)
55
+ @store.replace(@keys, YAML::dump(tmp))
56
+ end
57
+ end
58
+
59
+ # Here Dalli is set up as on Heroku using the Memcachier gem.
60
+ # https://devcenter.heroku.com/articles/memcachier#ruby
61
+ # On other setups you might have to specify your Memcached server in Dalli::Client.new
62
+ Geocoder.configure(:cache => AutoexpireCacheDalli.new(Dalli::Client.new))
@@ -1,10 +1,10 @@
1
1
  # This class implements a cache with simple delegation to the Redis store, but
2
2
  # when it creates a key/value pair, it also sends an EXPIRE command with a TTL.
3
3
  # It should be fairly simple to do the same thing with Memcached.
4
- class AutoexpireCache
5
- def initialize(store)
4
+ class AutoexpireCacheRedis
5
+ def initialize(store, ttl = 86400)
6
6
  @store = store
7
- @ttl = 86400
7
+ @ttl = ttl
8
8
  end
9
9
 
10
10
  def [](url)
@@ -25,4 +25,4 @@ class AutoexpireCache
25
25
  end
26
26
  end
27
27
 
28
- Geocoder.configure(:cache => AutoexpireCache.new(Redis.new))
28
+ Geocoder.configure(:cache => AutoexpireCacheRedis.new(Redis.new))
@@ -18,4 +18,4 @@ Geocoder.configure(
18
18
  # calculation options
19
19
  # :units => :mi, # :km for kilometers or :mi for miles
20
20
  # :distances => :linear # :spherical or :linear
21
- end
21
+ )
data/lib/geocoder.rb CHANGED
@@ -44,7 +44,7 @@ module Geocoder
44
44
  #
45
45
  def cache
46
46
  warn "WARNING: Calling Geocoder.cache is DEPRECATED. The #cache method now belongs to the Geocoder::Lookup object."
47
- Geocoder::Lookup.get(Geocoder.config.lookup).send(:configuration).cache
47
+ Geocoder::Lookup.get(Geocoder.config.lookup).cache
48
48
  end
49
49
  end
50
50
 
@@ -15,6 +15,8 @@ module Geocoder
15
15
  store[key_for(url)]
16
16
  when store.respond_to?(:get)
17
17
  store.get key_for(url)
18
+ when store.respond_to?(:read)
19
+ store.read key_for(url)
18
20
  end
19
21
  end
20
22
 
@@ -27,6 +29,8 @@ module Geocoder
27
29
  store[key_for(url)] = value
28
30
  when store.respond_to?(:set)
29
31
  store.set key_for(url), value
32
+ when store.respond_to?(:write)
33
+ store.write key_for(url), value
30
34
  end
31
35
  end
32
36
 
@@ -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
 
@@ -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
280
  # Use Geocoder.configure(:units => ...) to configure default units.
269
281
  #
270
282
  def earth_radius(units = nil)
271
283
  units ||= Geocoder.config.units
272
- units == :km ? EARTH_RADIUS : to_miles(EARTH_RADIUS)
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
@@ -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
@@ -72,6 +72,16 @@ module Geocoder
72
72
  def query_url(query)
73
73
  fail
74
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
75
85
 
76
86
  private # -------------------------------------------------------------
77
87
 
@@ -225,16 +235,6 @@ module Geocoder
225
235
  end
226
236
  end
227
237
 
228
- ##
229
- # The working Cache object.
230
- #
231
- def cache
232
- if @cache.nil? and store = configuration.cache
233
- @cache = Cache.new(store, configuration.cache_prefix)
234
- end
235
- @cache
236
- end
237
-
238
238
  ##
239
239
  # Simulate ActiveSupport's Object#to_query.
240
240
  # Removes any keys with nil value.
@@ -0,0 +1,52 @@
1
+ require 'geocoder/lookups/base'
2
+ require "geocoder/results/esri"
3
+ require 'rack/utils'
4
+
5
+ module Geocoder::Lookup
6
+ class Esri < Base
7
+
8
+ def name
9
+ "Esri"
10
+ end
11
+
12
+ def query_url(query)
13
+ search_keyword = query.reverse_geocode? ? "reverseGeocode" : "find"
14
+
15
+ "#{protocol}://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/#{search_keyword}?" +
16
+ url_query_string(query)
17
+ end
18
+
19
+ private # ---------------------------------------------------------------
20
+
21
+ def results(query)
22
+ return [] unless doc = fetch_data(query)
23
+
24
+ if (!query.reverse_geocode?)
25
+ return [] if doc['locations'].empty?
26
+ end
27
+
28
+ if (doc['error'].nil?)
29
+ return [ doc ]
30
+ else
31
+ return []
32
+ end
33
+ end
34
+
35
+ def query_url_params(query)
36
+ if query.reverse_geocode?
37
+ {
38
+ :location => query.coordinates.reverse.join(','),
39
+ :outFields => :*,
40
+ :p => :pjson
41
+ }.merge(super)
42
+ else
43
+ {
44
+ :f => :pjson,
45
+ :outFields => :*,
46
+ :text => query.sanitized_text
47
+ }.merge(super)
48
+ end
49
+ end
50
+
51
+ end
52
+ end
@@ -10,7 +10,7 @@ module Geocoder::Lookup
10
10
  end
11
11
 
12
12
  def required_api_key_parts
13
- ["key"]
13
+ []
14
14
  end
15
15
 
16
16
  def query_url(query)