geo-distance 0.1.2 → 0.2.0

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.
Files changed (36) hide show
  1. data/Gemfile +7 -9
  2. data/Gemfile.lock +30 -13
  3. data/README.textile +28 -8
  4. data/VERSION +1 -1
  5. data/geo-distance.gemspec +40 -32
  6. data/lib/geo-distance.rb +14 -132
  7. data/lib/geo-distance/class_methods.rb +71 -0
  8. data/lib/geo-distance/conversion.rb +67 -0
  9. data/lib/geo-distance/conversion/meters.rb +29 -0
  10. data/lib/geo-distance/conversion/radians.rb +36 -0
  11. data/lib/geo-distance/core_ext.rb +12 -23
  12. data/lib/geo-distance/distance.rb +24 -0
  13. data/lib/geo-distance/formula.rb +53 -7
  14. data/lib/geo-distance/formula/flat.rb +21 -0
  15. data/lib/geo-distance/{haversine.rb → formula/haversine.rb} +18 -14
  16. data/lib/geo-distance/formula/n_vector.rb +30 -0
  17. data/lib/geo-distance/formula/spherical.rb +44 -0
  18. data/lib/geo-distance/formula/vincenty.rb +80 -0
  19. data/lib/geo-distance/scale.rb +18 -0
  20. data/spec/geo_distance/class_methods_spec.rb +70 -0
  21. data/spec/geo_distance/core_ext_spec.rb +65 -0
  22. data/spec/geo_distance/distance_spec.rb +74 -0
  23. data/spec/geo_distance/formula/flat_spec.rb +31 -0
  24. data/spec/geo_distance/formula/haversine_spec.rb +33 -0
  25. data/spec/geo_distance/formula/n_vector.rb +33 -0
  26. data/spec/geo_distance/formula/spherical_spec.rb +33 -0
  27. data/spec/geo_distance/formula/vincenty_spec.rb +33 -0
  28. data/spec/spec_helper.rb +0 -10
  29. metadata +97 -96
  30. data/lib/geo-distance/spherical.rb +0 -24
  31. data/lib/geo-distance/vincenty.rb +0 -79
  32. data/spec/core_ext_spec.rb +0 -9
  33. data/spec/dist_default_spec.rb +0 -23
  34. data/spec/dist_haversine_spec.rb +0 -21
  35. data/spec/dist_spherical_spec.rb +0 -21
  36. data/spec/dist_vincenty_spec.rb +0 -21
