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.
- data/CHANGELOG.md +15 -1
- data/README.md +82 -2
- data/examples/autoexpire_cache_dalli.rb +62 -0
- data/examples/{autoexpire_cache.rb → autoexpire_cache_redis.rb} +4 -4
- data/lib/generators/geocoder/config/templates/initializer.rb +1 -1
- data/lib/geocoder.rb +1 -1
- data/lib/geocoder/cache.rb +4 -0
- data/lib/geocoder/calculations.rb +33 -1
- data/lib/geocoder/lookup.rb +2 -0
- data/lib/geocoder/lookups/base.rb +10 -10
- data/lib/geocoder/lookups/esri.rb +52 -0
- data/lib/geocoder/lookups/mapquest.rb +1 -1
- data/lib/geocoder/lookups/maxmind.rb +19 -3
- data/lib/geocoder/lookups/ovi.rb +52 -0
- data/lib/geocoder/lookups/test.rb +6 -0
- data/lib/geocoder/lookups/yahoo.rb +4 -2
- data/lib/geocoder/query.rb +4 -4
- data/lib/geocoder/request.rb +1 -1
- data/lib/geocoder/results/esri.rb +51 -0
- data/lib/geocoder/results/google.rb +28 -0
- data/lib/geocoder/results/mapquest.rb +1 -1
- data/lib/geocoder/results/maxmind.rb +4 -5
- data/lib/geocoder/results/ovi.rb +62 -0
- data/lib/geocoder/results/test.rb +2 -1
- data/lib/geocoder/results/yandex.rb +12 -2
- data/lib/geocoder/stores/active_record.rb +38 -16
- data/lib/geocoder/stores/mongo_base.rb +2 -1
- data/lib/geocoder/version.rb +1 -1
- data/test/calculations_test.rb +6 -0
- data/test/fixtures/esri_madison_square_garden +59 -0
- data/test/fixtures/esri_no_results +8 -0
- data/test/fixtures/esri_reverse +21 -0
- data/test/fixtures/maxmind_24_24_24_21 +1 -1
- data/test/fixtures/maxmind_24_24_24_22 +1 -1
- data/test/fixtures/maxmind_24_24_24_23 +1 -1
- data/test/fixtures/maxmind_24_24_24_24 +1 -1
- data/test/fixtures/ovi_madison_square_garden +72 -0
- data/test/fixtures/ovi_no_results +8 -0
- data/test/fixtures/yandex_no_city_and_town +112 -0
- data/test/lookup_test.rb +1 -0
- data/test/near_test.rb +32 -0
- data/test/query_test.rb +5 -0
- data/test/request_test.rb +29 -0
- data/test/result_test.rb +9 -0
- data/test/services_test.rb +59 -5
- data/test/test_helper.rb +1 -1
- data/test/test_mode_test.rb +32 -23
- 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
|
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
|
5
|
-
def initialize(store)
|
4
|
+
class AutoexpireCacheRedis
|
5
|
+
def initialize(store, ttl = 86400)
|
6
6
|
@store = store
|
7
|
-
@ttl =
|
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 =>
|
28
|
+
Geocoder.configure(:cache => AutoexpireCacheRedis.new(Redis.new))
|
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).
|
47
|
+
Geocoder::Lookup.get(Geocoder.config.lookup).cache
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
data/lib/geocoder/cache.rb
CHANGED
@@ -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
|
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/lookup.rb
CHANGED
@@ -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
|