geokit-rails3 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. data/.gitignore +30 -0
  2. data/CHANGELOG.rdoc +46 -0
  3. data/CONFIG.markdown +67 -0
  4. data/Gemfile +3 -0
  5. data/Gemfile.lock +89 -0
  6. data/MIT-LICENSE +20 -0
  7. data/README.markdown +554 -0
  8. data/Rakefile +17 -0
  9. data/geokit-rails3.gemspec +29 -0
  10. data/lib/geokit-rails.rb +10 -0
  11. data/lib/geokit-rails3/acts_as_mappable.rb +433 -0
  12. data/lib/geokit-rails3/adapters/abstract.rb +31 -0
  13. data/lib/geokit-rails3/adapters/mysql.rb +22 -0
  14. data/lib/geokit-rails3/adapters/postgresql.rb +22 -0
  15. data/lib/geokit-rails3/adapters/sqlserver.rb +43 -0
  16. data/lib/geokit-rails3/core_extensions.rb +14 -0
  17. data/lib/geokit-rails3/defaults.rb +21 -0
  18. data/lib/geokit-rails3/geocoder_control.rb +18 -0
  19. data/lib/geokit-rails3/ip_geocode_lookup.rb +44 -0
  20. data/lib/geokit-rails3/railtie.rb +38 -0
  21. data/lib/geokit-rails3/version.rb +3 -0
  22. data/test/acts_as_mappable_test.rb +473 -0
  23. data/test/boot.rb +32 -0
  24. data/test/database.yml +20 -0
  25. data/test/fixtures/companies.yml +7 -0
  26. data/test/fixtures/custom_locations.yml +54 -0
  27. data/test/fixtures/locations.yml +54 -0
  28. data/test/fixtures/mock_addresses.yml +17 -0
  29. data/test/fixtures/mock_families.yml +2 -0
  30. data/test/fixtures/mock_houses.yml +9 -0
  31. data/test/fixtures/mock_organizations.yml +5 -0
  32. data/test/fixtures/mock_people.yml +5 -0
  33. data/test/fixtures/stores.yml +0 -0
  34. data/test/models/company.rb +3 -0
  35. data/test/models/custom_location.rb +12 -0
  36. data/test/models/location.rb +4 -0
  37. data/test/models/mock_address.rb +4 -0
  38. data/test/models/mock_family.rb +3 -0
  39. data/test/models/mock_house.rb +3 -0
  40. data/test/models/mock_organization.rb +4 -0
  41. data/test/models/mock_person.rb +4 -0
  42. data/test/models/store.rb +3 -0
  43. data/test/schema.rb +60 -0
  44. data/test/tasks.rake +38 -0
  45. data/test/test_helper.rb +23 -0
  46. metadata +236 -0
