geokit-rails3 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown DELETED
@@ -1,554 +0,0 @@
1
- ## INSTALLATION
2
-
3
- Geokit consists of a Gem ([geokit-gem](http://github.com/andre/geokit-gem/tree/master)) and a Rails plugin ([geokit-rails3](http://github.com/andre/geokit-rails3/tree/master)).
4
-
5
- Make sure you use a version >= 3.0 of Rails.
6
-
7
- You just have to add the 'geokit-rails3' gem to your Gemfile
8
-
9
- gem 'geokit-rails3'
10
-
11
- Then tell bundler to update the gems :
12
-
13
- $ bundle install
14
-
15
- If you want to use geokit-rails3 in a Rails 2 application, just use the good old plugin.
16
-
17
-
18
- ## FEATURE SUMMARY
19
-
20
- Geokit provides key functionality for location-oriented Rails applications:
21
-
22
- - Distance calculations, for both flat and spherical environments. For example,
23
- given the location of two points on the earth, you can calculate the miles/KM
24
- between them.
25
- - ActiveRecord distance-based finders. For example, you can find all the points
26
- in your database within a 50-mile radius.
27
- - IP-based location lookup utilizing hostip.info. Provide an IP address, and get
28
- city name and latitude/longitude in return
29
- - A before_filter helper to geocoder the user's location based on IP address,
30
- and retain the location in a cookie.
31
- - Geocoding from multiple providers. It provides a fail-over mechanism, in case
32
- your input fails to geocode in one service. Geocoding is provided buy the Geokit
33
- gem, which you must have installed
34
-
35
- The goal of this plugin is to provide the common functionality for location-oriented
36
- applications (geocoding, location lookup, distance calculation) in an easy-to-use
37
- package.
38
-
39
- ## A NOTE ON TERMINOLOGY
40
-
41
- Throughout the code and API, latitude and longitude are referred to as lat
42
- and lng. We've found over the long term the abbreviation saves lots of typing time.
43
-
44
- ## LOCATION QUERIES
45
-
46
- To get started, just specify an ActiveRecord class as `acts_as_mappale`:
47
-
48
- class Location < ActiveRecord::Base
49
- acts_as_mappable
50
- end
51
-
52
- There are some defaults you can override:
53
-
54
- class Location < ActiveRecord::Base
55
- acts_as_mappable :default_units => :miles,
56
- :default_formula => :sphere,
57
- :distance_field_name => :distance,
58
- :lat_column_name => :lat,
59
- :lng_column_name => :lng
60
- end
61
-
62
-
63
- The optional parameters are `units`, `formula`, and `distance_field_name`.
64
- Values for **units** can be `:miles`, `:kms` (kilometers), or `:nms` (nautical miles),
65
- with `:miles` as the default. Values for **formula** can be `:sphere` or `:flat` with
66
- `:sphere` as the default. `:sphere` gives you Haversine calculations, while `:flat`
67
- gives the Pythagoreum Theory. These defaults persist through out the gem.
68
-
69
- The plug-in creates a calculated `distance` field on AR instances that have
70
- been retrieved throw a Geokit location query. By default, these fields are
71
- known as "distance" but this can be changed through the `:distance_field_name` key.
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
- Once you've specified `acts_as_mappable`, a set of distance-based
78
- scopes are available:
79
-
80
- Origin as a two-element array of latititude/longitude:
81
-
82
- Location.origin([37.792,-122.393])
83
-
84
- Origin as a geocodeable string:
85
-
86
- Location.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
- Location.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
- Location.origin(@somewhere).within(5)
97
-
98
- . . . however these syntaxes will also work:
99
-
100
- find.origin(@somewhere).where("distance < 5")
101
-
102
- Note however that the second form should be avoided. With the first,
103
- Geokit automatically adds a bounding box to speed up the radial query in the database.
104
- With the second form, it does not.
105
-
106
- If you need to combine distance conditions with other conditions, you should do
107
- so like this:
108
-
109
- Location.origin(@somewhere).within(5).where(:state => state)
110
-
111
- If the _within_ scope is called without an _origin_ scope, it is simply ignored.
112
-
113
- Other convenience scopes work intuitively and are as follows:
114
-
115
- Location.origin(@somewhere).beyond(5)
116
- Location.closest(@somewhere)
117
- Location.farthest(@somewhere)
118
-
119
- The `closest` and `farthest` scopes just add a `limit(1)` in the scopes chain.
120
-
121
- Lastly, if all that is desired is the raw SQL for distance
122
- calculations, you can use the following:
123
-
124
- Location.distance_sql(origin, units=default_units, formula=default_formula)
125
-
126
- Thereafter, you are free to use it in `find_by_sql` as you wish.
127
-
128
- There are methods available to enable you to get the count based upon
129
- the find condition that you have provided. These all work similarly to
130
- the finders. So for instance:
131
-
132
- You can then chain these scope with any other or use a "calling" method like `first`, `all`, `count`, …
133
-
134
- Location.origin(@somewhere).within(5).all
135
- Location.origin([37.792,-122.393]).first
136
-
137
- ## FINDING WITHIN A BOUNDING BOX
138
-
139
- If you are displaying points on a map, you probably need to query for whatever falls within the rectangular bounds of the map:
140
-
141
- Store.bounds([sw_point,ne_point]).all
142
-
143
- 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.
144
-
145
- If you need to calculate the bounding box from a point and radius, you can do that:
146
-
147
- bounds=Bounds.from_point_and_radius(home,5)
148
- Store.bounds(bounds).all
149
-
150
- <!-- End of the first batch of "updates" -->
151
-
152
- ## USING INCLUDES
153
-
154
- You can use includes along with your distance finders:
155
-
156
- stores=Store.find :all, :origin=>home, :include=>[:reviews,:cities] :within=>5, :order=>'distance'
157
-
158
- *However*, ActiveRecord drops the calculated distance column when you use include. So, if you need to
159
- use the distance column, you'll have to re-calculate it post-query in Ruby:
160
-
161
- stores.sort_by_distance_from(home)
162
-
163
- In this case, you may want to just use the bounding box
164
- condition alone in your SQL (there's no use calculating the distance twice):
165
-
166
- bounds=Bounds.from_point_and_radius(home,5)
167
- stores=Store.find :all, :include=>[:reviews,:cities] :bounds=>bounds
168
- stores.sort_by_distance_from(home)
169
-
170
- ## USING :through
171
-
172
- You can also specify a model as mappable "through" another associated model.
173
- In other words, that associated model is the actual mappable model with
174
- "lat" and "lng" attributes, but this "through" model can still utilize
175
- all of the above find methods to search for records.
176
-
177
- class Location < ActiveRecord::Base
178
- belongs_to :locatable, :polymorphic => true
179
- acts_as_mappable
180
- end
181
-
182
- class Company < ActiveRecord::Base
183
- has_one :location, :as => :locatable # also works for belongs_to associations
184
- acts_as_mappable :through => :location
185
- end
186
-
187
- Then you can still call:
188
-
189
- Company.find_within(distance, :origin => @somewhere)
190
-
191
- You can also give :through a hash if you location is nested deep. For example, given:
192
-
193
- class House
194
- acts_as_mappable
195
- end
196
-
197
- class Family
198
- belongs_to :house
199
- end
200
-
201
- class Person
202
- belongs_to :family
203
- acts_as_mappable :through => { :family => :house }
204
- end
205
-
206
- Remember that the notes above about USING INCLUDES apply to the results from
207
- this find, since an include is automatically used.
208
-
209
- ## IP GEOCODING
210
-
211
- You can obtain the location for an IP at any time using the geocoder
212
- as in the following example:
213
-
214
- location = IpGeocoder.geocode('12.215.42.19')
215
-
216
- where Location is a GeoLoc instance containing the latitude,
217
- longitude, city, state, and country code. Also, the success
218
- value is true.
219
-
220
- If the IP cannot be geocoded, a GeoLoc instance is returned with a
221
- success value of false.
222
-
223
- It should be noted that the IP address needs to be visible to the
224
- Rails application. In other words, you need to ensure that the
225
- requesting IP address is forwarded by any front-end servers that
226
- are out in front of the Rails app. Otherwise, the IP will always
227
- be that of the front-end server.
228
-
229
- The Multi-Geocoder will also geocode IP addresses and provide
230
- failover among multiple IP geocoders. Just pass in an IP address for the
231
- parameter instead of a street address. Eg:
232
-
233
- location = Geocoders::MultiGeocoder.geocode('12.215.42.19')
234
-
235
- The MultiGeocoder class requires 2 configuration setting for the provider order.
236
- Ordering is done through `Geokit::Geocoders::provider_order` and
237
- `Geokit::Geocoders::ip_provider_order`, found in
238
- `config/initializers/geokit_config.rb`. If you don't already have a
239
- `geokit_config.rb` file, the plugin creates one when it is first installed.
240
-
241
-
242
- ## IP GEOCODING HELPER
243
-
244
- A class method called geocode_ip_address has been mixed into the
245
- ActionController::Base. This enables before_filter style lookup of
246
- the IP address. Since it is a filter, it can accept any of the
247
- available filter options.
248
-
249
- Usage is as below:
250
-
251
- class LocationAwareController < ActionController::Base
252
- geocode_ip_address
253
- end
254
-
255
- A first-time lookup will result in the GeoLoc class being stored
256
- in the session as `:geo_location` as well as in a cookie called
257
- `:geo_session`. Subsequent lookups will use the session value if it
258
- exists or the cookie value if it doesn't exist. The last resort is
259
- to make a call to the web service. Clients are free to manage the
260
- cookie as they wish.
261
-
262
- The intent of this feature is to be able to provide a good guess as
263
- to a new visitor's location.
264
-
265
- ## INTEGRATED FIND AND GEOCODING
266
-
267
- Geocoding has been integrated with the finders enabling you to pass
268
- a physical address or an IP address. This would look the following:
269
-
270
- Location.find_farthest(:origin => '217.15.10.9')
271
- Location.find_farthest(:origin => 'Irving, TX')
272
-
273
- where the IP or physical address would be geocoded to a location and
274
- then the resulting latitude and longitude coordinates would be used
275
- in the find. This is not expected to be common usage, but it can be
276
- done nevertheless.
277
-
278
- ## ADDRESS GEOCODING
279
-
280
- Geocoding is provided by the Geokit gem, which is required for this plugin.
281
- See the top of this file for instructions on installing the Geokit gem.
282
-
283
- Geokit can geocode addresses using multiple geocodeing web services.
284
- Geokit supports services like Google, Yahoo, and Geocoder.us, and more --
285
- see the Geokit gem API for a complete list.
286
-
287
- These geocoder services are made available through the following classes:
288
- GoogleGeocoder, YahooGeocoder, UsGeocoder, CaGeocoder, and GeonamesGeocoder.
289
- Further, an additional geocoder class called MultiGeocoder incorporates an ordered failover
290
- sequence to increase the probability of successful geocoding.
291
-
292
- All classes are called using the following signature:
293
-
294
- include Geokit::Geocoders
295
- location = XxxGeocoder.geocode(address)
296
-
297
- where you replace Xxx Geocoder with the appropriate class. A GeoLoc
298
- instance is the result of the call. This class has a "success"
299
- attribute which will be true if a successful geocoding occurred.
300
- If successful, the lat and lng properties will be populated.
301
-
302
- Geocoders are named with the convention NameGeocoder. This
303
- naming convention enables Geocoder to auto-detect its sub-classes
304
- in order to create methods called `name_geocoder(address)` so that
305
- all geocoders can be called through the base class. This is done
306
- purely for convenience; the individual geocoder classes are expected
307
- to be used independently.
308
-
309
- The MultiGeocoder class requires the configuration of a provider
310
- order which dictates what order to use the various geocoders. Ordering
311
- is done through `Geokit::Geocoders::provider_order`, found in
312
- `config/initializers/geokit_config.rb`.
313
-
314
- If you don't already have a `geokit_config.rb` file, the plugin creates one
315
- when it is first installed.
316
-
317
- Make sure your failover configuration matches the usage characteristics
318
- of your application -- for example, if you routinely get bogus input to
319
- geocode, your code will be much slower if you have to failover among
320
- multiple geocoders before determining that the input was in fact bogus.
321
-
322
- The Geocoder.geocode method returns a GeoLoc object. Basic usage:
323
-
324
- loc=Geocoder.geocode('100 Spear St, San Francisco, CA')
325
- if loc.success
326
- puts loc.lat
327
- puts loc.lng
328
- puts loc.full_address
329
- end
330
-
331
- ## REVERSE GEOCODING
332
-
333
- Currently, only the Google Geocoder supports reverse geocoding.
334
- Pass the lat/lng as a string, array or LatLng instance:
335
-
336
- res=Geokit::Geocoders::GoogleGeocoder.reverse_geocode "37.791821,-122.394679"
337
- => #<Geokit::GeoLoc:0x558ed0 ...
338
- res.full_address
339
- "101-115 Main St, San Francisco, CA 94105, USA"
340
-
341
- The address will usually appear as a range, as it does in the above example.
342
-
343
-
344
- ## INTEGRATED FIND WITH ADDRESS GEOCODING
345
-
346
- Just has you can pass an IP address directly into an ActiveRecord finder
347
- as the origin, you can also pass a physical address as the origin:
348
-
349
- Location.find_closest(:origin => '100 Spear st, San Francisco, CA')
350
-
351
- where the physical address would be geocoded to a location and then the
352
- resulting latitude and longitude coordinates would be used in the
353
- find.
354
-
355
- Note that if the address fails to geocode, the find method will raise an
356
- ActiveRecord::GeocodeError you must be prepared to catch. Alternatively,
357
- You can geocoder the address beforehand, and pass the resulting lat/lng
358
- into the finder if successful.
359
-
360
- ## Auto Geocoding
361
-
362
- If your geocoding needs are simple, you can tell your model to automatically
363
- geocode itself on create:
364
-
365
- class Store < ActiveRecord::Base
366
- acts_as_mappable :auto_geocode=>true
367
- end
368
-
369
- It takes two optional params:
370
-
371
- class Store < ActiveRecord::Base
372
- acts_as_mappable :auto_geocode=>{:field=>:address, :error_message=>'Could not geocode address'}
373
- end
374
-
375
- . . . which is equivilent to:
376
-
377
- class Store << ActiveRecord::Base
378
- acts_as_mappable
379
- before_validation_on_create :geocode_address
380
-
381
- private
382
- def geocode_address
383
- geo=Geokit::Geocoders::MultiGeocoder.geocode (address)
384
- errors.add(:address, "Could not Geocode address") if !geo.success
385
- self.lat, self.lng = geo.lat,geo.lng if geo.success
386
- end
387
- end
388
-
389
- If you need any more complicated geocoding behavior for your model, you should roll your own
390
- `before_validate` callback.
391
-
392
-
393
- ## Distances, headings, endpoints, and midpoints
394
-
395
- distance=home.distance_from(work, :units=>:miles)
396
- heading=home.heading_to(work) # result is in degrees, 0 is north
397
- endpoint=home.endpoint(90,2) # two miles due east
398
- midpoing=home.midpoint_to(work)
399
-
400
- ## Cool stuff you can do with bounds
401
-
402
- bounds=Bounds.new(sw_point,ne_point)
403
- bounds.contains?(home)
404
- puts bounds.center
405
-
406
-
407
- HOW TO . . .
408
- =================================================================================
409
-
410
- A few quick examples to get you started ....
411
-
412
- ## How to install the Geokit Rails plugin
413
- (See the very top of this file)
414
-
415
- ## How to find all stores within a 10-mile radius of a given lat/lng
416
- 1. ensure your stores table has lat and lng columns with numeric or float
417
- datatypes to store your latitude/longitude
418
-
419
- 3. use `acts_as_mappable` on your store model:
420
-
421
- class Store < ActiveRecord::Base
422
- acts_as_mappable
423
- ...
424
- end
425
-
426
- 3. finders now have extra capabilities:
427
-
428
- Store.find(:all, :origin =>[32.951613,-96.958444], :within=>10)
429
-
430
- ## How to geocode an address
431
-
432
- 1. configure your geocoder key(s) in `config/initializers/geokit_config.rb`
433
-
434
- 2. also in `geokit_config.rb`, make sure that `Geokit::Geocoders::provider_order` reflects the
435
- geocoder(s). If you only want to use one geocoder, there should
436
- be only one symbol in the array. For example:
437
-
438
- Geokit::Geocoders::provider_order=[:google]
439
-
440
- 3. Test it out in script/console
441
-
442
- include Geokit::Geocoders
443
- res = MultiGeocoder.geocode('100 Spear St, San Francisco, CA')
444
- puts res.lat
445
- puts res.lng
446
- puts res.full_address
447
- ... etc. The return type is GeoLoc, see the API for
448
- all the methods you can call on it.
449
-
450
- ## How to find all stores within 10 miles of a given address
451
-
452
- 1. as above, ensure your table has the lat/lng columns, and you've
453
- applied `acts_as_mappable` to the Store model.
454
-
455
- 2. configure and test out your geocoder, as above
456
-
457
- 3. pass the address in under the :origin key
458
-
459
- Store.find(:all, :origin=>'100 Spear st, San Francisco, CA',
460
- :within=>10)
461
-
462
- 4. you can also use a zipcode, or anything else that's geocodable:
463
-
464
- Store.find(:all, :origin=>'94117',
465
- :conditions=>'distance<10')
466
-
467
- ## How to sort a query by distance from an origin
468
-
469
- You now have access to a 'distance' column, and you can use it
470
- as you would any other column. For example:
471
- Store.find(:all, :origin=>'94117', :order=>'distance')
472
-
473
- ## How to elements of an array according to distance from a common point
474
-
475
- Usually, you can do your sorting in the database as part of your find call.
476
- If you need to sort things post-query, you can do so:
477
-
478
- stores=Store.find :all
479
- stores.sort_by_distance_from(home)
480
- puts stores.first.distance
481
-
482
- Obviously, each of the items in the array must have a latitude/longitude so
483
- they can be sorted by distance.
484
-
485
- ## Database indexes
486
-
487
- MySQL can't create indexes on a calculated field such as those Geokit uses to
488
- calculate distance based on latitude/longitude values for a record. However,
489
- indexing the lat and lng columns does improve Geokit distance calculation
490
- performance since the lat and lng columns are used in a straight comparison
491
- for distance calculation. Assuming a Page model that is incorporating the
492
- Geokit plugin the migration would be as follows.
493
-
494
- class AddIndexOPageLatAndLng < ActiveRecord::Migration
495
-
496
- def self.up
497
- add_index :pages, [:lat, :lng]
498
- end
499
-
500
- def self.down
501
- remove_index :pages, [:lat, :lng]
502
- end
503
- end
504
-
505
- ## Database Compatability
506
-
507
- * Geokit works with MySQL (tested with version 5.0.41), PostgreSQL (tested with version 8.2.6) and Microsoft SQL Server (tested with 2000).
508
- * Geokit does *not* work with SQLite, as it lacks the necessary geometry functions.
509
- * Geokit is known to *not* work with Postgres versions under 8.1 -- it uses the least() funciton.
510
-
511
-
512
- ## HIGH-LEVEL NOTES ON WHAT'S WHERE
513
-
514
- `acts_as_mappable.rb`, as you'd expect, contains the ActsAsMappable
515
- module which gets mixed into your models to provide the
516
- location-based finder goodness.
517
-
518
- `ip_geocode_lookup.rb` contains the before_filter helper method which
519
- enables auto lookup of the requesting IP address.
520
-
521
- ### The Geokit gem provides the building blocks of distance-based operations:
522
-
523
- The Mappable module, which provides basic
524
- distance calculation methods, i.e., calculating the distance
525
- between two points.
526
-
527
- The LatLng class is a simple container for latitude and longitude, but
528
- it's made more powerful by mixing in the above-mentioned Mappable
529
- module -- therefore, you can calculate easily the distance between two
530
- LatLng ojbects with `distance = first.distance_to(other)`
531
-
532
- GeoLoc represents an address or location which
533
- has been geocoded. You can get the city, zipcode, street address, etc.
534
- from a GeoLoc object. GeoLoc extends LatLng, so you also get lat/lng
535
- AND the Mappable modeule goodness for free.
536
-
537
- ## GOOGLE GROUP
538
-
539
- Follow the Google Group for updates and discussion on Geokit: http://groups.google.com/group/geokit
540
-
541
- ## IMPORTANT POST-INSTALLATION NOTES:
542
-
543
- *1. The configuration file*: Geokit for Rails uses a configuration file in config/initializers.
544
- You *must* add your own keys for the various geocoding services if you want to use geocoding.
545
- If you need to refer to the original template again, see the `assets/api_keys_template` file.
546
-
547
- *2. The gem dependency*: Geokit for Rails depends on the Geokit gem. Tell Rails about this
548
- dependency in `config/environment.rb`, within the initializer block:
549
- config.gem "geokit"
550
-
551
- *If you're having trouble with dependencies ....*
552
-
553
- Try installing the gem manually (sudo gem install geokit), then adding a `require 'geokit'` to the top of
554
- `vendor/plugins/geokit-rails3/init.rb` and/or `config/geokit_config.rb`.
data/Rakefile DELETED
@@ -1,17 +0,0 @@
1
- require 'bundler'
2
- Bundler::GemHelper.install_tasks
3
-
4
- require 'rake/rdoctask'
5
- Rake::RDocTask.new do |rdoc|
6
- version = File.exist?('VERSION') ? File.read('VERSION') : ""
7
-
8
- rdoc.rdoc_dir = 'rdoc'
9
- rdoc.title = "geokit-rails3 #{version}"
10
- rdoc.rdoc_files.include('README*')
11
- rdoc.rdoc_files.include('lib/**/*.rb')
12
- end
13
-
14
- load 'test/tasks.rake'
15
-
16
- desc 'Default: run unit tests.'
17
- task :default => :test
@@ -1,29 +0,0 @@
1
- # -*- encoding: utf-8 -*-
2
- require File.expand_path("../lib/geokit-rails3/version", __FILE__)
3
-
4
- Gem::Specification.new do |s|
5
- s.name = "geokit-rails3"
6
- s.version = GeokitRails3::VERSION
7
- s.platform = Gem::Platform::RUBY
8
- s.authors = ["Andre Lewis", "Bill Eisenhauer", "Jeremy Lecour"]
9
- s.email = ["andre@earthcode.com", "bill_eisenhauer@yahoo.com", "jeremy.lecour@gmail.com"]
10
- s.homepage = "http://github.com/jlecour/geokit-rails3"
11
- s.summary = "Integrate Geokit with Rails 3"
12
- s.description = "Port of the Rails plugin \"geokit-rails\" to Rails 3, as a gem"
13
-
14
- s.required_rubygems_version = ">= 1.3.6"
15
- # s.rubyforge_project = "test_gem"
16
-
17
- s.add_runtime_dependency 'rails', '~> 3.0.0'
18
- s.add_runtime_dependency 'geokit', '~> 1.5.0'
19
-
20
- s.add_development_dependency "bundler", "~> 1.0.0"
21
- s.add_development_dependency "rcov", "~> 0.9.9"
22
- s.add_development_dependency "mocha", "~> 0.9.8"
23
- s.add_development_dependency "mysql", "~> 2.8.1"
24
-
25
- s.files = `git ls-files`.split("\n")
26
- s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
27
- s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
28
- s.require_paths = ["lib"]
29
- end