-
Notifications
You must be signed in to change notification settings - Fork 0
/
README
333 lines (244 loc) · 12.6 KB
/
README
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
------------------------------------- ( G E O K I T ) ---------------------------------------
# FEATURE SUMMARY
This plugin provides key functionality for location-oriented Rails applications:
- Distance calculations, for both flat and spherical environments. For example,
given the location of two points on the earth, you can calculate the miles/KM
between them.
- ActiveRecord distance-based finders. For example, you can find all the points
in your database within a 50-mile radius.
- Geocoding from multiple providers. It currently supports Google, Yahoo,
Geocoder.us, and Geocoder.ca geocoders, and it provides a uniform response
structure from all of them. It also provides a fail-over mechanism, in case
your input fails to geocode in one service.
- IP-based location lookup utilizing hostip.info. Provide an IP address, and get
city name and latitude/longitude in return
- A before_filter helper to geocoder the user's location based on IP address,
and retain the location in a cookie.
The goal of this plugin is to provide the common functionality for location-oriented
applications (geocoding, location lookup, distance calculation) in an easy-to-use
package.
# A NOTE ON TERMINOLOGY
Throughout the code and API of this, latitude and longitude are refered to as lat
and lng. We've found over the long term the abbreviation saves lots of typing time.
# DISTANCE CALCULATIONS AND QUERIES
If you want only distance calculation services, you need only mix in the Mappable
module like so:
class Location
include GeoKit::Mappable
end
After doing so, you can do things like:
Location.distance_between(from, to)
with optional parameters :units and :formula. Values for :units can be :miles or
:kms with :miles as the default. Values for :formula can be :sphere or :flat with
:sphere as the default. :sphere gives you Haversine calculations, while :flat
gives the Pythagoreum Theory. These defaults persist through out the plug-in.
You can also do:
location.distance_to(other)
The real power and utility of the plug-in is in its query support. This is
achieved through mixing into an ActiveRecord model object. Doing so, looks
as below:
class Location < ActiveRecord::Base
acts_as_mappable
end
The plug-in uses the above-mentioned defaults, but can be modified to use
different units and a different formula. This is done through the :default_units
and :default_formula keys which accept the same values as mentioned above.
The plug-in creates a calculated column and potentially a calculated condition.
By default, these are known as "distance" but this can be changed through the
:distance_field_name key.
So, an alternative invocation would look as below:
class Location < ActiveRecord::Base
acts_as_mappable :default_units => :kms,
:default_formula => :flat,
:distance_field_name => :distance
end
You can also define alternative column names for latitude and longitude using
the :lat_column_name and :lng_column_name keys. The defaults are 'lat' and
'lng' respectively.
Thereafter, a set of finder methods are made available. Below are the
different combinations:
find(:all, :origin)
- Adds the distance from origin into the result set named as the distance
field name.
find(:all, :lat => 32.951613, :lng => -96.958444, )
- Same as above, but uses the coordinates to form the origin.
find(:all, :origin, :conditions => "distance < 5")
- In addition to adding a distance column, substitutes the distance
formula for the distance field in the conditions clause. This saves
from having to add complicated SQL in the conditions clause. The result
set returns model instances that are less than 5 units away.
NOTE: conditions can also be compound as in
:conditions => "distance < 100 and state ='TX'".
If :origin or :lat or :lng are not provided in the finder
call, the find method works as normal. Further, these keys are removed
from the :options hash prior to invoking the superclass behavior.
Other convenience methods work intuitively and are as follows:
find_within(distance, options={})
find_beyond(distance, options={})
find_closest(options={})
find_farthest(options={})
where the options respect the defaults, but can be overridden if
desired.
Lastly, if all that is desired is the raw SQL for distance
calculations, you can use the following:
distance_sql(origin, units=default_units, formula=default_formula)
Thereafter, you are free to use it in find_by_sql as you wish.
# IP GEOCODING
You can obtain the location for an IP at any time using the geocoder
as in the following example:
location = IpGeocoder.geocode('12.215.42.19')
where Location is a GeoLoc instance containing the latitude,
longitude, city, state, and country code. Also, the success
value is true.
If the IP cannot be geocoded, a GeoLoc instance is returned with a
success value of false.
It should be noted that the IP address needs to be visible to the
Rails application. In other words, you need to ensure that the
requesting IP address is forwarded by any front-end servers that
are out in front of the Rails app. Otherwise, the IP will always
be that of the front-end server.
# IP GEOCODING HELPER
A class method called geocode_ip_address has been mixed into the
ActionController::Base. This enables before_filter style lookup of
the IP address. Since it is a filter, it can accept any of the
available filter options.
Usage is as below:
class LocationAwareController < ActionController::Base
geocode_ip_address
end
A first-time lookup will result in the GeoLoc class being stored
in the session as :geo_location as well as in a cookie called
:geo_session. Subsequent lookups will use the session value if it
exists or the cookie value if it doesn't exist. The last resort is
to make a call to the web service. Clients are free to manage the
cookie as they wish.
The intent of this feature is to be able to provide a good guess as
to a new visitor's location.
# INTEGRATED FIND AND GEOCODING
Geocoding has been integrated with the finders enabling you to pass
a physical address or an IP address. This would look the following:
Location.find_farthest(:origin => '217.15.10.9')
Location.find_farthest(:origin => 'Irving, TX')
where the IP or physical address would be geocoded to a location and
then the resulting latitude and longitude coordinates would be used
in the find. This is not expected to be common usage, but it can be
done nevertheless.
# ADDRESS GEOCODING
GeoKit can geocode addresses using multiple geocodeing web services.
Currently, GeoKit supports Google, Yahoo, and Geocoder.us geocoding
services.
These geocoder services are made available through three classes:
GoogleGeocoder, YahooGeocoder, and UsGeocoder. Further, an additional
geocoder class called MultiGeocoder incorporates an ordered failover
sequence to increase the probability of successful geocoding.
All classes are called using the following signature:
include GeoKit::Geocoders
location = XxxGeocoder.geocode(address)
where you replace Xxx Geocoder with the appropriate class. A GeoLoc
instance is the result of the call. This class has a "success"
attribute which will be true if a successful geocoding occurred.
If successful, the lat and lng properties will be populated.
Geocoders are named with the naming convention NameGeocoder. This
naming convention enables Geocoder to auto-detect its sub-classes
in order to create methods called name_geocoder(address) so that
all geocoders are called through the base class. This is done
purely for convenience; the individual geocoder classes are expected
to be used independently.
The MultiGeocoder class requires the configuration of a provider
order which dictates what order to use the various geocoders. Ordering
is done through the PROVIDER_ORDER constant found in environment.rb.
On installation, this plugin appends a template for your API keys to
your environment.rb.
Make sure your failover configuration matches the usage characteristics
of your application -- for example, if you routinely get bogus input to
geocode, your code will be much slower if you have to failover among
multiple geocoders before determining that the input was in fact bogus.
The Geocoder.geocode method returns a GeoLoc object. Basic usage:
loc=Geocoder.geocode('100 Spear St, San Francisco, CA')
if loc.success
puts loc.lat
puts loc.lng
puts loc.full_address
end
# INTEGRATED FIND WITH ADDRESS GEOCODING
Just has you can pass an IP address directly into an ActiveRecord finder
as the origin, you can also pass a physical address as the origin:
Location.find_closest(:origin => '100 Spear st, San Francisco, CA')
where the physical address would be geocoded to a location and then the
resulting latitude and longitude coordinates would be used in the
find.
Note that if the address fails to geocode, the find method will raise an
ActiveRecord::GeocodeError you must be prepared to catch. Alternatively,
You can geocoder the address beforehand, and pass the resulting lat/lng
into the finder if successful.
=================================================================================
# HOW TO . . .
=================================================================================
## How to install the GeoKit plugin
cd [APP_ROOT]
ruby script/plugin install svn://rubyforge.org/var/svn/geokit/trunk
or, to install as an external (your project must be version controlled):
ruby script/plugin install -x svn://rubyforge.org/var/svn/geokit/trunk
## How to find all stores within a 10-mile radius of a given lat/lng
1. ensure your stores table has lat and lng columns with numeric or float
datatypes to store your latitude/longitude
3. use acts_as_mappable on your store model:
class Store < ActiveRecord::Base
acts_as_mappable
...
end
3. find(:all, :lat => 32.951613, :lng => -96.958444, :conditions=>'distance<10')
## How to geocode an address
1. configure your geocoder key(s) in environment.rb
2. also in environment.rb, make sure that PROVIDER_ORDER reflects the
geocoder(s). If you only want to use one geocoder, there should
be only one symbol in the array. For example:
PROVIDER_ORDER=[:google]
3. Test it out in script/console
include GeoKit::Geocoders
res = MultiGeocoder.geocode('100 Spear St, San Francisco, CA')
puts res.lat
puts res.lng
puts res.full_address
... etc. The return type is GeoLoc, see the API for
all the methods you can call on it.
## How to find all stores within 10 miles of a given address
1. as above, ensure your table has the lat/lng columns, and you've
applied acts_as_mappable to the Store model.
2. configure and test out your geocoder, as above
3. pass the address in under the :origin key
Store.find(:all, :origin=>'100 Spear st, San Francisco, CA',
:conditions=>'distance<10')
4. you can also use a zipcode, or anything else that's geocodable:
Store.find(:all, :origin=>'94117',
:conditions=>'distance<10')
## How to sort a query by distance from an origin
You now have access to a 'distance' column, and you can use it
as you would any other column. For example:
Store.find(:all, :origin=>'94117', :order=>'distance')
=================================================================================
# HIGH-LEVEL NOTES ON WHAT'S WHERE
=================================================================================
acts_as_mappable.rb, as you'd expect, contains the ActsAsMappable
module which gets mixed into your models to provide the
location-based finder goodness.
mappable.rb contains the Mappable module, which provides basic
distance calculation methods, i.e., calculating the distance
between two points.
mappable.rb also contains LatLng and GeoLoc.
LatLng is a simple container for latitude and longitude, but
it's made more powerful by mixing in the above-mentioned Mappable
module -- therefore, you can calculate easily the distance between two
LatLng ojbects with distance = first.distance_to(other)
GeoLoc (also in mappable.rb) represents an address or location which
has been geocoded. You can get the city, zipcode, street address, etc.
from a GeoLoc object. GeoLoc extends LatLng, so you also get lat/lng
AND the Mappable modeule goodness for free.
geocoders.rb contains the geocoder classes.
ip_geocode_lookup.rb contains the before_filter helper method which
enables auto lookup of the requesting IP address.
# IMPORTANT NOTE: We have appended to your environment.rb file
Installation of this plugin has appended an API key template
to your environment.rb file. You *must* add your own keys for the various
geocoding services if you want to use geocoding. If you need to refer to the original
template again, see the api_keys_template file in the root of the plugin.