diff --git a/lib/geocoder/calculations.rb b/lib/geocoder/calculations.rb index abc8b94740fdc7bd4a6f080ce4d4d956a14d4063..8b5bcaae0e9aa6c7205c19c176293a10b9d83453 100644 --- a/lib/geocoder/calculations.rb +++ b/lib/geocoder/calculations.rb @@ -24,7 +24,8 @@ module Geocoder ## # Distance spanned by one degree of latitude in the given units. # - def latitude_degree_distance(units = :mi) + def latitude_degree_distance(units = nil) + units ||= Geocoder::Configuration.units 2 * Math::PI * earth_radius(units) / 360 end @@ -32,7 +33,8 @@ module Geocoder # Distance spanned by one degree of longitude at the given latitude. # This ranges from around 69 miles at the equator to zero at the poles. # - def longitude_degree_distance(latitude, units = :mi) + def longitude_degree_distance(latitude, units = nil) + units ||= Geocoder::Configuration.units latitude_degree_distance(units) * Math.cos(to_radians(latitude)) end @@ -49,12 +51,13 @@ module Geocoder # # The options hash supports: # - # * <tt>:units</tt> - <tt>:mi</tt> (default) or <tt>:km</tt> + # * <tt>:units</tt> - <tt>:mi</tt> or <tt>:km</tt> + # See Geocoder::Configuration to know how configure default units. # def distance_between(point1, point2, options = {}) # set default options - options[:units] ||= :mi + options[:units] ||= Geocoder::Configuration.units # convert to coordinate arrays point1 = extract_coordinates(point1) @@ -81,17 +84,18 @@ module Geocoder # See Geocoder::Calculations.distance_between for # ways of specifying the points. Also accepts an options hash: # - # * <tt>:method</tt> - <tt>:linear</tt> (default) or <tt>:spherical</tt>; + # * <tt>:method</tt> - <tt>:linear</tt> or <tt>:spherical</tt>; # the spherical method is "correct" in that it returns the shortest path - # (one along a great circle) but the linear method is the default as it - # is less confusing (returns due east or west when given two points with - # the same latitude) + # (one along a great circle) but the linear method is less confusing + # (returns due east or west when given two points with the same latitude). + # See Geocoder::Configuration to know how configure default method. # # Based on: http://www.movable-type.co.uk/scripts/latlong.html # def bearing_between(point1, point2, options = {}) # set default options + options[:method] ||= Geocoder::Configuration.method options[:method] = :linear unless options[:method] == :spherical # convert to coordinate arrays @@ -177,12 +181,13 @@ module Geocoder # See Geocoder::Calculations.distance_between for # ways of specifying the point. Also accepts an options hash: # - # * <tt>:units</tt> - <tt>:mi</tt> (default) or <tt>:km</tt> + # * <tt>:units</tt> - <tt>:mi</tt> or <tt>:km</tt>. + # See Geocoder::Configuration to know how configure default units. # def bounding_box(point, radius, options = {}) lat,lon = extract_coordinates(point) radius = radius.to_f - units = options[:units] || :mi + units = options[:units] || Geocoder::Configuration.units [ lat - (radius / latitude_degree_distance(units)), lon - (radius / longitude_degree_distance(lat, units)), @@ -219,11 +224,13 @@ module Geocoder end end - def distance_to_radians(distance, units = :mi) + def distance_to_radians(distance, units = nil) + units ||= Geocoder::Configuration.units distance.to_f / earth_radius(units) end - def radians_to_distance(radians, units = :mi) + def radians_to_distance(radians, units = nil) + units ||= Geocoder::Configuration.units radians * earth_radius(units) end @@ -242,9 +249,11 @@ module Geocoder end ## - # Radius of the Earth in the given units (:mi or :km). Default is :mi. + # Radius of the Earth in the given units (:mi or :km). + # See Geocoder::Configuration to know how configure default units. # - def earth_radius(units = :mi) + def earth_radius(units = nil) + units ||= Geocoder::Configuration.units units == :km ? EARTH_RADIUS : to_miles(EARTH_RADIUS) end @@ -277,3 +286,4 @@ module Geocoder end end end + diff --git a/lib/geocoder/configuration.rb b/lib/geocoder/configuration.rb index 108ba674ec00d6a67e2b3ed8375d126fc8875c55..867a4727d0a9203984a70e00c950bff61cf7aeec 100644 --- a/lib/geocoder/configuration.rb +++ b/lib/geocoder/configuration.rb @@ -28,8 +28,8 @@ module Geocoder # config.always_raise = [] # # # Calculation options - # @units = :km # :km for kilometers or :mi for miles - # @method = :spherical # :spherical or :linear + # @units = :mi # :km for kilometers or :mi for miles + # @method = :linear # :spherical or :linear # end # # @example Using +Geocoder::Configuration+ class directly, like in: @@ -73,8 +73,10 @@ module Geocoder @always_raise = [] # Calculation options - @units = :km # Internationl System standard unit for distance - @method = :spherical # More precise + @units = :mi # :mi or :km - Wouldn't it be better to better change this + # definitions to use the International Units System + # (:km by default)? + @method = :linear # :linear or spherical end # Delegates getters and setters for all configuration settings, diff --git a/lib/geocoder/models/active_record.rb b/lib/geocoder/models/active_record.rb index 67a17106accb7e66632091aac450bed5ceb2f699..29bbe5f406d5534ffc674599c28d4c86340f10ce 100644 --- a/lib/geocoder/models/active_record.rb +++ b/lib/geocoder/models/active_record.rb @@ -14,7 +14,9 @@ module Geocoder :user_address => address_attr, :latitude => options[:latitude] || :latitude, :longitude => options[:longitude] || :longitude, - :geocode_block => block + :geocode_block => block, + :units => options[:units], + :method => options[:method] ) end @@ -27,7 +29,9 @@ module Geocoder :fetched_address => options[:address] || :address, :latitude => latitude_attr, :longitude => longitude_attr, - :reverse_block => block + :reverse_block => block, + :units => options[:units], + :method => options[:method] ) end @@ -39,3 +43,4 @@ module Geocoder end end end + diff --git a/lib/geocoder/models/base.rb b/lib/geocoder/models/base.rb index 1e944264aced08e5633551a088ae8329cb6a7b38..d1ea75c3ef9c7b4cc514815202b93f5c35b20fb6 100644 --- a/lib/geocoder/models/base.rb +++ b/lib/geocoder/models/base.rb @@ -12,7 +12,9 @@ module Geocoder if defined?(@geocoder_options) @geocoder_options elsif superclass.respond_to?(:geocoder_options) - superclass.geocoder_options + superclass.geocoder_options || { } + else + { } end end @@ -24,7 +26,6 @@ module Geocoder fail end - private # ---------------------------------------------------------------- def geocoder_init(options) @@ -38,3 +39,4 @@ module Geocoder end end end + diff --git a/lib/geocoder/models/mongo_base.rb b/lib/geocoder/models/mongo_base.rb index 393e007fbd27793960224a222801838e674c1ab4..8c9fbe7017f45af7bafb4c6e4057ac67c2e885ad 100644 --- a/lib/geocoder/models/mongo_base.rb +++ b/lib/geocoder/models/mongo_base.rb @@ -16,7 +16,9 @@ module Geocoder :geocode => true, :user_address => address_attr, :coordinates => options[:coordinates] || :coordinates, - :geocode_block => block + :geocode_block => block, + :units => options[:units], + :method => options[:method] ) end @@ -28,7 +30,9 @@ module Geocoder :reverse_geocode => true, :fetched_address => options[:address] || :address, :coordinates => coordinates_attr, - :reverse_block => block + :reverse_block => block, + :units => options[:units], + :method => options[:method] ) end @@ -36,7 +40,7 @@ module Geocoder def geocoder_init(options) unless geocoder_initialized? - @geocoder_options = {} + @geocoder_options = { } require "geocoder/stores/#{geocoder_file_name}" include eval("Geocoder::Store::" + geocoder_module_name) end @@ -53,3 +57,4 @@ module Geocoder end end end + diff --git a/lib/geocoder/stores/active_record.rb b/lib/geocoder/stores/active_record.rb index fb0b478bcf2318f299f8993eda62790df04fb9e9..2438d4f1da2c2a79cf061a6c0d30fe792683c3b6 100644 --- a/lib/geocoder/stores/active_record.rb +++ b/lib/geocoder/stores/active_record.rb @@ -51,22 +51,23 @@ module Geocoder::Store ## # Get options hash suitable for passing to ActiveRecord.find to get - # records within a radius (in miles) of the given point. + # records within a radius (in kilometers) of the given point. # Options hash may include: # - # * +:units+ - <tt>:mi</tt> (default) or <tt>:km</tt>; to be used + # * +:units+ - <tt>:mi</tt> or <tt>:km</tt>; to be used. # for interpreting radius as well as the +distance+ attribute which - # is added to each found nearby object - # * +:bearing+ - <tt>:linear</tt> (default) or <tt>:spherical</tt>; + # is added to each found nearby object. + # See Geocoder::Configuration to know how configure default units. + # * +:bearing+ - <tt>:linear</tt> or <tt>:spherical</tt>. # the method to be used for calculating the bearing (direction) # between the given point and each found nearby point; - # set to false for no bearing calculation + # set to false for no bearing calculation. + # See Geocoder::Configuration to know how configure default method. # * +:select+ - string with the SELECT SQL fragment (e.g. “id, nameâ€) # * +:order+ - column(s) for ORDER BY SQL clause; default is distance # * +:exclude+ - an object to exclude (used by the +nearbys+ method) # def near_scope_options(latitude, longitude, radius = 20, options = {}) - radius *= Geocoder::Calculations.km_in_mi if options[:units] == :km if connection.adapter_name.match /sqlite/i approx_near_scope_options(latitude, longitude, radius, options) else @@ -88,7 +89,9 @@ module Geocoder::Store 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) + options[:bearing] ||= (options[:method] || + geocoder_options[:method] || + Geocoder::Configuration.method) bearing = case options[:bearing] when :linear "CAST(" + @@ -110,7 +113,8 @@ module Geocoder::Store ")) + 360 " + "AS decimal) % 360" end - earth = Geocoder::Calculations.earth_radius(options[:units] || :mi) + options[:units] ||= (geocoder_options[:units] || Geocoder::Configuration.units) + earth = Geocoder::Calculations.earth_radius(options[:units]) distance = "#{earth} * 2 * ASIN(SQRT(" + "POWER(SIN((#{latitude} - #{lat_attr}) * PI() / 180 / 2), 2) + " + "COS(#{latitude} * PI() / 180) * COS(#{lat_attr} * PI() / 180) * " + @@ -136,7 +140,11 @@ module Geocoder::Store 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) + unless options.include?(:bearing) + options[:bearing] = (options[:method] || \ + geocoder_options[:method] || \ + Geocoder::Configuration.method) + end if options[:bearing] bearing = "CASE " + "WHEN (#{lat_attr} >= #{latitude} AND #{lon_attr} >= #{longitude}) THEN 45.0 " + @@ -148,8 +156,9 @@ 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) + options[:units] ||= (geocoder_options[:units] || Geocoder::Configuration.units) + dx = Geocoder::Calculations.longitude_degree_distance(30, options[:units]) + dy = Geocoder::Calculations.latitude_degree_distance(options[:units]) # sin of 45 degrees = average x or y component of vector factor = Math.sin(Math::PI / 4) @@ -222,3 +231,4 @@ module Geocoder::Store alias_method :fetch_address, :reverse_geocode end end + diff --git a/lib/geocoder/stores/base.rb b/lib/geocoder/stores/base.rb index 13a0a3532c56d07ed3e8489aa0467e13ab4bc826..06d96a7ee3d755bb7037b17e2cf798f74b920273 100644 --- a/lib/geocoder/stores/base.rb +++ b/lib/geocoder/stores/base.rb @@ -20,9 +20,10 @@ module Geocoder # Calculate the distance from the object to an arbitrary point. # See Geocoder::Calculations.distance_between for ways of specifying # the point. Also takes a symbol specifying the units - # (:mi or :km; default is :mi). + # (:mi or :km; can be specified in Geocoder configuration). # - def distance_to(point, units = :mi) + def distance_to(point, units = nil) + units ||= self.class.geocoder_options[:units] return nil unless geocoded? Geocoder::Calculations.distance_between( to_coordinates, point, :units => units) @@ -36,6 +37,7 @@ module Geocoder # ways of specifying the point. # def bearing_to(point, options = {}) + options[:method] ||= self.class.geocoder_options[:method] return nil unless geocoded? Geocoder::Calculations.bearing_between( to_coordinates, point, options) @@ -47,6 +49,7 @@ module Geocoder # ways of specifying the point. # def bearing_from(point, options = {}) + options[:method] ||= self.class.geocoder_options[:method] return nil unless geocoded? Geocoder::Calculations.bearing_between( point, to_coordinates, options) @@ -78,7 +81,6 @@ module Geocoder fail end - private # -------------------------------------------------------------- ## @@ -115,3 +117,4 @@ module Geocoder end end end + diff --git a/lib/geocoder/stores/mongo_base.rb b/lib/geocoder/stores/mongo_base.rb index 9203e2147b8dc0a0a4542bb76576cefbceaaef68..7daa57f9e4b6782dbc5ac60297255afc38a91968 100644 --- a/lib/geocoder/stores/mongo_base.rb +++ b/lib/geocoder/stores/mongo_base.rb @@ -20,6 +20,7 @@ module Geocoder::Store radius = args.size > 0 ? args.shift : 20 options = args.size > 0 ? args.shift : {} + options[:units] ||= geocoder_options[:units] # Use BSON::OrderedHash if Ruby's hashes are unordered. # Conditions must be in order required by indexes (see mongo gem). @@ -30,7 +31,7 @@ module Geocoder::Store conds[field] = empty.clone conds[field]["$nearSphere"] = coords.reverse conds[field]["$maxDistance"] = \ - Geocoder::Calculations.distance_to_radians(radius, options[:units] || :mi) + Geocoder::Calculations.distance_to_radians(radius, options[:units]) if obj = options[:exclude] conds[:_id.ne] = obj.id @@ -79,3 +80,4 @@ module Geocoder::Store end end end + diff --git a/test/calculations_test.rb b/test/calculations_test.rb index 25343cb7a5f3ae3f53153e9bf3fafe57fa1bcdde..fd36884e1b74994cec2f2399242a4d4571ab4bf7 100644 --- a/test/calculations_test.rb +++ b/test/calculations_test.rb @@ -2,7 +2,12 @@ require 'test_helper' class CalculationsTest < Test::Unit::TestCase - + def setup + Geocoder.configure do + config.units = :mi + config.method = :linear + end + end # --- degree distance --- @@ -145,3 +150,4 @@ class CalculationsTest < Test::Unit::TestCase assert_equal l.bearing_from([50,-86.1]), l.bearing_to([50,-86.1]) - 180 end end + diff --git a/test/configuration_test.rb b/test/configuration_test.rb index 2812fe432caf2ef84a09c888ce4d43019bffebc1..598d06370cb410613c975f4578b5bf063ce89818 100644 --- a/test/configuration_test.rb +++ b/test/configuration_test.rb @@ -13,39 +13,75 @@ class ConfigurationTest < Test::Unit::TestCase end end - # --- default configuration --- - def test_default_units_in_kilometers - assert_equal 111, Geocoder::Calculations.distance_between([0,0], [0,1]).round - end - # --- class method configuration --- def test_configurated_by_class_method Geocoder::Configuration.units = :mi distance = Geocoder::Calculations.distance_between([0,0], [0,1]).round assert_not_equal 111, distance - assert_equal 69, distance + assert_equal 69, distance Geocoder::Configuration.units = :km distance = Geocoder::Calculations.distance_between([0,0], [0,1]).round - assert_equal 111, distance + assert_equal 111, distance assert_not_equal 69, distance + + Geocoder::Configuration.method = :spherical + angle = Geocoder::Calculations.bearing_between([50,-85], [40.750354, -73.993371]).round + assert_equal 136, angle + assert_not_equal 130, angle + + Geocoder::Configuration.method = :linear + angle = Geocoder::Calculations.bearing_between([50,-85], [40.750354, -73.993371]).round + assert_not_equal 136, angle + assert_equal 130, angle end # --- Geocoder#configure method configuration --- def test_geocoder_configuration - Geocoder.configure { config.units = :mi } + # DSL + Geocoder.configure do + config.units = :mi + config.method = :linear + end assert_equal Geocoder::Configuration.units, :mi distance = Geocoder::Calculations.distance_between([0,0], [0,1]).round assert_not_equal 111, distance - assert_equal 69, distance + assert_equal 69, distance - Geocoder.configure.units = :km + assert_equal Geocoder::Configuration.method, :linear + angle = Geocoder::Calculations.bearing_between([50,-85], [40.750354, -73.993371]).round + assert_not_equal 136, angle + assert_equal 130, angle + + # Direct + Geocoder.configure.units = :km + Geocoder.configure.method = :spherical assert_equal Geocoder::Configuration.units, :km distance = Geocoder::Calculations.distance_between([0,0], [0,1]).round - assert_equal 111, distance + assert_equal 111, distance assert_not_equal 69, distance + + assert_equal Geocoder::Configuration.method, :spherical + angle = Geocoder::Calculations.bearing_between([50,-85], [40.750354, -73.993371]).round + assert_equal 136, angle + assert_not_equal 130, angle + end + + # Geocoder per-model configuration + def test_model_configuration + Landmark.reverse_geocoded_by :latitude, :longitude, :method => :spherical, :units => :km + assert_equal Landmark.geocoder_options[:units], :km + assert_equal :spherical, Landmark.geocoder_options[:method] + + venue = Landmark.new(*landmark_params(:msg)) + venue.latitude = 0 + venue.longitude = 0 + assert_equal 111, venue.distance_to([0,1]).round + venue.latitude = 40.750354 + venue.longitude = -73.993371 + assert_equal 136, venue.bearing_from([50,-85]).round end end diff --git a/test/test_helper.rb b/test/test_helper.rb index f491504aaed96ec1748d11ce86f3640fa1a395cf..7379bee0597bff10aa0fa6c6416f816370303e68 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -29,6 +29,7 @@ module ActiveRecord def self.scope(*args); end def method_missing(name, *args, &block) + puts "Nao incluiu direito no active record..." if name == "geocoder_options" if name.to_s[-1..-1] == "=" write_attribute name.to_s[0...-1], *args else @@ -244,3 +245,4 @@ class Test::Unit::TestCase all_lookups - [:freegeoip] end end +