Skip to content
Snippets Groups Projects
Commit 5b479b1b authored by alexreisner's avatar alexreisner
Browse files

Add alternate implementation of 'near' named scope.

This less accurate SQLite-compatible implementation is activated
automatically if an SQLite database is being used.
parent cc24b9ad
No related branches found
No related tags found
No related merge requests found
......@@ -102,6 +102,24 @@ If your model has +address+, +city+, +state+, and +country+ attributes you might
Please see the code for more methods and detailed information about arguments (eg, working with kilometers).
== SQLite
SQLite's lack of trigonometric functions means Geocoder's default implementation of the +near+ method (named scope) does not work. When using SQLite, Geocoder will automatically use a less accurate algorithm for finding objects near a given point. Results of this algorithm should not be trusted too much as it will return objects that are outside the given radius.
=== Discussion
There are few options for finding objects near a given point in SQLite without installing extensions:
1. Use a square instead of a circle for finding nearby points. For example, if you want to find points near 40.71, 100.23, search for objects with latitude between 39.71 and 41.71 and longitude between 99.23 and 101.23. One degree of latitude or longitude is at most 69 miles so divide your radius (in miles) by 69.0 to get the amount to add and subtract from your center coordinates to get the upper and lower bounds. The results will not be very accurate (you'll get points outside the desired radius), but you will get all the points within the required radius.
2. Load all objects into memory and compute distances between them using the <tt>Geocoder.distance_between</tt> method. This will produce accurate results but will be very slow (and use a lot of memory) if you have a lot of objects in your database.
3. If you have a large number of objects (so you can't use approach #2) and you need accurate results (better than approach #1 will give), you can use a combination of the two. Get all the objects within a square around your center point, and then eliminate the ones that are too far away using <tt>Geocoder.distance_between</tt>.
Because Geocoder needs to provide this functionality as a named scope, we must go with option #1, but feel free to implement #2 or #3 if you need more accuracy.
== To-do List
* <tt>install.rb</tt> should do some setup when installed as a plugin
......
......@@ -47,9 +47,6 @@ module Geocoder
##
# Get options hash suitable for passing to ActiveRecord.find to get
# records within a radius (in miles) of the given point.
# Taken from excellent tutorial at:
# http://www.scribd.com/doc/2569355/Geo-Distance-Search-with-MySQL
#
# Options hash may include:
#
# +order+ :: column(s) for ORDER BY SQL clause
......@@ -57,6 +54,24 @@ module Geocoder
# +offset+ :: number of records to skip (for LIMIT SQL clause)
#
def near_scope_options(latitude, longitude, radius = 20, options = {})
if ActiveRecord::Base.connection.adapter_name == "SQLite"
approx_near_scope_options(latitude, longitude, radius, options)
else
full_near_scope_options(latitude, longitude, radius, options)
end
end
private # ----------------------------------------------------------------
##
# Named scope options hash for use with a database that supports POWER(),
# SQRT(), PI(), and trigonometric functions (SIN(), COS(), and ASIN()).
#
# Taken from the excellent tutorial at:
# http://www.scribd.com/doc/2569355/Geo-Distance-Search-with-MySQL
#
def full_near_scope_options(latitude, longitude, radius, options)
# set defaults/clean up arguments
options[:order] ||= 'distance ASC'
......@@ -95,6 +110,44 @@ module Geocoder
:limit => limit
}
end
##
# Named scope options hash for use with a database without trigonometric
# functions, like SQLite. Approach is to find objects within a square
# rather than a circle, so results are very approximate (will include
# objects outside the given radius).
#
def approx_near_scope_options(latitude, longitude, radius, options)
# set defaults/clean up arguments
radius = radius.to_i
# constrain search to a (radius x radius) square
factor = (Math::cos(latitude * Math::PI / 180.0) * 69.0).abs
lon_lo = longitude - (radius / factor);
lon_hi = longitude + (radius / factor);
lat_lo = latitude - (radius / 69.0);
lat_hi = latitude + (radius / 69.0);
# build limit clause
limit = nil
if options[:limit] or options[:offset]
options[:offset] ||= 0
limit = "#{options[:offset]},#{options[:limit]}"
end
# generate hash
lat_attr = geocoder_options[:latitude]
lon_attr = geocoder_options[:longitude]
{
:conditions => [
"#{lat_attr} BETWEEN ? AND ? AND " +
"#{lon_attr} BETWEEN ? AND ?",
lat_lo, lat_hi, lon_lo, lon_hi],
:order => options[:order],
:limit => limit
}
end
end
##
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment