geocoder 0.9.10 → 0.9.11

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/.gitignore CHANGED
@@ -1,3 +1,4 @@
1
1
  pkg/*
2
+ rdoc/*
2
3
  *.gem
3
4
  .bundle
@@ -2,6 +2,16 @@
2
2
 
3
3
  Per-release changes to Geocoder.
4
4
 
5
+ == 0.9.11 (2011 Mar ??)
6
+
7
+ * Add support for result caching.
8
+ * Add support for Geocoder.ca geocoding service.
9
+ * Add +bearing+ attribute to objects returned by geo-aware queries (thanks github.com/matellis).
10
+ * Add config setting: language.
11
+ * Add config settings: use_https, google_api_key (thanks github.com/svesely).
12
+ * DEPRECATION: Geocoder.search now returns an array instead of a single result.
13
+ * DEPRECATION: obj.nearbys second argument is now an options hash (instead of units). Please change <tt>obj.nearbys(20, :km)</tt> to: <tt>obj.nearbys(20, :units => :km)</tt>.
14
+
5
15
  == 0.9.10 (2011 Mar 9)
6
16
 
7
17
  * Fix broken scopes (github.com/mikepinde).
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2009-10 Alex Reisner
1
+ Copyright (c) 2009-11 Alex Reisner
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
@@ -1,6 +1,6 @@
1
1
  = Geocoder
2
2
 
3
- Geocoder is a complete geocoding solution for Ruby. With Rails it adds object geocoding (by street or IP address), reverse geocoding (find street address based on given coordinates), and distance calculations to Ruby on Rails. It's as simple as calling +geocode+ on your objects, and then using a scope like <tt>Venue.near("Billings, MT")</tt>. Since it does not rely on proprietary database functions finding geocoded objects in a given area works with out-of-the-box PostgreSQL, MySQL, and even SQLite.
3
+ Geocoder is a complete geocoding solution for Ruby. With Rails it adds geocoding (by street or IP address), reverse geocoding (find street address based on given coordinates), and distance calculations for ActiveRecord objects. It's as simple as calling +geocode+ on your objects, and then using a scope like <tt>Venue.near("Billings, MT")</tt>. Since it does not rely on proprietary database functions finding geocoded objects in a given area works with out-of-the-box PostgreSQL, MySQL, and even SQLite.
4
4
 
5
5
 
6
6
  == Compatibility
@@ -9,6 +9,8 @@ Geocoder has been successfully tested with Ruby (MRI) 1.8.7, 1.9.2, and JRuby 1.
9
9
 
10
10
  Geocoder is compatible with Rails 3. If you need to use it with Rails 2 please see the <tt>rails2</tt> branch (no longer maintained, limited feature set).
11
11
 
12
+ Geocoder also works outside of Rails but you'll need to install either the +json+ (for MRI) or +json_pure+ (for JRuby) gem.
13
+
12
14
 
13
15
  == Install
14
16
 
@@ -33,13 +35,22 @@ At the command prompt:
33
35
 
34
36
  === Required Attributes
35
37
 
36
- Your object must have two attributes (database columns) for storing latitude and longitude coordinates. By default they should be called +latitude+ and +longitude+ but this can be changed (see "More on Configuration" below):
38
+ *ActiveRecord:* Your object must have two attributes (database columns) for storing latitude and longitude coordinates. By default they should be called +latitude+ and +longitude+ but this can be changed (see "More on Configuration" below):
37
39
 
38
40
  rails generate migration AddLatitudeAndLongitudeToModel latitude:float longitude:float
39
41
  rake db:migrate
40
42
 
41
43
  For reverse geocoding your model must provide a method that returns an address. This can be a single attribute, but it can also be a method that returns a string assembled from different attributes (eg: +city+, +state+, and +country+).
42
44
 
45
+ *Mongoid:* Define your address and coordinate fields right in the model. You also need to include the <tt>Geocoder::Model::Mongoid</tt> module _before_ calling <tt>geocoded_by</tt>:
46
+
47
+ field :address
48
+ field :latitude, :type => Float
49
+ field :longitude, :type => Float
50
+
51
+ include Geocoder::Model::Mongoid
52
+ geocoded_by :address
53
+
43
54
  === Model Behavior
44
55
 
45
56
  In your model, tell Geocoder which method returns your object's full address:
@@ -82,12 +93,37 @@ Some utility methods are also available:
82
93
  => 3619.77359999382
83
94
 
84
95
  # find the geographic center (aka center of gravity) of objects or points
85
- Geocoder::Calculations.geographic_center([ city1, city2, city3, [40.22,-73.99], city4 ])
96
+ Geocoder::Calculations.geographic_center([ city1, city2, [40.22,-73.99], city4 ])
86
97
  => [35.14968, -90.048929]
87
98
 
88
99
  Please see the code for more methods and detailed information about arguments (eg, working with kilometers).
89
100
 
90
101
 
102
+ == Distance and Bearing
103
+
104
+ When you run a location-aware query the returned objects have two attributes added to them:
105
+
106
+ * <tt>obj.distance</tt> - number of miles from the search point to this object
107
+ * <tt>obj.bearing</tt> - direction from the search point to this object
108
+
109
+ The bearing is given as a number (between 0 and 360): clockwise degrees from due north. Some examples:
110
+
111
+ * +0+ - due north
112
+ * +180+ - due south
113
+ * +90+ - due east
114
+ * +270+ - due west
115
+ * +230.1+ - southwest
116
+ * +359.9+ - almost due north
117
+
118
+ You can convert these numbers to compass point names by using the utility method provided:
119
+
120
+ Geocoder::Calculations.compass_point(355) # => "N"
121
+ Geocoder::Calculations.compass_point(45) # => "NE"
122
+ Geocoder::Calculations.compass_point(208) # => "SW"
123
+
124
+ <i>Note: when using SQLite +distance+ and +bearing+ values are provided for interface consistency only. They are not accurate.</i>
125
+
126
+
91
127
  == More on Configuration
92
128
 
93
129
  You are not stuck with using the +latitude+ and +longitude+ database column names for storing coordinates. For example, to use +lat+ and +lon+:
@@ -126,14 +162,14 @@ So far we have looked at shortcuts for assigning geocoding results to object att
126
162
 
127
163
  Every <tt>Geocoder::Result</tt> object, +result+, provides the following data:
128
164
 
129
- * <tt>result.latitude # float</tt>
130
- * <tt>result.longitude # float</tt>
131
- * <tt>result.coordinates # array of the above two</tt>
132
- * <tt>result.address # string</tt>
133
- * <tt>result.city # string</tt>
134
- * <tt>result.postal_code # string</tt>
135
- * <tt>result.country_name # string</tt>
136
- * <tt>result.country_code # string</tt>
165
+ * +result.latitude+ - float
166
+ * +result.longitude+ - float
167
+ * +result.coordinates+ - array of the above two
168
+ * +result.address+ - string
169
+ * +result.city+ - string
170
+ * +result.postal_code+ - string
171
+ * +result.country_name+ - string
172
+ * +result.country_code+ - string
137
173
 
138
174
  and if you're familiar with the results returned by the geocoding service you're using, you can access even more (see code comments for details: <tt>lib/geocoder/results/*</tt>).
139
175
 
@@ -144,31 +180,85 @@ By default Geocoder uses Google's geocoding API to fetch coordinates and address
144
180
 
145
181
  # config/initializers/geocoder.rb
146
182
  Geocoder::Configuration.lookup = :yahoo
147
- Geocoder::Configuration.yahoo_appid = "..."
148
183
 
149
- To obtain a Yahoo app id go to:
184
+ Street address geocoding services currently supported (valid settings for the above):
150
185
 
151
- https://developer.apps.yahoo.com/wsregapp
186
+ * Google: <tt>:google</tt>
187
+ * Yahoo: <tt>:yahoo</tt>
188
+ * Geocoder.ca: <tt>:geocoder_ca</tt> (US and Canada only)
152
189
 
153
190
  Note that the result objects returned by different geocoding services all implement the methods listed above. Beyond that, however, you must be familiar with your particular subclass of <tt>Geocoder::Result</tt> and the geocoding service's result structure:
154
191
 
155
- Google: http://code.google.com/apis/maps/documentation/geocoding/#JSON
192
+ * Google: http://code.google.com/apis/maps/documentation/geocoding/#JSON
193
+ * Yahoo: http://developer.yahoo.com/geo/placefinder/guide/responses.html
194
+ * Geocoder.ca: (???)
195
+ * FreeGeoIP: http://github.com/fiorix/freegeoip/blob/master/README.rst
196
+
197
+ === API Keys
156
198
 
157
- Yahoo: http://developer.yahoo.com/geo/placefinder/guide/responses.html
199
+ To use your Google API key or Yahoo app ID:
158
200
 
159
- FreeGeoIP: http://github.com/fiorix/freegeoip/blob/master/README.rst
201
+ Geocoder::Configuration.api_key = "..."
160
202
 
161
- === Timeouts
203
+ To obtain an API key (not required):
204
+
205
+ * Yahoo: https://developer.apps.yahoo.com/wsregapp
206
+ * Google: http://code.google.com/apis/maps/signup.html
207
+
208
+ === Timeout
162
209
 
163
210
  You can set the timeout used for connections to the geocoding service. The default is 3 seconds but if you want to set it to 5, for example, put the following in an initializer:
164
211
 
165
- # config/initializers/geocoder.rb
166
212
  Geocoder::Configuration.timeout = 5
167
213
 
214
+ === Language
215
+
216
+ You can set the language used for reverse geocoding results to German, for example, by setting the following:
217
+
218
+ Geocoder::Configuration.language = :de
219
+
220
+ For a list of supported languages see the documentation for the geocoding service you're using.
221
+
222
+ === HTTPS
223
+
224
+ If you want to use HTTPS for geocoding service connections:
225
+
226
+ Geocoder::Configuration.use_https = true
227
+
228
+ Note that currently the only service that supports HTTPS is Google.
229
+
230
+
231
+ == Caching Results
232
+
233
+ It's a good idea, when relying on any external service, to cache retrieved data. When implemented correctly it improves your app's response time and stability. It's easy to cache geocoding results with Geocoder, just configure a cache store:
234
+
235
+ Geocoder::Configuration.cache = Redis.new
236
+
237
+ This example uses Redis, but the cache store can be any object that supports these methods:
238
+
239
+ * <tt>store#[](key)</tt> - retrieves a value
240
+ * <tt>store#[]=(key, value)</tt> - stores a value
241
+ * <tt>store#keys</tt> - lists all keys
242
+
243
+ Even a plain Ruby hash will work, though it's not a great choice (cleared out when app is restarted, not shared between app instances, etc).
244
+
245
+ You can also set a custom prefix to be used for cache keys:
246
+
247
+ Geocoder::Configuration.cache_prefix = "..."
248
+
249
+ By default the prefix is <tt>geocoder:</tt>
250
+
251
+ If you need to expire cached content:
252
+
253
+ Geocoder.cache.expire("http://...") # expire cached result for a URL
254
+ Geocoder.cache.expire(:all) # expire all cached results
255
+
256
+ Do *not* include the prefix when passing a URL to be expired. Expiring <tt>:all</tt> will only expire keys with the configured prefix (won't kill every entry in your key/value store).
257
+
168
258
 
169
259
  == Forward and Reverse Geocoding in the Same Model
170
260
 
171
- If you apply both forward and reverse geocoding functionality to the same model, you will provide two address methods:
261
+ If you apply both forward and reverse geocoding functionality to the same model (say users can supply an address or coordinates and you want to fill in whatever's missing), you will provide two address methods:
172
262
 
173
263
  * one for storing the fetched address (reverse geocoding)
174
264
  * one for providing an address to use when fetching coordinates (forward geocoding)
@@ -210,23 +300,21 @@ Geocoder adds a +location+ method to the standard <tt>Rack::Request</tt> object
210
300
 
211
301
  You can use Geocoder outside of Rails by calling the <tt>Geocoder.search</tt> method:
212
302
 
213
- result = Geocoder.search("McCarren Park, Brooklyn, NY")
303
+ results = Geocoder.search("McCarren Park, Brooklyn, NY")
214
304
 
215
- This returns a <tt>Geocoder::Result</tt> object with all information provided by the geocoding service. Please see above and in the code for details.
305
+ This returns an array of <tt>Geocoder::Result</tt> objects with all information provided by the geocoding service. Please see above and in the code for details.
216
306
 
217
307
 
218
308
  == Distance Queries in SQLite
219
309
 
220
- SQLite's lack of trigonometric functions requires an alternate implementation of the +near+ scope. When using SQLite, Geocoder will automatically use a less accurate algorithm for finding objects near a given point. Results of this algorithm should not be trusted too much as it will return objects that are outside the given radius.
221
-
222
- It is also not possible to calculate distances between points without the trig functions so you cannot sort results by "nearness."
310
+ SQLite's lack of trigonometric functions requires an alternate implementation of the +near+ scope. When using SQLite, Geocoder will automatically use a less accurate algorithm for finding objects near a given point. Results of this algorithm should not be trusted too much as it will return objects that are outside the given radius, along with inaccurate distance and bearing calculations.
223
311
 
224
312
 
225
313
  === Discussion
226
314
 
227
315
  There are few options for finding objects near a given point in SQLite without installing extensions:
228
316
 
229
- 1. Use a square instead of a circle for finding nearby points. For example, if you want to find points near 40.71, 100.23, search for objects with latitude between 39.71 and 41.71 and longitude between 99.23 and 101.23. One degree of latitude or longitude is at most 69 miles so divide your radius (in miles) by 69.0 to get the amount to add and subtract from your center coordinates to get the upper and lower bounds. The results will not be very accurate (you'll get points outside the desired radius--at worst 29% farther away), but you will get all the points within the required radius.
317
+ 1. Use a square instead of a circle for finding nearby points. For example, if you want to find points near 40.71, 100.23, search for objects with latitude between 39.71 and 41.71 and longitude between 99.23 and 101.23. One degree of latitude or longitude is at most 69 miles so divide your radius (in miles) by 69.0 to get the amount to add and subtract from your center coordinates to get the upper and lower bounds. The results will not be very accurate (you'll get points outside the desired radius), but you will get all the points within the required radius.
230
318
 
231
319
  2. Load all objects into memory and compute distances between them using the <tt>Geocoder::Calculations.distance_between</tt> method. This will produce accurate results but will be very slow (and use a lot of memory) if you have a lot of objects in your database.
232
320
 
@@ -245,12 +333,11 @@ You cannot use the +near+ scope with another scope that provides an +includes+ o
245
333
  If anyone has a more elegant solution to this problem I am very interested in seeing it.
246
334
 
247
335
 
248
- == To-do List
336
+ == Roadmap
249
337
 
250
- * add support for DataMapper
251
- * add support for Mongoid
252
- * make 'near' scope work with AR associations
253
- * http://stackoverflow.com/questions/3266358/geocoder-rails-plugin-near-search-problem-with-activerecord
338
+ * add support for more ORMs (Mongoid, DataMapper)
339
+ * add support for more geocoding services
340
+ * maintain the same simple interface
254
341
 
255
342
 
256
343
  Copyright (c) 2009-11 Alex Reisner, released under the MIT license
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.9.10
1
+ 0.9.11
@@ -1,6 +1,6 @@
1
1
  require "geocoder/configuration"
2
2
  require "geocoder/calculations"
3
- require "geocoder/railtie"
3
+ require "geocoder/cache"
4
4
  require "geocoder/request"
5
5
 
6
6
  module Geocoder
@@ -10,17 +10,35 @@ module Geocoder
10
10
  # Search for information about an address or a set of coordinates.
11
11
  #
12
12
  def search(*args)
13
- return nil if blank_query?(args[0])
14
- ip = (args.size == 1 and ip_address?(args.first))
15
- lookup(ip).search(*args)
13
+ if blank_query?(args[0])
14
+ results = []
15
+ else
16
+ ip = (args.size == 1 and ip_address?(args.first))
17
+ results = lookup(ip).search(*args)
18
+ end
19
+ results.instance_eval do
20
+ def warn_search_deprecation(attr)
21
+ warn "DEPRECATION WARNING: Geocoder.search now returns an array of Geocoder::Result objects. " +
22
+ "Calling '%s' directly on the returned array will cause an exception in Geocoder v1.0." % attr
23
+ end
24
+
25
+ def coordinates; warn_search_deprecation('coordinates'); first.coordinates if first; end
26
+ def latitude; warn_search_deprecation('latitude'); first.latitude if first; end
27
+ def longitude; warn_search_deprecation('longitude'); first.longitude if first; end
28
+ def address; warn_search_deprecation('address'); first.address if first; end
29
+ def city; warn_search_deprecation('city'); first.city if first; end
30
+ def country; warn_search_deprecation('country'); first.country if first; end
31
+ def country_code; warn_search_deprecation('country_code'); first.country_code if first; end
32
+ end
33
+ return results
16
34
  end
17
35
 
18
36
  ##
19
37
  # Look up the coordinates of the given street or IP address.
20
38
  #
21
39
  def coordinates(address)
22
- if result = search(address)
23
- result.coordinates
40
+ if (results = search(address)).size > 0
41
+ results.first.coordinates
24
42
  end
25
43
  end
26
44
 
@@ -28,9 +46,19 @@ module Geocoder
28
46
  # Look up the address of the given coordinates.
29
47
  #
30
48
  def address(latitude, longitude)
31
- if result = search(latitude, longitude)
32
- result.address
49
+ if (results = search(latitude, longitude)).size > 0
50
+ results.first.address
51
+ end
52
+ end
53
+
54
+ ##
55
+ # The working Cache object, or +nil+ if none configured.
56
+ #
57
+ def cache
58
+ if @cache.nil? and store = Configuration.cache
59
+ @cache = Cache.new(store, Configuration.cache_prefix)
33
60
  end
61
+ @cache
34
62
  end
35
63
 
36
64
 
@@ -42,17 +70,20 @@ module Geocoder
42
70
  private # -----------------------------------------------------------------
43
71
 
44
72
  ##
45
- # Get the lookup object (which communicates with the remote geocoding API).
73
+ # Get a Lookup object (which communicates with the remote geocoding API).
46
74
  # Returns an IP address lookup if +ip+ parameter true.
47
75
  #
48
76
  def lookup(ip = false)
49
77
  if ip
50
78
  get_lookup :freegeoip
51
79
  else
52
- get_lookup Geocoder::Configuration.lookup || :google
80
+ get_lookup Configuration.lookup || :google
53
81
  end
54
82
  end
55
83
 
84
+ ##
85
+ # Retrieve a Lookup object from the store.
86
+ #
56
87
  def get_lookup(name)
57
88
  unless defined?(@lookups)
58
89
  @lookups = {}
@@ -63,11 +94,15 @@ module Geocoder
63
94
  @lookups[name]
64
95
  end
65
96
 
97
+ ##
98
+ # Spawn a Lookup of the given name.
99
+ #
66
100
  def spawn_lookup(name)
67
101
  if valid_lookups.include?(name)
68
102
  name = name.to_s
69
103
  require "geocoder/lookups/#{name}"
70
- eval("Geocoder::Lookup::#{name[0...1].upcase + name[1..-1]}.new")
104
+ klass = name.split("_").map{ |i| i[0...1].upcase + i[1..-1] }.join
105
+ eval("Geocoder::Lookup::#{klass}.new")
71
106
  else
72
107
  valids = valid_lookups.map{ |l| ":#{l}" }.join(", ")
73
108
  raise ConfigurationError, "Please specify a valid lookup for Geocoder " +
@@ -75,8 +110,11 @@ module Geocoder
75
110
  end
76
111
  end
77
112
 
113
+ ##
114
+ # Array of valid Lookup names.
115
+ #
78
116
  def valid_lookups
79
- [:google, :yahoo, :freegeoip]
117
+ [:google, :yahoo, :geocoder_ca, :freegeoip]
80
118
  end
81
119
 
82
120
  ##
@@ -93,8 +131,12 @@ module Geocoder
93
131
  # Is the given search query blank? (ie, should we not bother searching?)
94
132
  #
95
133
  def blank_query?(value)
96
- !value.to_s.match(/[A-z0-9]/)
134
+ !!value.to_s.match(/^\s*$/)
97
135
  end
98
136
  end
99
137
 
100
- Geocoder::Railtie.insert
138
+ # load Railtie if Rails exists
139
+ if defined?(Rails)
140
+ require "geocoder/railtie"
141
+ Geocoder::Railtie.insert
142
+ end
@@ -0,0 +1,70 @@
1
+ module Geocoder
2
+ class Cache
3
+
4
+ def initialize(store, prefix)
5
+ @store = store
6
+ @prefix = prefix
7
+ end
8
+
9
+ ##
10
+ # Read from the Cache.
11
+ #
12
+ def [](url)
13
+ interpret store[key_for(url)]
14
+ end
15
+
16
+ ##
17
+ # Write to the Cache.
18
+ #
19
+ def []=(url, value)
20
+ store[key_for(url)] = value
21
+ end
22
+
23
+ ##
24
+ # Expire cache entry for given URL,
25
+ # or pass <tt>:all</tt> to expire everything.
26
+ #
27
+ def expire(url)
28
+ if url == :all
29
+ urls.each{ |u| expire(u) }
30
+ else
31
+ self[url] = nil
32
+ end
33
+ end
34
+
35
+
36
+ private # ----------------------------------------------------------------
37
+
38
+ attr_reader :prefix, :store
39
+
40
+ ##
41
+ # Cache key for a given URL.
42
+ #
43
+ def key_for(url)
44
+ [prefix, url].join
45
+ end
46
+
47
+ ##
48
+ # Array of keys with the currently configured prefix
49
+ # that have non-nil values.
50
+ #
51
+ def keys
52
+ store.keys.select{ |k| k.match /^#{prefix}/ and interpret(store[k]) }
53
+ end
54
+
55
+ ##
56
+ # Array of cached URLs.
57
+ #
58
+ def urls
59
+ keys.map{ |k| k[/^#{prefix}(.*)/, 1] }
60
+ end
61
+
62
+ ##
63
+ # Clean up value before returning. Namely, convert empty string to nil.
64
+ # (Some key/value stores return empty string instead of nil.)
65
+ #
66
+ def interpret(value)
67
+ value == "" ? nil : value
68
+ end
69
+ end
70
+ end