geokit-rails3-1beta 0.2.0.beta1 → 0.3.1.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. data/CONFIG.markdown +67 -0
  2. data/Gemfile +3 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.markdown +653 -0
  5. data/Rakefile +17 -0
  6. data/geokit-rails3-1beta.gemspec +31 -0
  7. data/lib/geokit-rails3-1beta.rb +10 -0
  8. data/lib/{geokit-rails3 → geokit-rails3-1beta}/acts_as_mappable.old.rb +0 -0
  9. data/lib/{geokit-rails3 → geokit-rails3-1beta}/acts_as_mappable.rb +0 -0
  10. data/lib/{geokit-rails3 → geokit-rails3-1beta}/adapters/abstract.rb +0 -0
  11. data/lib/{geokit-rails3 → geokit-rails3-1beta}/adapters/mysql.rb +0 -0
  12. data/lib/{geokit-rails3 → geokit-rails3-1beta}/adapters/mysql2.rb +0 -0
  13. data/lib/{geokit-rails3 → geokit-rails3-1beta}/adapters/postgresql.rb +0 -0
  14. data/lib/{geokit-rails3 → geokit-rails3-1beta}/adapters/sqlserver.rb +0 -0
  15. data/lib/{geokit-rails3 → geokit-rails3-1beta}/core_extensions.rb +0 -0
  16. data/lib/{geokit-rails3 → geokit-rails3-1beta}/defaults.rb +0 -0
  17. data/lib/{geokit-rails3 → geokit-rails3-1beta}/geocoder_control.rb +0 -0
  18. data/lib/{geokit-rails3 → geokit-rails3-1beta}/ip_geocode_lookup.rb +0 -0
  19. data/lib/{geokit-rails3 → geokit-rails3-1beta}/railtie.rb +3 -3
  20. data/lib/geokit-rails3-1beta/version.rb +3 -0
  21. metadata +26 -69
  22. data/lib/geokit-rails3.rb +0 -10
  23. data/lib/geokit-rails3/version.rb +0 -3
  24. data/test/acts_as_mappable_test.rb +0 -420
  25. data/test/boot.rb +0 -32
  26. data/test/database.yml +0 -20
  27. data/test/fixtures/companies.yml +0 -7
  28. data/test/fixtures/custom_locations.yml +0 -54
  29. data/test/fixtures/locations.yml +0 -54
  30. data/test/fixtures/mock_addresses.yml +0 -17
  31. data/test/fixtures/mock_families.yml +0 -2
  32. data/test/fixtures/mock_houses.yml +0 -9
  33. data/test/fixtures/mock_organizations.yml +0 -5
  34. data/test/fixtures/mock_people.yml +0 -5
  35. data/test/fixtures/stores.yml +0 -0
  36. data/test/ip_geocode_lookup_test.disabled.rb +0 -82
  37. data/test/models/company.rb +0 -3
  38. data/test/models/custom_location.rb +0 -12
  39. data/test/models/location.rb +0 -4
  40. data/test/models/mock_address.rb +0 -4
  41. data/test/models/mock_family.rb +0 -3
  42. data/test/models/mock_house.rb +0 -3
  43. data/test/models/mock_organization.rb +0 -4
  44. data/test/models/mock_person.rb +0 -4
  45. data/test/models/store.rb +0 -3
  46. data/test/schema.rb +0 -60
  47. data/test/tasks.rake +0 -38
  48. data/test/test_helper.rb +0 -23