data/.gitignore ADDED
@@ -0,0 +1,30 @@
1
+ # Test log files
2
+ test/*-debug.log
3
+
4
+ # SVN
5
+ .svn
6
+
7
+ # RVM
8
+ .rvmrc
9
+
10
+ # TextMate
11
+ *.tmproj
12
+
13
+ # rcov generated
14
+ coverage
15
+
16
+ # rdoc generated
17
+ rdoc
18
+
19
+ # yard generated
20
+ doc
21
+ .yardoc
22
+
23
+ # bundler
24
+ .bundle
25
+
26
+ # jeweler generated
27
+ pkg
28
+
29
+ # For MacOS:
30
+ .DS_Store
data/CHANGELOG.rdoc ADDED
@@ -0,0 +1,46 @@
1
+ == 2009-10-02 / Version 1.2.0
2
+ * Overhaul the test suite to be independent of a Rails project
3
+ * Added concept of database adapter. Ported mysql/postgresql conditional code to their own adapter.
4
+ * Added SQL Server support. THANKS http://github.com/brennandunn for all the improvements in this release
5
+
6
+ == 2009-09-26 / Version 1.1.3
7
+ * documentation updates and updated to work with Geokit gem v1.5.0
8
+ * IMPORTANT: in the Geokit gem, Geokit::Geocoders::timeout became Geokit::Geocoders::request_timeout for jruby compatibility.
9
+ The plugin sets this in config/initializers/geokit_config.rb. So if you've upgraded the gem to 1.5.0, you need to
10
+ make the change manually from Geokit::Geocoders::timeout to Geokit::Geocoders::request_timeout in config/initializers/geokit_config.rb
11
+
12
+ == 2009-06-08 / Version 1.1.2
13
+ * Added support for hashes in :through. So you can do: acts_as_mappable :through => { :state => :country } (Thanks José Valim).
14
+
15
+ == 2009-05-22 / Version 1.1.1
16
+ * Support for multiple ip geocoders (Thanks dreamcat4)
17
+ * Now checks if either :origin OR :bounds is passed, and proceeds with geokit query if this is true (Thanks Glenn Powell)
18
+ * Raises a helpful error if someone uses through but the association does not exists or was not defined yet (Thanks José Valim)
19
+
20
+ == 2009-04-11 / Version 1.1.0
21
+ * Fixed :through usages so that the through model is only included in the query if there
22
+ is an :origin passed in (Only if it is a geokit search) (Thanks Glenn Powell)
23
+ * Move library initialisation into lib/geokit-rails. init.rb uses lib/geokit-rails now (thanks Alban Peignier)
24
+ * Handle the case where a user passes a hash to the :conditions Finder option (thanks Adam Greene)
25
+ * Added ability to specify domain-specific API keys (Thanks Glenn Powell)
26
+
27
+ == 2009-02-20
28
+ * More powerful assosciations in the Rails Plugin:You can now specify a model as mappable "through" an associated model.
29
+ In other words, that associated model is the actual mappable model with "lat" and "lng" attributes, but this "through" model
30
+ can still utilize all Geokit's "find by distance" finders. Also Rails 2.3 compatibility (thanks github/glennpow)
31
+
32
+ == 2008-12-18
33
+ * Split Rails plugin from geocoder gem
34
+ * updated for Rails 2.2.2
35
+
36
+ == 2008-08-20
37
+ * Further fix of distance calculation, this time in SQL. Now uses least() function, which is available in MySQL version 3.22.5+ and postgres versions 8.1+
38
+
39
+ == 2008-01-16
40
+ * fixed the "zero-distance" bug (calculating between two points that are the same)
41
+
42
+ == 2007-11-12
43
+ * fixed a small but with queries crossing meridian, and also fixed find(:closest)
44
+
45
+ == 2007-10-11
46
+ * Fixed Rails2/Edge compatability
data/CONFIG.markdown ADDED
@@ -0,0 +1,67 @@
1
+ # You can configure Geokit in you 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
+ Uncomment 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
data/Gemfile.lock ADDED
@@ -0,0 +1,89 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ geokit-rails3 (0.0.3)
5
+ geokit (~> 1.5.0)
6
+ rails (~> 3.0.0)
7
+
8
+ GEM
9
+ remote: http://rubygems.org/
10
+ specs:
11
+ abstract (1.0.0)
12
+ actionmailer (3.0.0)
13
+ actionpack (= 3.0.0)
14
+ mail (~> 2.2.5)
15
+ actionpack (3.0.0)
16
+ activemodel (= 3.0.0)
17
+ activesupport (= 3.0.0)
18
+ builder (~> 2.1.2)
19
+ erubis (~> 2.6.6)
20
+ i18n (~> 0.4.1)
21
+ rack (~> 1.2.1)
22
+ rack-mount (~> 0.6.12)
23
+ rack-test (~> 0.5.4)
24
+ tzinfo (~> 0.3.23)
25
+ activemodel (3.0.0)
26
+ activesupport (= 3.0.0)
27
+ builder (~> 2.1.2)
28
+ i18n (~> 0.4.1)
29
+ activerecord (3.0.0)
30
+ activemodel (= 3.0.0)
31
+ activesupport (= 3.0.0)
32
+ arel (~> 1.0.0)
33
+ tzinfo (~> 0.3.23)
34
+ activeresource (3.0.0)
35
+ activemodel (= 3.0.0)
36
+ activesupport (= 3.0.0)
37
+ activesupport (3.0.0)
38
+ arel (1.0.1)
39
+ activesupport (~> 3.0.0)
40
+ builder (2.1.2)
41
+ erubis (2.6.6)
42
+ abstract (>= 1.0.0)
43
+ geokit (1.5.0)
44
+ i18n (0.4.1)
45
+ mail (2.2.6.1)
46
+ activesupport (>= 2.3.6)
47
+ mime-types
48
+ treetop (>= 1.4.5)
49
+ mime-types (1.16)
50
+ mocha (0.9.8)
51
+ rake
52
+ mysql (2.8.1)
53
+ polyglot (0.3.1)
54
+ rack (1.2.1)
55
+ rack-mount (0.6.13)
56
+ rack (>= 1.0.0)
57
+ rack-test (0.5.5)
58
+ rack (>= 1.0)
59
+ rails (3.0.0)
60
+ actionmailer (= 3.0.0)
61
+ actionpack (= 3.0.0)
62
+ activerecord (= 3.0.0)
63
+ activeresource (= 3.0.0)
64
+ activesupport (= 3.0.0)
65
+ bundler (~> 1.0.0)
66
+ railties (= 3.0.0)
67
+ railties (3.0.0)
68
+ actionpack (= 3.0.0)
69
+ activesupport (= 3.0.0)
70
+ rake (>= 0.8.4)
71
+ thor (~> 0.14.0)
72
+ rake (0.8.7)
73
+ rcov (0.9.9)
74
+ thor (0.14.2)
75
+ treetop (1.4.8)
76
+ polyglot (>= 0.3.1)
77
+ tzinfo (0.3.23)
78
+
79
+ PLATFORMS
80
+ ruby
81
+
82
+ DEPENDENCIES
83
+ bundler (~> 1.0.0)
84
+ geokit (~> 1.5.0)
85
+ geokit-rails3!
86
+ mocha (~> 0.9.8)
87
+ mysql (~> 2.8.1)
88
+ rails (~> 3.0.0)
89
+ rcov (~> 0.9.9)
data/MIT-LICENSE ADDED
@@ -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.markdown ADDED
@@ -0,0 +1,554 @@
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`.