diff --git a/lib/geocoder/stores/active_record.rb b/lib/geocoder/stores/active_record.rb index 67b78e76eccc7cd8345a63675e3fff5fd229d1f6..beb2b849a4240e07dcd32b754a6d8a6fd06208e4 100644 --- a/lib/geocoder/stores/active_record.rb +++ b/lib/geocoder/stores/active_record.rb @@ -66,6 +66,11 @@ module Geocoder::Store # module ClassMethods + def distance_from(location, *args) + latitude, longitude = Geocoder::Calculations.extract_coordinates(location) + distance_from_options(latitude, longitude, *args) if latitude and longitude + end + private # ---------------------------------------------------------------- ## @@ -92,14 +97,19 @@ module Geocoder::Store end end + def distance_from_options(latitude, longitude, options = {}) + if connection.adapter_name.match /sqlite/i + approx_distance_from(latitude, longitude, options) + else + full_distance_from(latitude, longitude, options) + end + end + ## # Scope options hash for use with a database that supports POWER(), # SQRT(), PI(), and trigonometric functions SIN(), COS(), ASIN(), # ATAN2(), DEGREES(), and RADIANS(). # - # Distance calculations based on the excellent tutorial at: - # http://www.scribd.com/doc/2569355/Geo-Distance-Search-with-MySQL - # # Bearing calculation based on: # http://www.beginningspatial.com/calculating_bearing_one_point_another # @@ -128,11 +138,8 @@ module Geocoder::Store ")) + 360 " + "AS decimal) % 360" end - earth = Geocoder::Calculations.earth_radius(options[:units] || :mi) - distance = "#{earth} * 2 * ASIN(SQRT(" + - "POWER(SIN((#{latitude} - #{lat_attr}) * PI() / 180 / 2), 2) + " + - "COS(#{latitude} * PI() / 180) * COS(#{lat_attr} * PI() / 180) * " + - "POWER(SIN((#{longitude} - #{lon_attr}) * PI() / 180 / 2), 2) ))" + + distance = full_distance_from(latitude, longitude, options) conditions = ["#{distance} <= ?", radius] default_near_scope_options(latitude, longitude, radius, options).merge( :select => "#{options[:select] || "#{table_name}.*"}, " + @@ -142,6 +149,36 @@ module Geocoder::Store ) end + + # Distance calculations based on the excellent tutorial at: + # http://www.scribd.com/doc/2569355/Geo-Distance-Search-with-MySQL + + def full_distance_from(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 approx_distance_from(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 + ## # Scope options hash for use with a database without trigonometric # functions, like SQLite. Approach is to find objects within a square @@ -166,14 +203,8 @@ module Geocoder::Store bearing = false end - 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) + distance = approx_distance_from(latitude, longitude, options) - distance = "(#{dy} * ABS(#{lat_attr} - #{latitude}) * #{factor}) + " + - "(#{dx} * ABS(#{lon_attr} - #{longitude}) * #{factor})" b = Geocoder::Calculations.bounding_box([latitude, longitude], radius, options) conditions = [ "#{lat_attr} BETWEEN ? AND ? AND #{lon_attr} BETWEEN ? AND ?"] +