diff --git a/lib/geocoder/calculations.rb b/lib/geocoder/calculations.rb index bd0ed118a459f2afec6836bfd926c6d626907450..83cf25ddb6350761d3710428e9109ead216dac7f 100644 --- a/lib/geocoder/calculations.rb +++ b/lib/geocoder/calculations.rb @@ -117,11 +117,8 @@ module Geocoder # def geographic_center(points) - # convert objects to [lat,lon] arrays and remove nils - points = points.map{ |p| p.is_a?(Array) ? p : p.to_coordinates }.compact - - # convert degrees to radians - points.map!{ |p| to_radians(p) } + # convert objects to [lat,lon] arrays and convert degrees to radians + points = points.map{ |p| to_radians(extract_coordinates(p)) } # convert to Cartesian coordinates x = []; y = []; z = [] @@ -236,5 +233,19 @@ module Geocoder def mi_in_km 1.0 / KM_IN_MI end + + ## + # Takes an object which is a [lat,lon] array, a geocodable string, + # or an object that implements +to_coordinates+ and returns a + # [lat,lon] array. Note that if a string is passed this may be a slow- + # running method and may return nil. + # + def extract_coordinates(point) + case point + when Array; point + when String; Geocoder.coordinates(point) + else point.to_coordinates + end + end end end diff --git a/lib/geocoder/orms/active_record.rb b/lib/geocoder/orms/active_record.rb index 908dca1d4c171d34a8f4b70c8fed016e888c0267..615b044f2a6cf63402507f3cf0f0327a6d3f0be6 100644 --- a/lib/geocoder/orms/active_record.rb +++ b/lib/geocoder/orms/active_record.rb @@ -34,8 +34,7 @@ module Geocoder::Orm # for details). # scope :near, lambda{ |location, *args| - latitude, longitude = location.is_a?(Array) ? - location : Geocoder.coordinates(location) + latitude, longitude = Geocoder::Calculations.extract_coordinates(location) if latitude and longitude near_scope_options(latitude, longitude, *args) else diff --git a/lib/geocoder/orms/base.rb b/lib/geocoder/orms/base.rb index 0c018a497f73f093d3c6c8203999710ed0394f94..cf256ddb9e68582fac0b1aacca681dec41bc07aa 100644 --- a/lib/geocoder/orms/base.rb +++ b/lib/geocoder/orms/base.rb @@ -29,6 +29,33 @@ module Geocoder alias_method :distance_from, :distance_to + ## + # Calculate the bearing from the object to another point. + # The point can be: + # + # * an array of coordinates ([lat,lon]) + # * a geocoded object (one which implements a +to_coordinates+ method + # which returns a [lat,lon] array + # * a geocodable address (string) + # + def bearing_to(point, options = {}) + return nil unless them = Geocoder::Calculations.extract_coordinates(point) + us = to_coordinates + Geocoder::Calculations.bearing_between( + us[0], us[1], them[0], them[1], options) + end + + ## + # Calculate the bearing from another point to the object. + # See bearing_to for details. + # + def bearing_from(point, options = {}) + return nil unless them = Geocoder::Calculations.extract_coordinates(point) + us = to_coordinates + Geocoder::Calculations.bearing_between( + them[0], them[1], us[0], us[1], options) + end + ## # Get nearby geocoded objects. # Takes the same options hash as the near class method (scope). @@ -40,7 +67,7 @@ module Geocoder warn "DEPRECATION WARNING: The units argument to the nearbys method has been replaced with an options hash (same options hash as the near scope). You should instead call: obj.nearbys(#{radius}, :units => #{options[:units]}). The old syntax will not be supported in Geocoder v1.0." end options.merge!(:exclude => self) - self.class.near(to_coordinates, radius, options) + self.class.near(self, radius, options) end ## diff --git a/lib/geocoder/orms/mongoid.rb b/lib/geocoder/orms/mongoid.rb index 462089635f2a9fa101d6b70c5f5ad573aa868277..76506f273ddf2f2a417a691e226f30b79d92fa9f 100644 --- a/lib/geocoder/orms/mongoid.rb +++ b/lib/geocoder/orms/mongoid.rb @@ -16,11 +16,10 @@ module Geocoder::Orm } scope :near, lambda{ |location, *args| + coords = Geocoder::Calculations.extract_coordinates(location) radius = args.size > 0 ? args.shift : 20 options = args.size > 0 ? args.shift : {} - coords = location.is_a?(Array) ? - location : Geocoder.coordinates(location) - conds = {:coordinates => { + conds = {:coordinates => { "$nearSphere" => coords.reverse, "$maxDistance" => Geocoder::Calculations.distance_to_radians( radius, options[:units] || :mi) diff --git a/test/geocoder_test.rb b/test/geocoder_test.rb index cad207d299250d52250ffe1183fe320abce60fb0..16ccee5dccc341e18def9427ca13ae53aae542e8 100644 --- a/test/geocoder_test.rb +++ b/test/geocoder_test.rb @@ -248,6 +248,21 @@ class GeocoderTest < Test::Unit::TestCase end end + def test_spherical_bearing_to + l = Landmark.new(*landmark_params(:msg)) + assert_equal 324, l.bearing_to([50,-85], :method => :spherical).round + end + + def test_spherical_bearing_from + l = Landmark.new(*landmark_params(:msg)) + assert_equal 136, l.bearing_from([50,-85], :method => :spherical).round + end + + def test_linear_bearing_from_and_to_are_exactly_opposite + l = Landmark.new(*landmark_params(:msg)) + assert_equal l.bearing_from([50,-86.1]), l.bearing_to([50,-86.1]) - 180 + end + # --- input handling --- @@ -280,6 +295,13 @@ class GeocoderTest < Test::Unit::TestCase end end + def test_extract_coordinates + coords = [-23,47] + l = Landmark.new("Madagascar", coords[0], coords[1]) + assert_equal coords, Geocoder::Calculations.extract_coordinates(l) + assert_equal coords, Geocoder::Calculations.extract_coordinates(coords) + end + # --- error handling ---