Skip to content

Commit

Permalink
Models chenged to use the configurations for units and method. Some t…
Browse files Browse the repository at this point in the history
…ests included.
  • Loading branch information
abravalheri committed Jul 21, 2011
1 parent 32ddeff commit 5c2cf49
Show file tree
Hide file tree
Showing 11 changed files with 135 additions and 52 deletions.
38 changes: 24 additions & 14 deletions lib/geocoder/calculations.rb
Expand Up @@ -24,15 +24,17 @@ module Calculations
##
# 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

##
# 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

Expand All @@ -49,12 +51,13 @@ def longitude_degree_distance(latitude, units = :mi)
#
# 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)
Expand All @@ -81,17 +84,18 @@ def distance_between(point1, point2, options = {})
# 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
Expand Down Expand Up @@ -177,12 +181,13 @@ def geographic_center(points)
# 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)),
Expand Down Expand Up @@ -219,11 +224,13 @@ def to_degrees(*args)
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

Expand All @@ -242,9 +249,11 @@ def to_miles(km)
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

Expand Down Expand Up @@ -277,3 +286,4 @@ def extract_coordinates(point)
end
end
end

10 changes: 6 additions & 4 deletions lib/geocoder/configuration.rb
Expand Up @@ -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:
Expand Down Expand Up @@ -73,8 +73,10 @@ def set_defaults
@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,
Expand Down
9 changes: 7 additions & 2 deletions lib/geocoder/models/active_record.rb
Expand Up @@ -14,7 +14,9 @@ def geocoded_by(address_attr, options = {}, &block)
: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

Expand All @@ -27,7 +29,9 @@ def reverse_geocoded_by(latitude_attr, longitude_attr, options = {}, &block)
:fetched_address => options[:address] || :address,
:latitude => latitude_attr,
:longitude => longitude_attr,
:reverse_block => block
:reverse_block => block,
:units => options[:units],
:method => options[:method]
)
end

Expand All @@ -39,3 +43,4 @@ def geocoder_module_name; "ActiveRecord"; end
end
end
end

6 changes: 4 additions & 2 deletions lib/geocoder/models/base.rb
Expand Up @@ -12,7 +12,9 @@ def geocoder_options
if defined?(@geocoder_options)
@geocoder_options
elsif superclass.respond_to?(:geocoder_options)
superclass.geocoder_options
superclass.geocoder_options || { }
else
{ }
end
end

Expand All @@ -24,7 +26,6 @@ def reverse_geocoded_by
fail
end


private # ----------------------------------------------------------------

def geocoder_init(options)
Expand All @@ -38,3 +39,4 @@ def geocoder_init(options)
end
end
end

11 changes: 8 additions & 3 deletions lib/geocoder/models/mongo_base.rb
Expand Up @@ -16,7 +16,9 @@ def geocoded_by(address_attr, options = {}, &block)
:geocode => true,
:user_address => address_attr,
:coordinates => options[:coordinates] || :coordinates,
:geocode_block => block
:geocode_block => block,
:units => options[:units],
:method => options[:method]
)
end

Expand All @@ -28,15 +30,17 @@ def reverse_geocoded_by(coordinates_attr, options = {}, &block)
:reverse_geocode => true,
:fetched_address => options[:address] || :address,
:coordinates => coordinates_attr,
:reverse_block => block
:reverse_block => block,
:units => options[:units],
:method => options[:method]
)
end

private # ----------------------------------------------------------------

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
Expand All @@ -53,3 +57,4 @@ def geocoder_initialized?
end
end
end

32 changes: 21 additions & 11 deletions lib/geocoder/stores/active_record.rb
Expand Up @@ -51,22 +51,23 @@ module ClassMethods

##
# 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
Expand All @@ -88,7 +89,9 @@ def near_scope_options(latitude, longitude, radius = 20, options = {})
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(" +
Expand All @@ -110,7 +113,8 @@ def full_near_scope_options(latitude, longitude, radius, options)
")) + 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) * " +
Expand All @@ -136,7 +140,11 @@ def full_near_scope_options(latitude, longitude, radius, options)
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 " +
Expand All @@ -148,8 +156,9 @@ def approx_near_scope_options(latitude, longitude, radius, options)
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)
Expand Down Expand Up @@ -222,3 +231,4 @@ def reverse_geocode
alias_method :fetch_address, :reverse_geocode
end
end

9 changes: 6 additions & 3 deletions lib/geocoder/stores/base.rb
Expand Up @@ -20,9 +20,10 @@ def to_coordinates
# 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)
Expand All @@ -36,6 +37,7 @@ def distance_to(point, units = :mi)
# 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)
Expand All @@ -47,6 +49,7 @@ def bearing_to(point, options = {})
# 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)
Expand Down Expand Up @@ -78,7 +81,6 @@ def reverse_geocode
fail
end


private # --------------------------------------------------------------

##
Expand Down Expand Up @@ -115,3 +117,4 @@ def do_lookup(reverse = false)
end
end
end

4 changes: 3 additions & 1 deletion lib/geocoder/stores/mongo_base.rb
Expand Up @@ -20,6 +20,7 @@ def self.included_by_model(base)

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).
Expand All @@ -30,7 +31,7 @@ def self.included_by_model(base)
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
Expand Down Expand Up @@ -79,3 +80,4 @@ def reverse_geocode
end
end
end

8 changes: 7 additions & 1 deletion test/calculations_test.rb
Expand Up @@ -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 ---

Expand Down Expand Up @@ -145,3 +150,4 @@ def test_linear_bearing_from_and_to_are_exactly_opposite
assert_equal l.bearing_from([50,-86.1]), l.bearing_to([50,-86.1]) - 180
end
end

0 comments on commit 5c2cf49

Please sign in to comment.