module Geocoder::Store::ActiveRecord::ClassMethods
def add_exclude_condition(conditions, exclude)
The given conditions MUST be an array.
Adds a condition to exclude a given object by ID.
#
def add_exclude_condition(conditions, exclude) if exclude conditions[0] << " AND #{table_name}.id != ?" conditions << exclude.id end conditions end
def approx_distance_from_sql(latitude, longitude, options)
def approx_distance_from_sql(latitude, longitude, options) lat_attr = geocoder_options[:latitude] lon_attr = geocoder_options[:longitude] dx = Geocoder::Calculations.longitude_degree_distance(30, options[:units] || :mi) dy = Geocoder::Calculations.latitude_degree_distance(options[:units] || :mi) # sin of 45 degrees = average x or y component of vector factor = Math.sin(Math::PI / 4) "(#{dy} * ABS(#{table_name}.#{lat_attr} - #{latitude}) * #{factor}) + " + "(#{dx} * ABS(#{table_name}.#{lon_attr} - #{longitude}) * #{factor})" end
def approx_near_scope_options(latitude, longitude, radius, options)
only exist for interface consistency--not intended for production!
Distance and bearing calculations are *extremely inaccurate*. They
objects outside the given radius).
rather than a circle, so results are very approximate (will include
functions, like SQLite. Approach is to find objects within a square
Scope options hash for use with a database without trigonometric
#
def approx_near_scope_options(latitude, longitude, radius, options) lat_attr = geocoder_options[:latitude] lon_attr = geocoder_options[:longitude] options[:bearing] = :linear unless options.include?(:bearing) if options[:bearing] bearing = "CASE " + "WHEN (#{lat_attr} >= #{latitude} AND #{lon_attr} >= #{longitude}) THEN 45.0 " + "WHEN (#{lat_attr} < #{latitude} AND #{lon_attr} >= #{longitude}) THEN 135.0 " + "WHEN (#{lat_attr} < #{latitude} AND #{lon_attr} < #{longitude}) THEN 225.0 " + "WHEN (#{lat_attr} >= #{latitude} AND #{lon_attr} < #{longitude}) THEN 315.0 " + "END" else bearing = false end distance = approx_distance_from_sql(latitude, longitude, options) b = Geocoder::Calculations.bounding_box([latitude, longitude], radius, options) conditions = [ "#{lat_attr} BETWEEN ? AND ? AND #{lon_attr} BETWEEN ? AND ?"] + [b[0], b[2], b[1], b[3] ] default_near_scope_options(latitude, longitude, radius, options).merge( :select => "#{options[:select] || "#{table_name}.*"}, " + "#{distance} AS distance" + (bearing ? ", #{bearing} AS bearing" : ""), :conditions => add_exclude_condition(conditions, options[:exclude]) ) end
def default_near_scope_options(latitude, longitude, radius, options)
Options used for any near-like scope.
#
def default_near_scope_options(latitude, longitude, radius, options) { :order => options[:order] || "distance", :limit => options[:limit], :offset => options[:offset] } end
def distance_from_sql(location, *args)
def distance_from_sql(location, *args) latitude, longitude = Geocoder::Calculations.extract_coordinates(location) distance_from_sql_options(latitude, longitude, *args) if latitude and longitude end
def distance_from_sql_options(latitude, longitude, options = {})
def distance_from_sql_options(latitude, longitude, options = {}) if connection.adapter_name.match /sqlite/i approx_distance_from_sql(latitude, longitude, options) else full_distance_from_sql(latitude, longitude, options) end end
def full_distance_from_sql(latitude, longitude, options)
def full_distance_from_sql(latitude, longitude, options) lat_attr = geocoder_options[:latitude] lon_attr = geocoder_options[:longitude] earth = Geocoder::Calculations.earth_radius(options[:units] || :mi) "#{earth} * 2 * ASIN(SQRT(" + "POWER(SIN((#{latitude} - #{table_name}.#{lat_attr}) * PI() / 180 / 2), 2) + " + "COS(#{latitude} * PI() / 180) * COS(#{table_name}.#{lat_attr} * PI() / 180) * " + "POWER(SIN((#{longitude} - #{table_name}.#{lon_attr}) * PI() / 180 / 2), 2) ))" end
def full_near_scope_options(latitude, longitude, radius, options)
http://www.beginningspatial.com/calculating_bearing_one_point_another
Bearing calculation based on:
ATAN2(), DEGREES(), and RADIANS().
SQRT(), PI(), and trigonometric functions SIN(), COS(), ASIN(),
Scope options hash for use with a database that supports POWER(),
#
def full_near_scope_options(latitude, longitude, radius, options) lat_attr = geocoder_options[:latitude] lon_attr = geocoder_options[:longitude] options[:bearing] = :linear unless options.include?(:bearing) bearing = case options[:bearing] when :linear "CAST(" + "DEGREES(ATAN2( " + "RADIANS(#{lon_attr} - #{longitude}), " + "RADIANS(#{lat_attr} - #{latitude})" + ")) + 360 " + "AS decimal) % 360" when :spherical "CAST(" + "DEGREES(ATAN2( " + "SIN(RADIANS(#{lon_attr} - #{longitude})) * " + "COS(RADIANS(#{lat_attr})), (" + "COS(RADIANS(#{latitude})) * SIN(RADIANS(#{lat_attr}))" + ") - (" + "SIN(RADIANS(#{latitude})) * COS(RADIANS(#{lat_attr})) * " + "COS(RADIANS(#{lon_attr} - #{longitude}))" + ")" + ")) + 360 " + "AS decimal) % 360" end distance = full_distance_from_sql(latitude, longitude, options) conditions = ["#{distance} <= ?", radius] default_near_scope_options(latitude, longitude, radius, options).merge( :select => "#{options[:select] || "#{table_name}.*"}, " + "#{distance} AS distance" + (bearing ? ", #{bearing} AS bearing" : ""), :conditions => add_exclude_condition(conditions, options[:exclude]) ) end
def near_scope_options(latitude, longitude, radius = 20, options = {})
* +:exclude+ - an object to exclude (used by the +nearbys+ method)
* +:order+ - column(s) for ORDER BY SQL clause; default is distance
* +:select+ - string with the SELECT SQL fragment (e.g. “id, name”)
set to false for no bearing calculation
between the given point and each found nearby point;
the method to be used for calculating the bearing (direction)
* +:bearing+ - :linear (default) or :spherical;
is added to each found nearby object
for interpreting radius as well as the +distance+ attribute which
* +:units+ - :mi (default) or :km; to be used
Options hash may include:
records within a radius (in miles) of the given point.
Get options hash suitable for passing to ActiveRecord.find to get
#
def near_scope_options(latitude, longitude, radius = 20, options = {}) if connection.adapter_name.match /sqlite/i approx_near_scope_options(latitude, longitude, radius, options) else full_near_scope_options(latitude, longitude, radius, options) end end