artofmission-Geokit 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2007 Bill Eisenhauer & Andre Lewis
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,451 @@
1
+ ## FEATURE SUMMARY
2
+
3
+ This plugin provides key functionality for location-oriented Rails applications:
4
+
5
+ - Distance calculations, for both flat and spherical environments. For example,
6
+ given the location of two points on the earth, you can calculate the miles/KM
7
+ between them.
8
+ - ActiveRecord distance-based finders. For example, you can find all the points
9
+ in your database within a 50-mile radius.
10
+ - Geocoding from multiple providers. It currently supports Google, Yahoo,
11
+ Geocoder.us, and Geocoder.ca geocoders, and it provides a uniform response
12
+ structure from all of them. It also provides a fail-over mechanism, in case
13
+ your input fails to geocode in one service.
14
+ - IP-based location lookup utilizing hostip.info. Provide an IP address, and get
15
+ city name and latitude/longitude in return
16
+ - A before_filter helper to geocoder the user's location based on IP address,
17
+ and retain the location in a cookie.
18
+
19
+ The goal of this plugin is to provide the common functionality for location-oriented
20
+ applications (geocoding, location lookup, distance calculation) in an easy-to-use
21
+ package.
22
+
23
+ ## A NOTE ON TERMINOLOGY
24
+
25
+ Throughout the code and API of this, latitude and longitude are referred to as lat
26
+ and lng. We've found over the long term the abbreviation saves lots of typing time.
27
+
28
+ ## DISTANCE CALCULATIONS AND QUERIES
29
+
30
+ If you want only distance calculation services, you need only mix in the Mappable
31
+ module like so:
32
+
33
+ class Location
34
+ include GeoKit::Mappable
35
+ end
36
+
37
+ After doing so, you can do things like:
38
+
39
+ Location.distance_between(from, to)
40
+
41
+ with optional parameters :units and :formula. Values for :units can be :miles or
42
+ :kms with :miles as the default. Values for :formula can be :sphere or :flat with
43
+ :sphere as the default. :sphere gives you Haversine calculations, while :flat
44
+ gives the Pythagoreum Theory. These defaults persist through out the plug-in.
45
+
46
+ You can also do:
47
+
48
+ location.distance_to(other)
49
+
50
+ The real power and utility of the plug-in is in its query support. This is
51
+ achieved through mixing into an ActiveRecord model object:
52
+
53
+ class Location < ActiveRecord::Base
54
+ acts_as_mappable
55
+ end
56
+
57
+ The plug-in uses the above-mentioned defaults, but can be modified to use
58
+ different units and a different formulae. This is done through the :default_units
59
+ and :default_formula keys which accept the same values as mentioned above.
60
+
61
+ The plug-in creates a calculated column and potentially a calculated condition.
62
+ By default, these are known as "distance" but this can be changed through the
63
+ :distance_field_name key.
64
+
65
+ So, an alternative invocation would look as below:
66
+
67
+ class Location < ActiveRecord::Base
68
+ acts_as_mappable :default_units => :kms,
69
+ :default_formula => :flat,
70
+ :distance_field_name => :distance
71
+ end
72
+
73
+ You can also define alternative column names for latitude and longitude using
74
+ the :lat_column_name and :lng_column_name keys. The defaults are 'lat' and
75
+ 'lng' respectively.
76
+
77
+ Thereafter, a set of finder methods are made available. Below are the
78
+ different combinations:
79
+
80
+ Origin as a two-element array of latititude/longitude:
81
+
82
+ find(:all, :origin => [37.792,-122.393])
83
+
84
+ Origin as a geocodeable string:
85
+
86
+ find(:all, :origin => '100 Spear st, San Francisco, CA')
87
+
88
+ Origin as an object which responds to lat and lng methods,
89
+ or latitude and longitude methods, or whatever methods you have
90
+ specified for lng_column_name and lat_column_name:
91
+
92
+ find(:all, :origin=>my_store) # my_store.lat and my_store.lng methods exist
93
+
94
+ Often you will need to find within a certain distance. The prefered syntax is:
95
+
96
+ find(:all, :origin => @somewhere, :within => 5)
97
+
98
+ . . . however these syntaxes will also work:
99
+
100
+ find_within(5, :origin => @somewhere)
101
+ find(:all, :origin => @somewhere, :conditions => "distance < 5")
102
+
103
+ Note however that the third form should be avoided. With either of the first two,
104
+ GeoKit automatically adds a bounding box to speed up the radial query in the database.
105
+ With the third form, it does not.
106
+
107
+ If you need to combine distance conditions with other conditions, you should do
108
+ so like this:
109
+
110
+ find(:all, :origin => @somewhere, :within => 5, :conditions=>['state=?',state])
111
+
112
+ If :origin is not provided in the finder call, the find method
113
+ works as normal. Further, the key is removed
114
+ from the :options hash prior to invoking the superclass behavior.
115
+
116
+ Other convenience methods work intuitively and are as follows:
117
+
118
+ find_within(distance, :origin => @somewhere)
119
+ find_beyond(distance, :origin => @somewhere)
120
+ find_closest(:origin => @somewhere)
121
+ find_farthest(:origin => @somewhere)
122
+
123
+ where the options respect the defaults, but can be overridden if
124
+ desired.
125
+
126
+ Lastly, if all that is desired is the raw SQL for distance
127
+ calculations, you can use the following:
128
+
129
+ distance_sql(origin, units=default_units, formula=default_formula)
130
+
131
+ Thereafter, you are free to use it in find_by_sql as you wish.
132
+
133
+ There are methods available to enable you to get the count based upon
134
+ the find condition that you have provided. These all work similarly to
135
+ the finders. So for instance:
136
+
137
+ count(:origin, :conditions => "distance < 5")
138
+ count_within(distance, :origin => @somewhere)
139
+ count_beyond(distance, :origin => @somewhere)
140
+
141
+ ## FINDING WITHIN A BOUNDING BOX
142
+
143
+ If you are displaying points on a map, you probably need to query for whatever falls within the rectangular bounds of the map:
144
+
145
+ Store.find :all, :bounds=>[sw_point,ne_point]
146
+
147
+ The input to :bounds can be array with the two points or a Bounds object. However you provide them, the order should always be the southwest corner, northeast corner of the rectangle. Typically, you will be getting the sw_point and ne_point from a map that is displayed on a web page.
148
+
149
+ If you need to calculate the bounding box from a point and radius, you can do that:
150
+
151
+ bounds=Bounds.from_point_and_radius(home,5)
152
+ Store.find :all, :bounds=>bounds
153
+
154
+ ## USING INCLUDES
155
+
156
+ You can use includes along with your distance finders:
157
+
158
+ stores=Store.find :all, :origin=>home, :include=>[:reviews,:cities] :within=>5, :order=>'distance'
159
+
160
+ *However*, ActiveRecord drops the calculated distance column when you use include. So, if you need to
161
+ use the distance column, you'll have to re-calculate it post-query in Ruby:
162
+
163
+ stores.sort_by_distance_from(home)
164
+
165
+ In this case, you may want to just use the bounding box
166
+ condition alone in your SQL (there's no use calculating the distance twice):
167
+
168
+ bounds=Bounds.from_point_and_radius(home,5)
169
+ stores=Store.find :all, :include=>[:reviews,:cities] :bounds=>bounds
170
+ stores.sort_by_distance_from(home)
171
+
172
+ ## IP GEOCODING
173
+
174
+ You can obtain the location for an IP at any time using the geocoder
175
+ as in the following example:
176
+
177
+ location = IpGeocoder.geocode('12.215.42.19')
178
+
179
+ where Location is a GeoLoc instance containing the latitude,
180
+ longitude, city, state, and country code. Also, the success
181
+ value is true.
182
+
183
+ If the IP cannot be geocoded, a GeoLoc instance is returned with a
184
+ success value of false.
185
+
186
+ It should be noted that the IP address needs to be visible to the
187
+ Rails application. In other words, you need to ensure that the
188
+ requesting IP address is forwarded by any front-end servers that
189
+ are out in front of the Rails app. Otherwise, the IP will always
190
+ be that of the front-end server.
191
+
192
+ ## IP GEOCODING HELPER
193
+
194
+ A class method called geocode_ip_address has been mixed into the
195
+ ActionController::Base. This enables before_filter style lookup of
196
+ the IP address. Since it is a filter, it can accept any of the
197
+ available filter options.
198
+
199
+ Usage is as below:
200
+
201
+ class LocationAwareController < ActionController::Base
202
+ geocode_ip_address
203
+ end
204
+
205
+ A first-time lookup will result in the GeoLoc class being stored
206
+ in the session as :geo_location as well as in a cookie called
207
+ :geo_session. Subsequent lookups will use the session value if it
208
+ exists or the cookie value if it doesn't exist. The last resort is
209
+ to make a call to the web service. Clients are free to manage the
210
+ cookie as they wish.
211
+
212
+ The intent of this feature is to be able to provide a good guess as
213
+ to a new visitor's location.
214
+
215
+ ## INTEGRATED FIND AND GEOCODING
216
+
217
+ Geocoding has been integrated with the finders enabling you to pass
218
+ a physical address or an IP address. This would look the following:
219
+
220
+ Location.find_farthest(:origin => '217.15.10.9')
221
+ Location.find_farthest(:origin => 'Irving, TX')
222
+
223
+ where the IP or physical address would be geocoded to a location and
224
+ then the resulting latitude and longitude coordinates would be used
225
+ in the find. This is not expected to be common usage, but it can be
226
+ done nevertheless.
227
+
228
+ ## ADDRESS GEOCODING
229
+
230
+ GeoKit can geocode addresses using multiple geocodeing web services.
231
+ Currently, GeoKit supports Google, Yahoo, and Geocoder.us geocoding
232
+ services.
233
+
234
+ These geocoder services are made available through three classes:
235
+ GoogleGeocoder, YahooGeocoder, and UsGeocoder. Further, an additional
236
+ geocoder class called MultiGeocoder incorporates an ordered failover
237
+ sequence to increase the probability of successful geocoding.
238
+
239
+ All classes are called using the following signature:
240
+
241
+ include GeoKit::Geocoders
242
+ location = XxxGeocoder.geocode(address)
243
+
244
+ where you replace Xxx Geocoder with the appropriate class. A GeoLoc
245
+ instance is the result of the call. This class has a "success"
246
+ attribute which will be true if a successful geocoding occurred.
247
+ If successful, the lat and lng properties will be populated.
248
+
249
+ Geocoders are named with the naming convention NameGeocoder. This
250
+ naming convention enables Geocoder to auto-detect its sub-classes
251
+ in order to create methods called name_geocoder(address) so that
252
+ all geocoders are called through the base class. This is done
253
+ purely for convenience; the individual geocoder classes are expected
254
+ to be used independently.
255
+
256
+ The MultiGeocoder class requires the configuration of a provider
257
+ order which dictates what order to use the various geocoders. Ordering
258
+ is done through the PROVIDER_ORDER constant found in environment.rb.
259
+
260
+ On installation, this plugin appends a template for your API keys to
261
+ your environment.rb.
262
+
263
+ Make sure your failover configuration matches the usage characteristics
264
+ of your application -- for example, if you routinely get bogus input to
265
+ geocode, your code will be much slower if you have to failover among
266
+ multiple geocoders before determining that the input was in fact bogus.
267
+
268
+ The Geocoder.geocode method returns a GeoLoc object. Basic usage:
269
+
270
+ loc=Geocoder.geocode('100 Spear St, San Francisco, CA')
271
+ if loc.success
272
+ puts loc.lat
273
+ puts loc.lng
274
+ puts loc.full_address
275
+ end
276
+
277
+ ## INTEGRATED FIND WITH ADDRESS GEOCODING
278
+
279
+ Just has you can pass an IP address directly into an ActiveRecord finder
280
+ as the origin, you can also pass a physical address as the origin:
281
+
282
+ Location.find_closest(:origin => '100 Spear st, San Francisco, CA')
283
+
284
+ where the physical address would be geocoded to a location and then the
285
+ resulting latitude and longitude coordinates would be used in the
286
+ find.
287
+
288
+ Note that if the address fails to geocode, the find method will raise an
289
+ ActiveRecord::GeocodeError you must be prepared to catch. Alternatively,
290
+ You can geocoder the address beforehand, and pass the resulting lat/lng
291
+ into the finder if successful.
292
+
293
+ ## Auto Geocoding
294
+
295
+ If your geocoding needs are simple, you can tell your model to automatically
296
+ geocode itself on create:
297
+
298
+ class Store < ActiveRecord::Base
299
+ acts_as_mappable :auto_geocode=>true
300
+ end
301
+
302
+ It takes two optional params:
303
+
304
+ class Store < ActiveRecord::Base
305
+ acts_as_mappable :auto_geocode=>{:field=>:address, :error_message=>'Could not geocode address'}
306
+ end
307
+
308
+ . . . which is equivilent to:
309
+
310
+ class Store << ActiveRecord::Base
311
+ acts_as_mappable
312
+ before_validation_on_create :geocode_address
313
+
314
+ private
315
+ def geocode_address
316
+ geo=GeoKit::Geocoders::MultiGeocoder.geocode (address)
317
+ errors.add(:address, "Could not Geocode address") if !geo.success
318
+ self.lat, self.lng = geo.lat,geo.lng if geo.success
319
+ end
320
+ end
321
+
322
+ If you need any more complicated geocoding behavior for your model, you should roll your own
323
+ before_validate callback.
324
+
325
+
326
+ ## Distances, headings, endpoints, and midpoints
327
+
328
+ distance=home.distance_from(work, :units=>:miles)
329
+ heading=home.heading_to(work) # result is in degrees, 0 is north
330
+ endpoint=home.endpoint(90,2) # two miles due east
331
+ midpoing=home.midpoint_to(work)
332
+
333
+ ## Cool stuff you can do with bounds
334
+
335
+ bounds=Bounds.new(sw_point,ne_point)
336
+ bounds.contains?(home)
337
+ puts bounds.center
338
+
339
+
340
+ HOW TO . . .
341
+ =================================================================================
342
+
343
+ ## How to install the GeoKit plugin
344
+ cd [APP_ROOT]
345
+ ruby script/plugin install svn://rubyforge.org/var/svn/geokit/trunk
346
+ or, to install as an external (your project must be version controlled):
347
+ ruby script/plugin install -x svn://rubyforge.org/var/svn/geokit/trunk
348
+
349
+ ## How to find all stores within a 10-mile radius of a given lat/lng
350
+ 1. ensure your stores table has lat and lng columns with numeric or float
351
+ datatypes to store your latitude/longitude
352
+
353
+ 3. use acts_as_mappable on your store model:
354
+ class Store < ActiveRecord::Base
355
+ acts_as_mappable
356
+ ...
357
+ end
358
+ 3. finders now have extra capabilities:
359
+ Store.find(:all, :origin =>[32.951613,-96.958444], :within=>10)
360
+
361
+ ## How to geocode an address
362
+
363
+ 1. configure your geocoder key(s) in environment.rb
364
+
365
+ 2. also in environment.rb, make sure that PROVIDER_ORDER reflects the
366
+ geocoder(s). If you only want to use one geocoder, there should
367
+ be only one symbol in the array. For example:
368
+ PROVIDER_ORDER=[:google]
369
+
370
+ 3. Test it out in script/console
371
+ include GeoKit::Geocoders
372
+ res = MultiGeocoder.geocode('100 Spear St, San Francisco, CA')
373
+ puts res.lat
374
+ puts res.lng
375
+ puts res.full_address
376
+ ... etc. The return type is GeoLoc, see the API for
377
+ all the methods you can call on it.
378
+
379
+ ## How to find all stores within 10 miles of a given address
380
+
381
+ 1. as above, ensure your table has the lat/lng columns, and you've
382
+ applied acts_as_mappable to the Store model.
383
+
384
+ 2. configure and test out your geocoder, as above
385
+
386
+ 3. pass the address in under the :origin key
387
+ Store.find(:all, :origin=>'100 Spear st, San Francisco, CA',
388
+ :within=>10)
389
+
390
+ 4. you can also use a zipcode, or anything else that's geocodable:
391
+ Store.find(:all, :origin=>'94117',
392
+ :conditions=>'distance<10')
393
+
394
+ ## How to sort a query by distance from an origin
395
+
396
+ You now have access to a 'distance' column, and you can use it
397
+ as you would any other column. For example:
398
+ Store.find(:all, :origin=>'94117', :order=>'distance')
399
+
400
+ ## How to elements of an array according to distance from a common point
401
+
402
+ Usually, you can do your sorting in the database as part of your find call.
403
+ If you need to sort things post-query, you can do so:
404
+
405
+ stores=Store.find :all
406
+ stores.sort_by_distance_from(home)
407
+ puts stores.first.distance
408
+
409
+ Obviously, each of the items in the array must have a latitude/longitude so
410
+ they can be sorted by distance.
411
+
412
+ Database Compatability
413
+ =================================================================================
414
+ GeoKit does *not* work with SQLite, as it lacks the necessary geometry functions.
415
+ GeoKit works with MySQL (tested with version 5.0.41) or PostgreSQL (tested with version 8.2.6)
416
+ GeoKit is known to *not* work with Postgres <8.1 -- it uses the least() funciton.
417
+
418
+
419
+ HIGH-LEVEL NOTES ON WHAT'S WHERE
420
+ =================================================================================
421
+
422
+ acts_as_mappable.rb, as you'd expect, contains the ActsAsMappable
423
+ module which gets mixed into your models to provide the
424
+ location-based finder goodness.
425
+
426
+ mappable.rb contains the Mappable module, which provides basic
427
+ distance calculation methods, i.e., calculating the distance
428
+ between two points.
429
+
430
+ mappable.rb also contains LatLng, GeoLoc, and Bounds.
431
+ LatLng is a simple container for latitude and longitude, but
432
+ it's made more powerful by mixing in the above-mentioned Mappable
433
+ module -- therefore, you can calculate easily the distance between two
434
+ LatLng ojbects with distance = first.distance_to(other)
435
+
436
+ GeoLoc (also in mappable.rb) represents an address or location which
437
+ has been geocoded. You can get the city, zipcode, street address, etc.
438
+ from a GeoLoc object. GeoLoc extends LatLng, so you also get lat/lng
439
+ AND the Mappable modeule goodness for free.
440
+
441
+ geocoders.rb contains the geocoder classes.
442
+
443
+ ip_geocode_lookup.rb contains the before_filter helper method which
444
+ enables auto lookup of the requesting IP address.
445
+
446
+ ## IMPORTANT NOTE: We have appended to your environment.rb file
447
+
448
+ Installation of this plugin has appended an API key template
449
+ to your environment.rb file. You *must* add your own keys for the various
450
+ geocoding services if you want to use geocoding. If you need to refer to the original
451
+ template again, see the api_keys_template file in the root of the plugin.