geo-distance2 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,29 @@
1
+ class GeoDistance
2
+ module Conversion
3
+ module Meters
4
+ def in_meters
5
+ convert_to_meters distance
6
+ end
7
+ alias_method :to_meters, :in_meters
8
+ alias_method :as_meters, :in_meters
9
+
10
+ def to_meters!
11
+ self.distance = convert_to_meters distance
12
+ self.unit = :meters
13
+ self
14
+ end
15
+ alias_method :in_meters!, :to_meters!
16
+ alias_method :as_meters!, :to_meters!
17
+
18
+ def convert_to_meters dist
19
+ (unit == :meters) ? dist : distance / GeoUnits.meters_map[unit]
20
+ end
21
+
22
+ def to_meters!
23
+ @distance = in_meters
24
+ @unit = :meters
25
+ self
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,36 @@
1
+ class GeoDistance
2
+ module Conversion
3
+ module Radians
4
+ def to_radians
5
+ cloned = self.dup
6
+ cloned.distance = in_radians
7
+ cloned.unit = :radians
8
+ cloned
9
+ end
10
+
11
+ def to_radians! lat = 0
12
+ @distance = in_radians(lat)
13
+ @unit = :radians
14
+ end
15
+
16
+ def radians_conversion_factor
17
+ unit.radians_ratio
18
+ end
19
+
20
+ # calculate the distance in radians for the given latitude
21
+ def in_radians lat = 0
22
+ (unit != :radians) ? distance.to_f / earth_factor(lat) : distance # radians_conversion_factor
23
+ end
24
+
25
+ protected
26
+
27
+ def earth_factor u = nil, lat = 0
28
+ (GeoDistance.earth_radius[u ||= unit] / 180) * latitude_factor(lat)
29
+ end
30
+
31
+ def latitude_factor latitude = 0
32
+ 90 / (90 - latitude)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,20 @@
1
+ require 'sugar-high/numeric'
2
+
3
+ module GeoDistanceExt
4
+ ::GeoDistance.all_units.each do |unit|
5
+ class_eval %{
6
+ def #{unit}
7
+ GeoDistance.new(self, :#{unit})
8
+ end
9
+ }
10
+ end
11
+ end
12
+
13
+ class Fixnum
14
+ include GeoDistanceExt
15
+ end
16
+
17
+ class Float
18
+ include GeoDistanceExt
19
+ end
20
+
@@ -0,0 +1,24 @@
1
+ require 'geo-distance/class_methods'
2
+
3
+ class GeoDistance
4
+ include Comparable
5
+
6
+ include Conversion
7
+
8
+ attr_accessor :distance, :unit
9
+
10
+ def initialize distance, unit = :radians
11
+ @distance = distance
12
+ @unit = GeoUnits.key(unit)
13
+ end
14
+
15
+ alias_method :units, :unit
16
+
17
+ def <=> other
18
+ in_meters <=> other.in_meters
19
+ end
20
+
21
+ def number
22
+ distance.round_to(precision[unit])
23
+ end
24
+ end
@@ -0,0 +1,62 @@
1
+ class GeoDistance
2
+ include GeoUnits
3
+
4
+ class DistanceFormula
5
+ extend Math
6
+ extend GeoUnits
7
+
8
+ def initialize
9
+ raise NotImplementedError
10
+ end
11
+
12
+ # use underlying distance formula
13
+ def self.geo_distance *args
14
+ GeoDistance.new distance(args), get_units(args.last_option)
15
+ end
16
+
17
+ # used to convert various argument types into GeoPoints
18
+ def self.get_points(*args)
19
+ args.flatten!
20
+ options = args.delete(args.last_option) || {}
21
+ units = options[:units] || GeoDistance.default_units
22
+
23
+ case args.size
24
+ when 2
25
+ [GeoPoint.new(args.first), GeoPoint.new(args.last), units]
26
+ when 4
27
+ [GeoPoint.new(args[0..1]), GeoPoint.new(args[2..3]), units]
28
+ else
29
+ raise "Distance from point A to B, must be given either as 4 arguments (lat1, lng1, lat2, lng2) or 2 arguments: (pointA, pointB), was: #{args}"
30
+ end
31
+ end
32
+
33
+ # used to get the units for how to calculate the distance
34
+ def self.get_units options = {}
35
+ GeoUnits.key(options[:units] || :kms)
36
+ end
37
+
38
+ # def self.degrees_to_radians(degrees)
39
+ # # degrees.to_f / 180.0 * Math::PI
40
+ # degrees.to_f * radians_per_degree
41
+ # end
42
+
43
+ # def self.units_sphere_multiplier(units)
44
+ # earth_radius_map[GeoUnit.key units]
45
+ # end
46
+ #
47
+ # def self.units_per_latitude_degree(units)
48
+ # GeoUnits.radian_multiplier[units.to_sym]
49
+ # end
50
+ #
51
+ # def self.units_per_longitude_degree(lat, units)
52
+ # miles_per_longitude_degree = (latitude_degrees * Math.cos(lat * pi_div_rad)).abs
53
+ # case units
54
+ # when :kms
55
+ # miles_per_longitude_degree * kms_per_mile
56
+ # when :miles
57
+ # miles_per_longitude_degree
58
+ # end
59
+ # end
60
+ end
61
+ end
62
+
@@ -0,0 +1,21 @@
1
+ require 'geo-distance/core_ext'
2
+ require 'geo-distance/formula'
3
+
4
+ class GeoDistance
5
+ class Flat < DistanceFormula
6
+
7
+ # Calculate distance using Flat earth formula, returns distance in whatever unit is specified (kms by default)
8
+ def self.distance *args
9
+ from, to, units = get_points(args)
10
+
11
+ return 0 if from == to # return 0.0 if points are have the same coordinates
12
+
13
+ Math.sqrt(
14
+ (units_per_latitude_degree(units) * (from.lat - to.lat))**2 +
15
+ (units_per_longitude_degree(from.lat, units) * (from.lng - to.lng))**2
16
+ )
17
+ end
18
+ end
19
+ end
20
+
21
+
@@ -0,0 +1,60 @@
1
+ #
2
+ # haversine formula to compute the great circle distance between two points given their latitude and longitudes
3
+ #
4
+ # Copyright (C) 2008, 360VL, Inc
5
+ # Copyright (C) 2008, Landon Cox
6
+ #
7
+ # http://www.esawdust.com (Landon Cox)
8
+ # contact:
9
+ # http://www.esawdust.com/blog/businesscard/businesscard.html
10
+ #
11
+ # LICENSE: GNU Affero GPL v3
12
+ # The ruby implementation of the Haversine formula is free software: you can redistribute it and/or modify
13
+ # it under the terms of the GNU Affero General Public License version 3 as published by the Free Software Foundation.
14
+ #
15
+ # This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
16
+ # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
17
+ # License version 3 for more details. http://www.gnu.org/licenses/
18
+ #
19
+ # Landon Cox - 9/25/08
20
+ #
21
+ # Notes:
22
+ #
23
+ # translated into Ruby based on information contained in:
24
+ # http://mathforum.org/library/drmath/view/51879.html Doctors Rick and Peterson - 4/20/99
25
+ # http://www.movable-type.co.uk/scripts/latlong.html
26
+ # http://en.wikipedia.org/wiki/Haversine_formula
27
+ #
28
+ # This formula can compute accurate distances between two points given latitude and longitude, even for
29
+ # short distances.
30
+
31
+ # PI = 3.1415926535
32
+
33
+ require 'geo-distance/core_ext'
34
+ require 'geo-distance/formula'
35
+
36
+ class GeoDistance
37
+ class Haversine < DistanceFormula
38
+ # given two lat/lon points, compute the distance between the two points using the haversine formula
39
+ # the result will be a Hash of distances which are key'd by 'mi','km','ft', and 'm'
40
+
41
+ def self.distance *args
42
+ begin
43
+ from, to, units = get_points(args)
44
+
45
+ lat1, lon1, lat2, lon2 = [from.lat, from.lng, to.lat, to.lng]
46
+ dlon = lon2 - lon1
47
+ dlat = lat2 - lat1
48
+
49
+ a = (Math.sin(dlat.rpd/2))**2 + Math.cos(lat1.rpd) * Math.cos((lat2.rpd)) * (Math.sin(dlon.rpd/2))**2
50
+ c = (2 * Math.atan2( Math.sqrt(a), Math.sqrt(1-a)))
51
+ c = c.to_deg
52
+
53
+ units ? c.radians_to(units) : c
54
+ rescue Errno::EDOM
55
+ 0.0
56
+ end
57
+ end
58
+ end
59
+
60
+ end
@@ -0,0 +1,30 @@
1
+ class GeoDistance
2
+ class NVector < DistanceFormula
3
+ def self.distance *args
4
+ from, to, units = get_points(args)
5
+ lat1, lon1, lat2, lon2 = [from.lat, from.lng, to.lat, to.lng]
6
+
7
+ sin_x1 = Math.sin(lon1)
8
+ cos_x1 = Math.cos(lon1)
9
+
10
+ sin_y1 = Math.sin(lat1)
11
+ cos_y1 = Math.cos(lat1)
12
+
13
+ sin_x2 = Math.sin(lon2)
14
+ cos_x2 = Math.cos(lon2)
15
+
16
+ sin_y2 = Math.sin(lat2)
17
+ cos_y2 = Math.cos(lat2)
18
+
19
+ cross_prod = (cos_y1*cos_x1 * cos_y2*cos_x2) + (cos_y1*sin_x1 * cos_y2*sin_x2) + (sin_y1 * sin_y2)
20
+
21
+ c = Math.acos(cross_prod)
22
+
23
+ # puts "c: #{c}"
24
+ # radians_per_degree
25
+ c = (c * 0.0201324).to_deg
26
+
27
+ units ? c.radians_to(units) : c
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,44 @@
1
+ require 'geo-distance/core_ext'
2
+ require 'geo-distance/formula'
3
+
4
+ # module GeoDistance
5
+ # class Spherical < DistanceFormula
6
+ # def self.distance( lat1, lon1, lat2, lon2)
7
+ # from_longitude = lon1.to_radians
8
+ # from_latitude = lat1.to_radians
9
+ # to_longitude = lon2.to_radians
10
+ # to_latitude = lat2.to_radians
11
+ #
12
+ # c = Math.acos(
13
+ # Math.sin(from_latitude) *
14
+ # Math.sin(to_latitude) +
15
+ #
16
+ # Math.cos(from_latitude) *
17
+ # Math.cos(to_latitude) *
18
+ # Math.cos(to_longitude - from_longitude)
19
+ # ) #* EARTH_RADIUS[units.to_sym]
20
+ #
21
+ # GeoDistance::Distance.new c
22
+ # end
23
+ # end
24
+ # end
25
+
26
+ class GeoDistance
27
+ class Spherical < DistanceFormula
28
+ def self.distance *args
29
+ from, to, units = get_points(args)
30
+
31
+ return 0.0 if from == to #return 0.0 if points are have the same coordinates
32
+
33
+ c = Math.acos(
34
+ Math.sin(degrees_to_radians(from.lat)) * Math.sin(degrees_to_radians(to.lat)) +
35
+ Math.cos(degrees_to_radians(from.lat)) * Math.cos(degrees_to_radians(to.lat)) *
36
+ Math.cos(degrees_to_radians(to.lng) - degrees_to_radians(from.lng))
37
+ ).to_deg
38
+
39
+ units ? c.radians_to(units) : c
40
+ rescue Errno::EDOM
41
+ 0.0
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,80 @@
1
+ require 'geo-distance/core_ext'
2
+ require 'geo-distance/formula'
3
+
4
+ class GeoDistance
5
+ class Vincenty < DistanceFormula
6
+
7
+ # Calculate the distance between two Locations using the Vincenty formula
8
+ #
9
+ def self.distance *args
10
+ begin
11
+ from, to, units = get_points(args)
12
+ lat1, lon1, lat2, lon2 = [from.lat, from.lng, to.lat, to.lng]
13
+
14
+ from_longitude = lon1.to_radians
15
+ from_latitude = lat1.to_radians
16
+ to_longitude = lon2.to_radians
17
+ to_latitude = lat2.to_radians
18
+
19
+ earth_major_axis_radius = earth_major_axis_radius_map[:kilometers]
20
+ earth_minor_axis_radius = earth_minor_axis_radius_map[:kilometers]
21
+
22
+ f = (earth_major_axis_radius - earth_minor_axis_radius) / earth_major_axis_radius
23
+
24
+ l = to_longitude - from_longitude
25
+ u1 = atan((1-f) * tan(from_latitude))
26
+ u2 = atan((1-f) * tan(to_latitude))
27
+ sin_u1 = sin(u1)
28
+ cos_u1 = cos(u1)
29
+ sin_u2 = sin(u2)
30
+ cos_u2 = cos(u2)
31
+
32
+ lambda = l
33
+ lambda_p = 2 * Math::PI
34
+ iteration_limit = 20
35
+ while (lambda-lambda_p).abs > 1e-12 && (iteration_limit -= 1) > 0
36
+ sin_lambda = sin(lambda)
37
+ cos_lambda = cos(lambda)
38
+ sin_sigma = sqrt((cos_u2*sin_lambda) * (cos_u2*sin_lambda) +
39
+ (cos_u1*sin_u2-sin_u1*cos_u2*cos_lambda) * (cos_u1*sin_u2-sin_u1*cos_u2*cos_lambda))
40
+ return 0 if sin_sigma == 0 # co-incident points
41
+ cos_sigma = sin_u1*sin_u2 + cos_u1*cos_u2*cos_lambda
42
+ sigma = atan2(sin_sigma, cos_sigma)
43
+ sin_alpha = cos_u1 * cos_u2 * sin_lambda / sin_sigma
44
+ cosSqAlpha = 1 - sin_alpha*sin_alpha
45
+ cos2SigmaM = cos_sigma - 2*sin_u1*sin_u2/cosSqAlpha
46
+
47
+ cos2SigmaM = 0 if cos2SigmaM.nan? # equatorial line: cosSqAlpha=0 (§6)
48
+
49
+ c = f/16*cosSqAlpha*(4+f*(4-3*cosSqAlpha))
50
+ lambda_p = lambda
51
+ lambda = l + (1-c) * f * sin_alpha *
52
+ (sigma + c*sin_sigma*(cos2SigmaM+c*cos_sigma*(-1+2*cos2SigmaM*cos2SigmaM)))
53
+ end
54
+ # formula failed to converge (happens on antipodal points)
55
+ # We'll call Haversine formula instead.
56
+ return Haversine.distance(from, to, units) if iteration_limit == 0
57
+
58
+ uSq = cosSqAlpha * (earth_major_axis_radius**2 - earth_minor_axis_radius**2) / (earth_minor_axis_radius**2)
59
+ a = 1 + uSq/16384*(4096+uSq*(-768+uSq*(320-175*uSq)))
60
+ b = uSq/1024 * (256+uSq*(-128+uSq*(74-47*uSq)))
61
+ delta_sigma = b*sin_sigma*(cos2SigmaM+b/4*(cos_sigma*(-1+2*cos2SigmaM*cos2SigmaM)-
62
+ b/6*cos2SigmaM*(-3+4*sin_sigma*sin_sigma)*(-3+4*cos2SigmaM*cos2SigmaM)))
63
+
64
+ c = earth_minor_axis_radius * a * (sigma-delta_sigma)
65
+
66
+ c = (c / unkilometer).to_deg
67
+
68
+ units ? c.radians_to(units) : c
69
+ rescue Errno::EDOM
70
+ 0.0
71
+ end
72
+ end
73
+
74
+ private
75
+
76
+ def self.unkilometer
77
+ 6378.135
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,18 @@
1
+ class GeoDistance
2
+ module Scale
3
+ def * arg
4
+ multiply arg
5
+ end
6
+
7
+ def / arg
8
+ multiply(1.0/arg)
9
+ self
10
+ end
11
+
12
+ def multiply arg
13
+ check_numeric! arg
14
+ self.distance *= arg
15
+ self
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,70 @@
1
+ require 'spec_helper'
2
+
3
+ GeoPoint.coord_mode = :lng_lat
4
+
5
+ describe "GeoDistance class methods" do
6
+ describe '#earth_radius' do
7
+ it 'should return in kms' do
8
+ GeoDistance.earth_radius(:kms).should be_between(6371, 6380)
9
+ end
10
+
11
+ it 'should return in meters' do
12
+ GeoDistance.earth_radius(:meters).should be_between(6371000, 6380000)
13
+ end
14
+ end
15
+
16
+ describe '#radians_ratio unit' do
17
+ it 'should return in kms' do
18
+ puts GeoDistance.radians_ratio unit(:kms) #.should be_between(6371, 6380)
19
+ end
20
+
21
+ it 'should return in meters' do
22
+ puts GeoDistance.radians_ratio unit(:meters) #.should be_between(6371000, 6380000)
23
+ end
24
+ end
25
+
26
+ describe '#default_algorithm' do
27
+ let(:from) { [-104.88544, 39.06546].geo_point }
28
+ let(:to) { [-104.80, 39.06546].geo_point }
29
+
30
+ it "should NOT set it to :haver" do
31
+ lambda { GeoDistance.default_algorithm = :haver}.should raise_error
32
+ end
33
+
34
+ it "should set it to :haversine" do
35
+ GeoDistance.default_algorithm = :haversine
36
+
37
+ dist = GeoDistance.geo_distance(from, to)
38
+
39
+ puts "the distance from #{from} to #{to} is: #{dist.meters} meters"
40
+
41
+ dist.feet.should == 24193.0
42
+ dist.to_feet.should == 24193.0
43
+ dist.kms.to_s.should match(/7\.376*/)
44
+ end
45
+
46
+ it "should set it to :flat" do
47
+ GeoDistance.default_algorithm = :flat
48
+
49
+ dist = GeoDistance.geo_distance(from, to)
50
+ puts "the distance from #{from} to #{to} is: #{dist.meters} meters"
51
+ dist.feet.should be_between(23500, 25000)
52
+ end
53
+
54
+ it "should set it to :sphere" do
55
+ GeoDistance.default_algorithm = :sphere
56
+
57
+ dist = GeoDistance.geo_distance(from, to)
58
+ puts "the distance from #{from} to #{to} is: #{dist.meters} meters"
59
+ dist.feet.should be_between(23500, 25000)
60
+ end
61
+
62
+ it "should set it to :vincenty" do
63
+ GeoDistance.default_algorithm = :vincenty
64
+
65
+ dist = GeoDistance.geo_distance(from, to)
66
+ puts "the distance from #{from} to #{to} is: #{dist.meters} meters"
67
+ dist.feet.should be_between(23500, 25000)
68
+ end
69
+ end
70
+ end