@@ -0,0 +1,67 @@
1
+ class GeoDistance
2
+ module Conversion
3
+ autoload :Meters, 'geo-distance/conversion/meters'
4
+ autoload :Radians, 'geo-distance/conversion/radians'
5
+
6
+ def self.included(base)
7
+ base.send :include, Meters
8
+ base.send :include, Radians
9
+ end
10
+
11
+ # return new GeoDistance instance with distance converted to specific unit
12
+ ::GeoDistance.units.each do |unit|
13
+ class_eval %{
14
+ def to_#{unit}
15
+ cloned = self.dup
16
+ un = GeoUnits.key :#{unit}
17
+ cloned.distance = in_meters * GeoUnits.meters_map[un]
18
+ cloned.unit = un
19
+ cloned
20
+ end
21
+ }
22
+ end
23
+
24
+ # in_ and as_ return distance as a Float
25
+ # to_xxx! and similar, modify distance (self) directly
26
+ (::GeoDistance.units - [:meters]).each do |unit|
27
+ class_eval %{
28
+ def in_#{unit}
29
+ dist = (unit == :radians) ? in_radians : distance
30
+ conv_unit = GeoUnits.key :#{unit}
31
+ convert_to_meters(dist) * GeoUnits.meters_map[conv_unit]
32
+ end
33
+ alias_method :as_#{unit}, :in_#{unit}
34
+
35
+ def to_#{unit}!
36
+ conv_unit = GeoUnits.key :#{unit}
37
+ self.distance = in_meters * GeoUnits.meters_map[conv_unit]
38
+ self.unit = conv_unit
39
+ self
40
+ end
41
+ alias_method :in_#{unit}!, :to_#{unit}!
42
+ alias_method :as_#{unit}!, :to_#{unit}!
43
+ }
44
+ end
45
+
46
+ ::GeoDistance.all_units.each do |unit|
47
+ class_eval %{
48
+ def #{unit}
49
+ un = GeoUnits.key :#{unit}
50
+ send(:"as_\#{un}")
51
+ end
52
+ }
53
+ end
54
+
55
+ protected
56
+
57
+ # distance delta
58
+ GeoDistance.units.each do |unit|
59
+ class_eval %{
60
+ def delta_#{unit}
61
+ unit = GeoUnits.key(:#{unit})
62
+ GeoDistance.earth_radius[:#{unit}] * distance
63
+ end
64
+ }
65
+ end
66
+ end
67
+ end
@@ -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
@@ -1,31 +1,20 @@
1
- class Float
2
- def round_to(x)
3
- (self * 10**x).round.to_f / 10**x
4
- end
5
-
6
- def ceil_to(x)
7
- (self * 10**x).ceil.to_f / 10**x
8
- end
9
-
10
- def floor_to(x)
11
- (self * 10**x).floor.to_f / 10**x
12
- end
13
-
14
- def rpd
15
- self * GeoDistance.radians_per_degree
16
- end
17
- alias_method :to_radians, :rpd
18
- end
1
+ require 'sugar-high/numeric'
19
2
 
20
- require 'geo-distance'
21
-
22
- class Integer
23
- ::GeoDistance.units.each do |unit|
3
+ module GeoDistanceExt
4
+ ::GeoDistance.all_units.each do |unit|
24
5
  class_eval %{
25
6
  def #{unit}
26
- GeoDistance::Distance.new(self, :#{unit})
7
+ GeoDistance.new(self, :#{unit})
27
8
  end
28
9
  }
29
10
  end
30
11
  end
31
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
@@ -1,16 +1,62 @@
1
- module GeoDistance
2
- EARTH_RADIUS = { :kilometers => 6378.135, :miles => 3963.1676 }
3
- # WGS-84 numbers
4
- EARTH_MAJOR_AXIS_RADIUS = { :kilometers => 6378.137, :miles => 3963.19059 }
5
- EARTH_MINOR_AXIS_RADIUS = { :kilometers => 6356.7523142, :miles => 3949.90276 }
6
-
1
+ class GeoDistance
2
+ include GeoUnits
3
+
7
4
  class DistanceFormula
8
- include Math
9
5
  extend Math
6
+ extend GeoUnits
10
7
 
11
8
  def initialize
12
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)
13
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
14
60
  end
15
61
  end
16
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
+
@@ -33,24 +33,28 @@
33
33
  require 'geo-distance/core_ext'
34
34
  require 'geo-distance/formula'
35
35
 
36
- module GeoDistance
36
+ class GeoDistance
37
37
  class Haversine < DistanceFormula
38
38
  # given two lat/lon points, compute the distance between the two points using the haversine formula
39
39
  # the result will be a Hash of distances which are key'd by 'mi','km','ft', and 'm'
40
-
41
- def self.distance( lat1, lon1, lat2, lon2)
42
- dlon = lon2 - lon1
43
- dlat = lat2 - lat1
44
-
45
- a = calc(dlat, lat1, lat2, dlon)
46
- c = 2 * Math.atan2( Math.sqrt(a), Math.sqrt(1-a))
47
-
48
- GeoDistance::Distance.new c
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
49
57
  end
50
-
51
- def self.calc dlat, lat1, lat2, dlon
52
- (Math.sin(dlat.rpd/2))**2 + Math.cos(lat1.rpd) * Math.cos((lat2.rpd)) * (Math.sin(dlon.rpd/2))**2
53
- end
54
58
  end
55
59
 
56
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