@@ -0,0 +1,67 @@
1
+ # You can configure Geokit in your environment files
2
+
3
+ These defaults are used in `Geokit::Mappable.distance_to` and in `acts_as_mappable`
4
+
5
+ config.geokit.default_units = :miles
6
+ config.geokit.default_formula = :sphere
7
+
8
+ This is the timeout value in seconds to be used for calls to the geocoder web services. For no timeout at all, comment out the setting. The timeout unit is in seconds.
9
+
10
+ config.geokit.geocoders.request_timeout = 3
11
+
12
+ These settings are used if web service calls must be routed through a proxy.
13
+ These setting can be `nil` if not needed, otherwise, addr and port must be filled in at a minimum. If the proxy requires authentication, the username and password can be provided as well.
14
+
15
+ config.geokit.geocoders.proxy_addr = nil
16
+ config.geokit.geocoders.proxy_port = nil
17
+ config.geokit.geocoders.proxy_user = nil
18
+ config.geokit.geocoders.proxy_pass = nil
19
+
20
+ This is your yahoo application key for the Yahoo Geocoder.
21
+
22
+ See [http://developer.yahoo.com/faq/index.html#appid](http://developer.yahoo.com/faq/index.html#appid)
23
+ and [http://developer.yahoo.com/maps/rest/V1/geocode.html](http://developer.yahoo.com/maps/rest/V1/geocode.html)
24
+
25
+ config.geokit.geocoders.yahoo = 'REPLACE_WITH_YOUR_YAHOO_KEY'
26
+
27
+ This is your Google Maps geocoder key.
28
+
29
+ See [http://www.google.com/apis/maps/signup.html](http://www.google.com/apis/maps/signup.html)
30
+ and [http://www.google.com/apis/maps/documentation/#Geocoding_Examples](http://www.google.com/apis/maps/documentation/#Geocoding_Examples)
31
+
32
+ config.geokit.geocoders.google = 'REPLACE_WITH_YOUR_GOOGLE_KEY'
33
+
34
+ This is your username and password for **geocoder.us**.
35
+ To use the free service, the value can be set to `nil` or `false`.
36
+ For usage tied to an account, the value should be set to `username:password`.
37
+
38
+ See [http://geocoder.us](http://geocoder.us)
39
+ and [http://geocoder.us/user/signup](http://geocoder.us/user/signup)
40
+
41
+ config.geokit.geocoders.geocoder_us = false
42
+
43
+ This is your authorization key for **geocoder.ca**.
44
+ To use the free service, the value can be set to `nil` or `false`. For usage tied to an account, set the value to the key obtained from
45
+ **Geocoder.ca**.
46
+
47
+ See [http://geocoder.ca](http://geocoder.ca)
48
+ and [http://geocoder.ca/?register=1](http://geocoder.ca/?register=1)
49
+
50
+ config.geokit.geocoders.geocoder_ca = false
51
+
52
+ Add this to use a username with the Geonames geocoder
53
+
54
+ config.geokitgeocoders.geonames="REPLACE_WITH_YOUR_GEONAMES_USERNAME"
55
+
56
+ This is the order in which the geocoders are called in a failover scenario..
57
+ If you only want to use a single geocoder, put a single symbol in the array.
58
+ Valid symbols are `:google`, `:yahoo`, `:us`, and `:ca`.
59
+ Be aware that there are **Terms of Use** restrictions on how you can use the various geocoders. Make sure you read up on relevant **Terms of Use** for each geocoder you are going to use.
60
+
61
+ config.geokit.geocoders.provider_order = [:google,:us]
62
+
63
+ The IP provider order. Valid symbols are `:ip`, `:geo_plugin`.
64
+ As before, make sure you read up on relevant **Terms of Use** for each
65
+
66
+ config.geokitgeocoders.ip_provider_order = [:geo_plugin,:ip]
67
+
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source :rubygems
2
+
3
+ gemspec
@@ -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.
@@ -0,0 +1,653 @@
1
+ # WARNING
2
+
3
+ This gem is an attempt to port [Andre Lewis' great **geokit-rails**](https://github.com/andre/geokit-gem) to Rails 3.
4
+
5
+ It is not finished. Some parts of the original plugin remain absolutely untouched or untested.
6
+
7
+ What has been done :
8
+
9
+ * the Rails 2 plugin has been transformed to a Rails 3 plugin, in a gem
10
+ * the Rails 3 conventions and APIs and best practices have been followed (hopefully)
11
+ * the basic features have been ported to use the new ActiveRecord 3 query syntax
12
+ * the test suite has been updated to use the new query methods
13
+ * the test suite pass for all the features that have been ported
14
+ * this README has been updated for what is working and tested
15
+
16
+ What hasn't been done (yet) :
17
+
18
+ * make it possible to use the `where` scope with a clause on the distance column
19
+ * port the IpGeocodeLookup module (the test suite is disabled)
20
+ * obviously, make the test suite pass for all the features that have not been ported yet
21
+ * update this README to reflect other changes that could have appeared with Rails 3
22
+
23
+ In short, this gem is **not a drop-in replacement** for the Andre's plugin :
24
+
25
+ 1. it is usable for the basic features. It there are some bugs on these features,
26
+ I'll try the best I can to fix them.
27
+
28
+ 2. it is not yet a full replacement of the geokit-rails plugin ;
29
+ some features have been ported, but the syntax has changed, and some have not been ported.
30
+
31
+ ## INSTALLATION
32
+
33
+ Geokit for Rails consists of a generic Gem ([geokit-gem](https://github.com/andre/geokit-gem)) and a Rails plugin ([geokit-rails3](https://github.com/jlecour/geokit-rails3)).
34
+
35
+ Make sure you use a version >= 3.0 of Rails.
36
+
37
+ You just have to add the 'geokit-rails3' gem to your Gemfile
38
+
39
+ gem 'geokit-rails3'
40
+
41
+ Then tell bundler to update the gems :
42
+
43
+ $ bundle install
44
+
45
+ If you want to use geokit-rails in a Rails 2 application, just use the good old plugin ([geokit-rails](https://github.com/andre/geokit-rails)).
46
+
47
+
48
+ ## FEATURE SUMMARY
49
+
50
+ Geokit provides key functionality for location-oriented Rails applications:
51
+
52
+ - Distance calculations, for both flat and spherical environments. For example,
53
+ given the location of two points on the earth, you can calculate the miles/Km
54
+ between them.
55
+ - ActiveRecord distance-based finders. For example, you can find all the points
56
+ in your database within a 50-mile radius.
57
+ - IP-based location lookup utilizing hostip.info. Provide an IP address, and get
58
+ city name and latitude/longitude in return
59
+ - A before_filter helper to geocoder the user's location based on IP address,
60
+ and retain the location in a cookie.
61
+ - Geocoding from multiple providers. It provides a fail-over mechanism, in case
62
+ your input fails to geocode in one service. Geocoding is provided buy the Geokit
63
+ gem, which you must have installed
64
+
65
+ The goal of this plugin is to provide the common functionality for location-oriented
66
+ applications (geocoding, location lookup, distance calculation) in an easy-to-use
67
+ package.
68
+
69
+ ## A NOTE ON TERMINOLOGY
70
+
71
+ Throughout the code and API, _latitude_ and _longitude_ are referred to as _lat_
72
+ and _lng_. We've found over the long term the abbreviation saves lots of typing time.
73
+
74
+ ## LOCATION QUERIES
75
+
76
+ ### MAKING A MODEL MAPPABLE
77
+
78
+ To get started, just specify an ActiveRecord class as `acts_as_mappable`:
79
+
80
+ class Location < ActiveRecord::Base
81
+ acts_as_mappable
82
+ end
83
+
84
+ There are some defaults you can override:
85
+
86
+ class Location < ActiveRecord::Base
87
+ acts_as_mappable :default_units => :miles,
88
+ :default_formula => :sphere,
89
+ :distance_field_name => :distance,
90
+ :lat_column_name => :lat,
91
+ :lng_column_name => :lng
92
+ end
93
+
94
+
95
+ The optional parameters are `units`, `formula`, and `distance_field_name`.
96
+ Values for **units** can be `:miles`, `:kms` (kilometers), or `:nms` (nautical miles),
97
+ with `:miles` as the default.
98
+ Values for **formula** can be `:sphere` or `:flat` with `:sphere` as the default.
99
+ `:sphere` gives you Haversine calculations, while `:flat` gives the Pythagoreum Theory.
100
+ These defaults persist through out the gem.
101
+
102
+ The plug-in creates a calculated `distance` field on AR instances that have
103
+ been retrieved throw a Geokit location query. By default, these fields are
104
+ known as "distance" but this can be changed through the `:distance_field_name` key.
105
+
106
+ You can also define alternative column names for latitude and longitude using
107
+ the `:lat_column_name` and `:lng_column_name` keys. The defaults are `lat` and
108
+ `lng` respectively.
109
+
110
+ ### NEW SCOPES TO USE
111
+
112
+ Once you've specified `acts_as_mappable`, a few scopes are available :
113
+
114
+ * `within` and `beyond` find records within or beyond a certain distance from the origin point.
115
+ * `in_range` finds records within a certain distance range from the origin point.
116
+ * `in_bounds` finds redords within a rectangle on the map
117
+ * `closest` and `farthest` find the closest or farthest record from the origin point
118
+
119
+ All these scopes are the porcelain for a lower level scope named `geo_scope` that take a hash of options.
120
+ Their first parameter is simply one of the possible options, without the name
121
+
122
+ A few examples :
123
+
124
+ Location.within(5, :origin => @somewhere)
125
+ # is the same as
126
+ Location.geo_scope(:within => 5, :origin => @somewhere)
127
+
128
+ Location.in_range(2..5, :origin => @somewhere)
129
+ # is the same as
130
+ Location.geo_scope(:range => 2..5, :origin => @somewhere)
131
+
132
+ Location.in_bounds([@south_west_point, @north_east_point], :origin => @somewhere)
133
+ # is the same as
134
+ Location.geo_scope(:bounds => [@south_west_point, @north_east_point], :origin => @somewhere)
135
+
136
+ The options can be :
137
+
138
+ `:origin` as a two-element array of latitude/longitude:
139
+
140
+ Location.geo_scope(:origin => [37.792,-122.393])
141
+
142
+ `:origin` as a geocodeable string:
143
+
144
+ Location.geo_scope(:origin => '100 Spear st, San Francisco, CA')
145
+
146
+ `:origin` as an object which responds to `lat` and `lng` methods,
147
+ or `latitude` and `longitude` methods, or whatever methods you have
148
+ specified for `lng_column_name` and `lat_column_name`:
149
+
150
+ Location.geo_scope(:origin => my_store)
151
+ # my_store.lat and my_store.lng methods exist
152
+
153
+ `:units` or `:formula` can be used to override the default values in a specific query
154
+
155
+ Location.within(5, :units => :km, :origin => @somewhere)
156
+ # it will get the records within 5 kilometers instead of 5 miles
157
+
158
+ `:range` as a native Ruby range
159
+
160
+ `:bounds` as an array of two elements : the wouth/west point and the north/east point.
161
+
162
+ @sw = GeoKit::LatLng.new(32.91663,-96.982841)
163
+ @ne = GeoKit::LatLng.new(32.96302,-96.919495)
164
+ @somewhere = Locationd.find(123456)
165
+ Location.geo_scope(:bounds => [@sw, @ne], :origin => @somewhere)
166
+
167
+ `:bounds` as a Geokit::Bounds object
168
+
169
+ @bounds = Geokit::Bounds.new([32.91663,-96.982841], [32.96302,-96.919495])
170
+ @somewhere = Locationd.find(123456)
171
+ Location.geo_scope(:bounds => [@sw, @ne], :origin => @somewhere)
172
+
173
+ When using a point of reference or bounds, you leverage the power of Geokit
174
+ to build this objects. Basicaly, if Geokit can make a Geokit::Point
175
+ or a Geokit::Bounds with what you give to it, you're good to go.
176
+
177
+ ### FIND BY SQL
178
+
179
+ Finaly, if all that is desired is the raw SQL for distance
180
+ calculations, you can use the following:
181
+
182
+ Location.distance_sql(origin, units = default_units, formula = default_formula)
183
+
184
+ Thereafter, you are free to use it in `find_by_sql` as you wish.
185
+
186
+ ### CHAINABILITY
187
+
188
+ You can then chain these scope with any other or use a "calling" method like `first`, `all`, `count`, …
189
+
190
+ Location.within(5, :origin => @somewhere).all
191
+ Location.within(5, :origin => @somewhere).count
192
+ Location.geo_scope(:origin => [37.792,-122.393]).first
193
+
194
+ You can add `order` clauses in the chain as for any ActiveRecord query
195
+
196
+ Location.within(5, :origin => @somewhere).order('nbr_seats ASC')
197
+
198
+ You can even sort by distance (use the same name as specified in the model class)
199
+
200
+ Location.within(5, :origin => @somewhere).order('distance DESC, nbr_seats ASC')
201
+
202
+ Idem for the `limit` clause. In fact, `closest` and `farthest` are defined like this :
203
+
204
+ def closest(options = {})
205
+ geo_scope(options).order("#{distance_column_name} asc").limit(1)
206
+ end
207
+ def farthest(options = {})
208
+ geo_scope(options).order("#{distance_column_name} desc").limit(1)
209
+ end
210
+
211
+ #### Important caveat
212
+
213
+ In the current version of geokit-rails3, it is not possible to add a `where` clause
214
+ using the _distance_ column. I've tried many different ways to do this and didn't get it woking.
215
+
216
+ One would expect to build a query like this :
217
+
218
+ scoped = Location.geo_scope(:origin => @somewhere)
219
+ scoped = scoped.where('distance <= 5)
220
+ results = scoped.all
221
+
222
+ This is not possible right now, it must be done in a single step like this :
223
+
224
+ scoped = Location.within(5, :origin => @somewhere)
225
+ results = scoped.all
226
+
227
+ Every good idea that would help achieve this is very much welcome.
228
+
229
+ ### FINDING WITHIN A BOUNDING BOX
230
+
231
+ If you are displaying points on a map, you probably need to query for whatever falls within the rectangular bounds of the map:
232
+
233
+ Store.in_bounds([sw_point,ne_point]).all
234
+
235
+ 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.
236
+
237
+ If you need to calculate the bounding box from a point and radius, you can do that:
238
+
239
+ bounds = Geokit::Bounds.from_point_and_radius(home,5)
240
+ Store.in_bounds(bounds).all
241
+
242
+ ----
243
+
244
+ # What is following is from the previous _geokit-rails_ plugin.
245
+
246
+ **It has not been tested with Rails 3 nor with this version of the gem.**
247
+ **Most of it should work, but it is not sure**
248
+
249
+
250
+ ## USING INCLUDES
251
+
252
+ You can use includes along with your distance finders:
253
+
254
+ stores = Store.within(5, :origin=>home).includes([:reviews,:cities]).order('distance asc').all
255
+
256
+ *However*, ActiveRecord drops the calculated distance column when you use include. So, if you need to
257
+ use the distance column, you'll have to re-calculate it post-query in Ruby:
258
+
259
+ stores.sort_by_distance_from(home)
260
+
261
+ In this case, you may want to just use the bounding box
262
+ condition alone in your SQL (there's no use calculating the distance twice):
263
+
264
+ bounds=Geokit::Bounds.from_point_and_radius(home,5)
265
+ stores=Store.includes([:reviews,:cities]).in_bounds(bounds)
266
+ stores.sort_by_distance_from(home)
267
+
268
+ ## USING :through
269
+
270
+ You can also specify a model as mappable "through" another associated model.
271
+ In other words, that associated model is the actual mappable model with
272
+ "lat" and "lng" attributes, but this "through" model can still utilize
273
+ all of the above find methods to search for records.
274
+
275
+ class Location < ActiveRecord::Base
276
+ belongs_to :locatable, :polymorphic => true
277
+ acts_as_mappable
278
+ end
279
+
280
+ class Company < ActiveRecord::Base
281
+ has_one :location, :as => :locatable # also works for belongs_to associations
282
+ acts_as_mappable :through => :location
283
+ end
284
+
285
+ Then you can still call:
286
+
287
+ Company.within(distance, :origin => @somewhere)
288
+
289
+ You can also give :through a hash if you location is nested deep. For example, given:
290
+
291
+ class House
292
+ acts_as_mappable
293
+ end
294
+
295
+ class Family
296
+ belongs_to :house
297
+ end
298
+
299
+ class Person
300
+ belongs_to :family
301
+ acts_as_mappable :through => { :family => :house }
302
+ end
303
+
304
+ Remember that the notes above about USING INCLUDES apply to the results from
305
+ this find, since an include is automatically used.
306
+
307
+ ## IP GEOCODING
308
+
309
+ You can obtain the location for an IP at any time using the geocoder
310
+ as in the following example:
311
+
312
+ location = IpGeocoder.geocode('12.215.42.19')
313
+
314
+ where Location is a GeoLoc instance containing the latitude,
315
+ longitude, city, state, and country code. Also, the success
316
+ value is true.
317
+
318
+ If the IP cannot be geocoded, a GeoLoc instance is returned with a
319
+ success value of false.
320
+
321
+ It should be noted that the IP address needs to be visible to the
322
+ Rails application. In other words, you need to ensure that the
323
+ requesting IP address is forwarded by any front-end servers that
324
+ are out in front of the Rails app. Otherwise, the IP will always
325
+ be that of the front-end server.
326
+
327
+ The Multi-Geocoder will also geocode IP addresses and provide
328
+ failover among multiple IP geocoders. Just pass in an IP address for the
329
+ parameter instead of a street address. Eg:
330
+
331
+ location = Geocoders::MultiGeocoder.geocode('12.215.42.19')
332
+
333
+ The MultiGeocoder class requires 2 configuration setting for the provider order.
334
+ Ordering is done through `Geokit::Geocoders::provider_order` and
335
+ `Geokit::Geocoders::ip_provider_order`, found in
336
+ `config/initializers/geokit_config.rb`. If you don't already have a
337
+ `geokit_config.rb` file, the plugin creates one when it is first installed.
338
+
339
+
340
+ ## IP GEOCODING HELPER
341
+
342
+ A class method called geocode_ip_address has been mixed into the
343
+ ActionController::Base. This enables before_filter style lookup of
344
+ the IP address. Since it is a filter, it can accept any of the
345
+ available filter options.
346
+
347
+ Usage is as below:
348
+
349
+ class LocationAwareController < ActionController::Base
350
+ geocode_ip_address
351
+ end
352
+
353
+ A first-time lookup will result in the GeoLoc class being stored
354
+ in the session as `:geo_location` as well as in a cookie called
355
+ `:geo_session`. Subsequent lookups will use the session value if it
356
+ exists or the cookie value if it doesn't exist. The last resort is
357
+ to make a call to the web service. Clients are free to manage the
358
+ cookie as they wish.
359
+
360
+ The intent of this feature is to be able to provide a good guess as
361
+ to a new visitor's location.
362
+
363
+ ## INTEGRATED FIND AND GEOCODING
364
+
365
+ Geocoding has been integrated with the finders enabling you to pass
366
+ a physical address or an IP address. This would look the following:
367
+
368
+ Location.farthest(:origin => '217.15.10.9')
369
+ Location.farthest(:origin => 'Irving, TX')
370
+
371
+ where the IP or physical address would be geocoded to a location and
372
+ then the resulting latitude and longitude coordinates would be used
373
+ in the find. This is not expected to be common usage, but it can be
374
+ done nevertheless.
375
+
376
+ ## ADDRESS GEOCODING
377
+
378
+ Geocoding is provided by the Geokit gem, which is required for this plugin.
379
+ See the top of this file for instructions on installing the Geokit gem.
380
+
381
+ Geokit can geocode addresses using multiple geocodeing web services.
382
+ Geokit supports services like Google, Yahoo, and Geocoder.us, and more --
383
+ see the Geokit gem API for a complete list.
384
+
385
+ These geocoder services are made available through the following classes:
386
+ GoogleGeocoder, YahooGeocoder, UsGeocoder, CaGeocoder, and GeonamesGeocoder.
387
+ Further, an additional geocoder class called MultiGeocoder incorporates an ordered failover
388
+ sequence to increase the probability of successful geocoding.
389
+
390
+ All classes are called using the following signature:
391
+
392
+ include Geokit::Geocoders
393
+ location = XxxGeocoder.geocode(address)
394
+
395
+ where you replace Xxx Geocoder with the appropriate class. A GeoLoc
396
+ instance is the result of the call. This class has a "success"
397
+ attribute which will be true if a successful geocoding occurred.
398
+ If successful, the lat and lng properties will be populated.
399
+
400
+ Geocoders are named with the convention NameGeocoder. This
401
+ naming convention enables Geocoder to auto-detect its sub-classes
402
+ in order to create methods called `name_geocoder(address)` so that
403
+ all geocoders can be called through the base class. This is done
404
+ purely for convenience; the individual geocoder classes are expected
405
+ to be used independently.
406
+
407
+ The MultiGeocoder class requires the configuration of a provider
408
+ order which dictates what order to use the various geocoders. Ordering
409
+ is done through `Geokit::Geocoders::provider_order`, found in
410
+ `config/initializers/geokit_config.rb`.
411
+
412
+ If you don't already have a `geokit_config.rb` file, the plugin creates one
413
+ when it is first installed.
414
+
415
+ Make sure your failover configuration matches the usage characteristics
416
+ of your application -- for example, if you routinely get bogus input to
417
+ geocode, your code will be much slower if you have to failover among
418
+ multiple geocoders before determining that the input was in fact bogus.
419
+
420
+ The Geocoder.geocode method returns a GeoLoc object. Basic usage:
421
+
422
+ loc=Geocoder.geocode('100 Spear St, San Francisco, CA')
423
+ if loc.success
424
+ puts loc.lat
425
+ puts loc.lng
426
+ puts loc.full_address
427
+ end
428
+
429
+ ## REVERSE GEOCODING
430
+
431
+ Currently, only the Google Geocoder supports reverse geocoding.
432
+ Pass the lat/lng as a string, array or LatLng instance:
433
+
434
+ res=Geokit::Geocoders::GoogleGeocoder.reverse_geocode "37.791821,-122.394679"
435
+ => #<Geokit::GeoLoc:0x558ed0 ...
436
+ res.full_address
437
+ "101-115 Main St, San Francisco, CA 94105, USA"
438
+
439
+ The address will usually appear as a range, as it does in the above example.
440
+
441
+
442
+ ## INTEGRATED FIND WITH ADDRESS GEOCODING
443
+
444
+ Just has you can pass an IP address directly into an ActiveRecord finder
445
+ as the origin, you can also pass a physical address as the origin:
446
+
447
+ Location.find_closest(:origin => '100 Spear st, San Francisco, CA')
448
+
449
+ where the physical address would be geocoded to a location and then the
450
+ resulting latitude and longitude coordinates would be used in the
451
+ find.
452
+
453
+ Note that if the address fails to geocode, the find method will raise an
454
+ ActiveRecord::GeocodeError you must be prepared to catch. Alternatively,
455
+ You can geocoder the address beforehand, and pass the resulting lat/lng
456
+ into the finder if successful.
457
+
458
+ ## Auto Geocoding
459
+
460
+ If your geocoding needs are simple, you can tell your model to automatically
461
+ geocode itself on create:
462
+
463
+ class Store < ActiveRecord::Base
464
+ acts_as_mappable :auto_geocode=>true
465
+ end
466
+
467
+ It takes two optional params:
468
+
469
+ class Store < ActiveRecord::Base
470
+ acts_as_mappable :auto_geocode=>{:field=>:address, :error_message=>'Could not geocode address'}
471
+ end
472
+
473
+ . . . which is equivilent to:
474
+
475
+ class Store << ActiveRecord::Base
476
+ acts_as_mappable
477
+ before_validation :geocode_address, :on => :create
478
+
479
+ private
480
+ def geocode_address
481
+ geo=Geokit::Geocoders::MultiGeocoder.geocode (address)
482
+ errors.add(:address, "Could not Geocode address") if !geo.success
483
+ self.lat, self.lng = geo.lat,geo.lng if geo.success
484
+ end
485
+ end
486
+
487
+ If you need any more complicated geocoding behavior for your model, you should roll your own
488
+ `before_validate` callback.
489
+
490
+
491
+ ## Distances, headings, endpoints, and midpoints
492
+
493
+ distance = home.distance_from(work, :units=>:miles)
494
+ heading = home.heading_to(work) # result is in degrees, 0 is north
495
+ endpoint = home.endpoint(90,2) # two miles due east
496
+ midpoing = home.midpoint_to(work)
497
+
498
+ ## Cool stuff you can do with bounds
499
+
500
+ bounds = Bounds.new(sw_point,ne_point)
501
+ bounds.contains?(home)
502
+ puts bounds.center
503
+
504
+
505
+ HOW TO . . .
506
+ =================================================================================
507
+
508
+ A few quick examples to get you started ....
509
+
510
+ ## How to install the Geokit Rails plugin
511
+ (See the very top of this file)
512
+
513
+ ## How to find all stores within a 10-mile radius of a given lat/lng
514
+ 1. ensure your stores table has lat and lng columns with numeric or float
515
+ datatypes to store your latitude/longitude
516
+
517
+ 3. use `acts_as_mappable` on your store model:
518
+
519
+ class Store < ActiveRecord::Base
520
+ acts_as_mappable
521
+ ...
522
+ end
523
+
524
+ 3. finders now have extra capabilities:
525
+
526
+ Store.find(:all, :origin =>[32.951613,-96.958444], :within=>10)
527
+
528
+ ## How to geocode an address
529
+
530
+ 1. configure your geocoder key(s) in `config/initializers/geokit_config.rb`
531
+
532
+ 2. also in `geokit_config.rb`, make sure that `Geokit::Geocoders::provider_order` reflects the
533
+ geocoder(s). If you only want to use one geocoder, there should
534
+ be only one symbol in the array. For example:
535
+
536
+ Geokit::Geocoders::provider_order=[:google]
537
+
538
+ 3. Test it out in script/console
539
+
540
+ include Geokit::Geocoders
541
+ res = MultiGeocoder.geocode('100 Spear St, San Francisco, CA')
542
+ puts res.lat
543
+ puts res.lng
544
+ puts res.full_address
545
+ ... etc. The return type is GeoLoc, see the API for
546
+ all the methods you can call on it.
547
+
548
+ ## How to find all stores within 10 miles of a given address
549
+
550
+ 1. as above, ensure your table has the lat/lng columns, and you've
551
+ applied `acts_as_mappable` to the Store model.
552
+
553
+ 2. configure and test out your geocoder, as above
554
+
555
+ 3. pass the address in under the :origin key
556
+
557
+ Store.find(:all, :origin=>'100 Spear st, San Francisco, CA',
558
+ :within=>10)
559
+
560
+ 4. you can also use a zipcode, or anything else that's geocodable:
561
+
562
+ Store.find(:all, :origin=>'94117',
563
+ :conditions=>'distance<10')
564
+
565
+ ## How to sort a query by distance from an origin
566
+
567
+ You now have access to a 'distance' column, and you can use it
568
+ as you would any other column. For example:
569
+
570
+ Store.find(:all, :origin=>'94117', :order=>'distance')
571
+
572
+ ## How to elements of an array according to distance from a common point
573
+
574
+ Usually, you can do your sorting in the database as part of your find call.
575
+ If you need to sort things post-query, you can do so:
576
+
577
+ stores = Store.all
578
+ stores.sort_by_distance_from(home)
579
+ puts stores.first.distance
580
+
581
+ Obviously, each of the items in the array must have a latitude/longitude so
582
+ they can be sorted by distance.
583
+
584
+ ## Database indexes
585
+
586
+ MySQL can't create indexes on a calculated field such as those Geokit uses to
587
+ calculate distance based on latitude/longitude values for a record. However,
588
+ indexing the lat and lng columns does improve Geokit distance calculation
589
+ performance since the lat and lng columns are used in a straight comparison
590
+ for distance calculation. Assuming a Page model that is incorporating the
591
+ Geokit plugin the migration would be as follows.
592
+
593
+ class AddIndexOPageLatAndLng < ActiveRecord::Migration
594
+
595
+ def self.up
596
+ add_index :pages, [:lat, :lng]
597
+ end
598
+
599
+ def self.down
600
+ remove_index :pages, [:lat, :lng]
601
+ end
602
+ end
603
+
604
+ ## Database Compatability
605
+
606
+ * Geokit works with MySQL (tested with version 5.0.41), PostgreSQL (tested with version 8.2.6) and Microsoft SQL Server (tested with 2000).
607
+ * Geokit does *not* work with SQLite, as it lacks the necessary geometry functions.
608
+ * Geokit is known to *not* work with Postgres versions under 8.1 -- it uses the least() funciton.
609
+
610
+
611
+ ## HIGH-LEVEL NOTES ON WHAT'S WHERE
612
+
613
+ `acts_as_mappable.rb`, as you'd expect, contains the ActsAsMappable
614
+ module which gets mixed into your models to provide the
615
+ location-based finder goodness.
616
+
617
+ `ip_geocode_lookup.rb` contains the before_filter helper method which
618
+ enables auto lookup of the requesting IP address.
619
+
620
+ ### The Geokit gem provides the building blocks of distance-based operations:
621
+
622
+ The Mappable module, which provides basic
623
+ distance calculation methods, i.e., calculating the distance
624
+ between two points.
625
+
626
+ The LatLng class is a simple container for latitude and longitude, but
627
+ it's made more powerful by mixing in the above-mentioned Mappable
628
+ module -- therefore, you can calculate easily the distance between two
629
+ LatLng ojbects with `distance = first.distance_to(other)`
630
+
631
+ GeoLoc represents an address or location which
632
+ has been geocoded. You can get the city, zipcode, street address, etc.
633
+ from a GeoLoc object. GeoLoc extends LatLng, so you also get lat/lng
634
+ AND the Mappable modeule goodness for free.
635
+
636
+ ## GOOGLE GROUP
637
+
638
+ Follow the Google Group for updates and discussion on Geokit: http://groups.google.com/group/geokit
639
+
640
+ ## IMPORTANT POST-INSTALLATION NOTES:
641
+
642
+ *1. The configuration file*: Geokit for Rails uses a configuration file in config/initializers.
643
+ You *must* add your own keys for the various geocoding services if you want to use geocoding.
644
+ If you need to refer to the original template again, see the `assets/api_keys_template` file.
645
+
646
+ *2. The gem dependency*: Geokit for Rails depends on the Geokit gem. Tell Rails about this
647
+ dependency in `config/environment.rb`, within the initializer block:
648
+ config.gem "geokit"
649
+
650
+ *If you're having trouble with dependencies ....*
651
+
652
+ Try installing the gem manually (sudo gem install geokit), then adding a `require 'geokit'` to the top of
653
+ `vendor/plugins/geokit-rails3/init.rb` and/or `config/geokit_config.rb`.