vincenty 1.0.8 → 1.0.9
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.
- checksums.yaml +5 -5
- data/History.txt +16 -0
- data/README.md +3 -3
- data/Rakefile +20 -15
- data/lib/angle.rb +127 -134
- data/lib/coordinate.rb +8 -9
- data/lib/core_extensions.rb +47 -49
- data/lib/latitude.rb +20 -22
- data/lib/longitude.rb +11 -15
- data/lib/track_and_distance.rb +31 -32
- data/lib/vincenty.rb +110 -112
- data/test/ts_all.rb +1 -1
- data/test/ts_angle.rb +46 -45
- data/test/ts_coordinate.rb +4 -3
- data/test/ts_latitude.rb +8 -5
- data/test/ts_longitude.rb +8 -5
- data/test/ts_track_and_distance.rb +9 -9
- data/test/ts_vincenty.rb +53 -53
- metadata +12 -13
data/lib/core_extensions.rb
CHANGED
@@ -1,99 +1,97 @@
|
|
1
1
|
require 'scanf'
|
2
2
|
|
3
|
-
#Extends Numeric, hence Fixed & Float to_r & to_d
|
4
|
-
#Also adds in sign.
|
3
|
+
# Extends Numeric, hence Fixed & Float to_r & to_d
|
4
|
+
# Also adds in sign.
|
5
5
|
class Numeric
|
6
|
-
#Convert Radians to Degrees
|
6
|
+
# Convert Radians to Degrees
|
7
7
|
# @return [Numeric] degrees
|
8
8
|
# @param [true,false] mod Optional argument mod == true, then applies % 360
|
9
|
-
def to_degrees(mod=false)
|
10
|
-
if mod
|
11
|
-
(self * 180
|
12
|
-
else
|
13
|
-
self * 180
|
9
|
+
def to_degrees(mod = false)
|
10
|
+
if mod
|
11
|
+
(self * 180 / Math::PI) % 360
|
12
|
+
else
|
13
|
+
self * 180 / Math::PI
|
14
14
|
end
|
15
15
|
end
|
16
|
-
|
17
|
-
#Converts degrees to Radians
|
16
|
+
|
17
|
+
# Converts degrees to Radians
|
18
18
|
# @return [Numeric] radians
|
19
19
|
# @param [true,false] mod Optional argument mod == true, then applies % Math::PI
|
20
|
-
def to_radians(mod=false)
|
21
|
-
if mod
|
20
|
+
def to_radians(mod = false)
|
21
|
+
if mod
|
22
22
|
(self * Math::PI / 180) % Math::PI
|
23
|
-
else
|
23
|
+
else
|
24
24
|
self * Math::PI / 180
|
25
25
|
end
|
26
26
|
end
|
27
|
-
|
27
|
+
|
28
28
|
alias to_r to_radians
|
29
29
|
alias to_rad to_radians
|
30
30
|
alias to_deg to_degrees
|
31
|
-
|
31
|
+
|
32
32
|
# @return [Fixnum] 1 if number is positive, -1 if negative.
|
33
33
|
def sign
|
34
34
|
self < 0 ? -1 : 1
|
35
35
|
end
|
36
|
-
|
37
36
|
end
|
38
37
|
|
39
|
-
#Alters round method to have an optional number of decimal places.
|
38
|
+
# Alters round method to have an optional number of decimal places.
|
40
39
|
class Float
|
41
|
-
|
42
|
-
#Replace default round, so we can reference it later.
|
40
|
+
# Replace default round, so we can reference it later.
|
43
41
|
# @return [Float]
|
44
42
|
alias round0 round
|
45
|
-
|
46
|
-
#Compatible Replacement for Float.round
|
43
|
+
|
44
|
+
# Compatible Replacement for Float.round
|
47
45
|
# @return [Float] float rounded to n decimal places.
|
48
46
|
# @param [Numeric] n Optional argument n is the number of decimal places to round to.
|
49
47
|
def round(n = 0)
|
50
48
|
if n == 0
|
51
|
-
self.round0 #This is to preserve the default behaviour, which is to return a Fixnum, not a float.
|
49
|
+
self.round0 # This is to preserve the default behaviour, which is to return a Fixnum, not a float.
|
52
50
|
else
|
53
51
|
m = 10.0**n
|
54
|
-
|
55
|
-
|
52
|
+
(self * m).round0 / m
|
53
|
+
end
|
56
54
|
end
|
57
55
|
end
|
58
56
|
|
59
|
-
#Extends String to to_dec_degrees, add to_r and to_d
|
57
|
+
# Extends String to to_dec_degrees, add to_r and to_d
|
60
58
|
class String
|
61
|
-
#string expected to be degrees, returns decimal degrees.
|
62
|
-
#common forms are S37 001'7.5'', 37 001'7.5''S , -37 001'7.5'', -37 0 1.512'. -37.01875 0, 37 001'.512S, S37 001'.512, ...
|
59
|
+
# string expected to be degrees, returns decimal degrees.
|
60
|
+
# common forms are S37 001'7.5'', 37 001'7.5''S , -37 001'7.5'', -37 0 1.512'. -37.01875 0, 37 001'.512S, S37 001'.512, ...
|
63
61
|
# @return [Float] angle in decimal degrees
|
64
|
-
def to_dec_degrees
|
65
|
-
#reorder 37 001'.512S, S37 001'.512 into 37 001.512'S, S37 001.512' respectively
|
66
|
-
s = self.gsub(/([0-9])(
|
67
|
-
#add in minutes and seconds to get 3 values 'deg 0 0'from S37 0, 37 0S
|
68
|
-
s.gsub!(/^([^0-9
|
69
|
-
#add in seconds get 3 values 'deg min 0' from S37 01.512', 37 01.512'S
|
70
|
-
s.gsub!(/^([^0-9
|
71
|
-
|
72
|
-
#look for anything of the form S37 001'7.5'', S37 01.512', S37.01875 0, ...
|
73
|
-
s.scanf(
|
62
|
+
def to_dec_degrees
|
63
|
+
# reorder 37 001'.512S, S37 001'.512 into 37 001.512'S, S37 001.512' respectively
|
64
|
+
s = self.gsub(/([0-9])(')\.([0-9]+)/, '\1.\3\2')
|
65
|
+
# add in minutes and seconds to get 3 values 'deg 0 0'from S37 0, 37 0S
|
66
|
+
s.gsub!(/^([^0-9.\-]*)([0-9\-.]+)([^0-9\-.]*)$/, '\1\2\3 0 0\5')
|
67
|
+
# add in seconds get 3 values 'deg min 0' from S37 01.512', 37 01.512'S
|
68
|
+
s.gsub!(/^([^0-9.\-]*)([0-9\-.]+)([^0-9\-.]+)([0-9\-.]+)([^0-9\-.]*)$/, '\1\2\3\4 0\5')
|
69
|
+
|
70
|
+
# look for anything of the form S37 001'7.5'', S37 01.512', S37.01875 0, ...
|
71
|
+
s.scanf('%[NSEW]%f%[^0-9-]%f%[^0-9-]%f') do |direction, deg, _sep1, min, _sep2, sec|
|
74
72
|
return Angle.decimal_deg( deg, min, sec, direction)
|
75
73
|
end
|
76
|
-
|
77
|
-
#look for anything of the form 37 001'7.5''S , -37 001'7.5'', -37 0 1.512'. -37.01875 0, ...
|
78
|
-
s.scanf(
|
74
|
+
|
75
|
+
# look for anything of the form 37 001'7.5''S , -37 001'7.5'', -37 0 1.512'. -37.01875 0, ...
|
76
|
+
s.scanf('%f%[^0-9-]%f%[^0-9-]%f%[^NSEW]%[NSEW]') do |deg, _sep1, min, _sep2, sec, _sep3, direction|
|
79
77
|
return Angle.decimal_deg( deg, min, sec, direction)
|
80
78
|
end
|
81
79
|
end
|
82
|
-
|
83
|
-
#Convert String number in Radians to Degrees
|
80
|
+
|
81
|
+
# Convert String number in Radians to Degrees
|
84
82
|
# @return [Float] degrees
|
85
83
|
# @param [true,false] mod Optional argument mod == true, then applies % 360
|
86
|
-
def to_degrees(mod=false)
|
87
|
-
self.to_f.to_degrees(mod)
|
84
|
+
def to_degrees(mod = false)
|
85
|
+
return self.to_f.to_degrees(mod)
|
88
86
|
end
|
89
|
-
|
90
|
-
#Converts string degrees to to_decimal_degrees, then to Radians
|
87
|
+
|
88
|
+
# Converts string degrees to to_decimal_degrees, then to Radians
|
91
89
|
# @return [Float] radians
|
92
90
|
# @param [true,false] mod Optional argument mod == true, then applies % Math::PI
|
93
|
-
def to_radians(mod=false)
|
94
|
-
self.to_dec_degrees.to_radians(mod)
|
91
|
+
def to_radians(mod = false)
|
92
|
+
return self.to_dec_degrees.to_radians(mod)
|
95
93
|
end
|
96
|
-
|
94
|
+
|
97
95
|
alias to_rad to_radians
|
98
96
|
alias to_r to_radians
|
99
97
|
alias to_deg to_degrees
|
data/lib/latitude.rb
CHANGED
@@ -1,44 +1,42 @@
|
|
1
|
-
|
2
1
|
require_relative 'angle.rb'
|
3
2
|
|
4
|
-
#Subclass of Angle to add in special treatment of to_d, to_r , to_s
|
5
|
-
#Latitude degrees are between -PI and PI, South to North (+/- 90 degrees)
|
6
|
-
|
3
|
+
# Subclass of Angle to add in special treatment of to_d, to_r , to_s
|
4
|
+
# Latitude degrees are between -PI and PI, South to North (+/- 90 degrees)
|
7
5
|
class Latitude < Angle
|
8
|
-
|
9
6
|
# @return [Float] angle as degrees in range -90 and 90
|
10
7
|
def to_degrees
|
11
8
|
degrees = super
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
9
|
+
if degrees > 270
|
10
|
+
-(360 - degrees)
|
11
|
+
elsif degrees > 180 || degrees > 90 || degrees < -90
|
12
|
+
180 - degrees
|
13
|
+
else
|
14
|
+
degrees
|
18
15
|
end
|
19
16
|
end
|
20
17
|
|
21
18
|
# @return [Float] angle as degrees in range -PI and PI
|
22
19
|
def to_radians
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
20
|
+
if @angle > 3 * Math::PI / 2
|
21
|
+
@angle - Math::PI * 2
|
22
|
+
elsif @angle > Math::PI || @angle > Math::PI / 2
|
23
|
+
Math::PI - @angle
|
24
|
+
elsif @angle < -Math::PI / 2
|
25
|
+
-Math::PI - @angle
|
26
|
+
else
|
27
|
+
@angle
|
29
28
|
end
|
30
29
|
end
|
31
30
|
|
32
31
|
# @return [String] angle as string in degrees minutes seconds direction.
|
33
|
-
#A South angle is negative, North is Positive.
|
32
|
+
# A South angle is negative, North is Positive.
|
34
33
|
# @param [String] fmt Optional format string passed to Angle#to_s
|
35
|
-
def to_s(fmt="%2d %2m'%2.4s\"%N")
|
36
|
-
|
34
|
+
def to_s(fmt = "%2d %2m'%2.4s\"%N")
|
35
|
+
super(fmt)
|
37
36
|
end
|
38
37
|
|
39
38
|
alias to_r to_radians
|
40
39
|
alias to_rad to_radians
|
41
|
-
#alias to_d to_degrees
|
40
|
+
# alias to_d to_degrees
|
42
41
|
alias to_deg to_degrees
|
43
|
-
|
44
42
|
end
|
data/lib/longitude.rb
CHANGED
@@ -1,38 +1,34 @@
|
|
1
|
-
|
2
1
|
require_relative 'angle.rb'
|
3
2
|
|
4
|
-
#Subclass of Angle to add in special treatment of to_d, to_r and to_s
|
5
|
-
#Longitude degrees are between -2PI and 2PI, West to East (+/- 180 degrees)
|
6
|
-
|
3
|
+
# Subclass of Angle to add in special treatment of to_d, to_r and to_s
|
4
|
+
# Longitude degrees are between -2PI and 2PI, West to East (+/- 180 degrees)
|
7
5
|
class Longitude < Angle
|
8
|
-
|
9
6
|
# @return [Float] angle as degrees in range -180 and 180
|
10
7
|
def to_degrees
|
11
8
|
degrees = super
|
12
|
-
|
13
|
-
|
14
|
-
|
9
|
+
if degrees > 180 then degrees - 360
|
10
|
+
else
|
11
|
+
degrees
|
15
12
|
end
|
16
13
|
end
|
17
14
|
|
18
15
|
# @return [Float] angle as degrees in range -2PI and 2PI
|
19
16
|
def to_radians
|
20
|
-
|
21
|
-
|
22
|
-
|
17
|
+
if @angle > Math::PI then @angle - 2 * Math::PI
|
18
|
+
else
|
19
|
+
@angle
|
23
20
|
end
|
24
21
|
end
|
25
22
|
|
26
23
|
# @return [String] angle as string in degrees minutes seconds direction.
|
27
|
-
#A West angle is negative, East is Positive.
|
24
|
+
# A West angle is negative, East is Positive.
|
28
25
|
# @param [String] fmt Optional format string passed to Angle#to_s
|
29
|
-
def to_s(fmt="%3d %2m'%2.4s\"%E")
|
26
|
+
def to_s(fmt = "%3d %2m'%2.4s\"%E")
|
30
27
|
super(fmt)
|
31
28
|
end
|
32
29
|
|
33
30
|
alias to_r to_radians
|
34
31
|
alias to_rad to_radians
|
35
|
-
#alias to_d to_degrees
|
32
|
+
# alias to_d to_degrees
|
36
33
|
alias to_deg to_degrees
|
37
|
-
|
38
34
|
end
|
data/lib/track_and_distance.rb
CHANGED
@@ -1,41 +1,40 @@
|
|
1
|
-
|
2
1
|
require_relative 'angle.rb'
|
3
2
|
|
4
|
-
#Holds a bearing and distance
|
3
|
+
# Holds a bearing and distance
|
5
4
|
class TrackAndDistance
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
5
|
+
# @return [Angle]
|
6
|
+
attr_accessor :bearing
|
7
|
+
# @return [Float]
|
8
|
+
attr_accessor :distance
|
10
9
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
10
|
+
# @param [String, Numeric, #to_radian, #to_f] Bearing can be a String or Numeric or any object with to_radians and to_f
|
11
|
+
# @param [Numeric] distance
|
12
|
+
# @param [true,false, :radians] radians Bearing is in degrees unless radians == true (or set to :radians).
|
13
|
+
def initialize(bearing, distance, radians = false)
|
14
|
+
@bearing = Angle.new(bearing, radians)
|
15
|
+
@distance = distance
|
16
|
+
end
|
18
17
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
end
|
18
|
+
# format string fmt is currently just for the bearing angle.
|
19
|
+
# Need to change this to include the distance is single format string.
|
20
|
+
# @return [String] Bearing angle and distance in meters.
|
21
|
+
# @param [String] fmt Optional format string passed to Coordinate#strf
|
22
|
+
def to_s(fmt = nil)
|
23
|
+
if fmt
|
24
|
+
# needs work to include distance as well as angle fmt.
|
25
|
+
return "#{@bearing.strf(fmt)} #{distance.round(4)}m"
|
26
|
+
else
|
27
|
+
return "#{@bearing.strf} #{distance.round(4)}m"
|
30
28
|
end
|
29
|
+
end
|
31
30
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
31
|
+
# @return [Array] with members bearing and distance.
|
32
|
+
def to_ary
|
33
|
+
return [ @bearing, @distance ]
|
34
|
+
end
|
36
35
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
36
|
+
# @return [Hash] with keys :bearing and :distance
|
37
|
+
def to_hash
|
38
|
+
return { bearing: @bearing, distance: @distance }
|
39
|
+
end
|
41
40
|
end
|
data/lib/vincenty.rb
CHANGED
@@ -5,199 +5,197 @@ require_relative 'longitude.rb'
|
|
5
5
|
require_relative 'track_and_distance.rb'
|
6
6
|
require_relative 'coordinate.rb'
|
7
7
|
|
8
|
-
#Vincenty's algorithms for finding the bearing and distance between two coordinates and
|
9
|
-
#for finding the latitude and longitude, given a start coordinate, distance and bearing.
|
8
|
+
# Vincenty's algorithms for finding the bearing and distance between two coordinates and
|
9
|
+
# for finding the latitude and longitude, given a start coordinate, distance and bearing.
|
10
10
|
#
|
11
11
|
# Coded from formulae from Wikipedia http://en.wikipedia.org/wiki/Vincenty%27s_formulae
|
12
12
|
# Modified to incorporate corrections to formulae as found in script on http://www.movable-type.co.uk/scripts/LatLongVincenty.html
|
13
13
|
# Added my Modification of the distanceAndAngle formulae to correct the compass bearing.
|
14
14
|
class Vincenty < Coordinate
|
15
|
-
VERSION = '1.0.
|
15
|
+
VERSION = '1.0.9'
|
16
16
|
|
17
17
|
# @return [String] constant VERSION
|
18
18
|
def version
|
19
19
|
VERSION
|
20
20
|
end
|
21
21
|
|
22
|
-
WGS84_ER = 6378137 #Equatorial Radius of earth
|
23
|
-
WGS84_IF = 298.257223563 #Inverse Flattening
|
24
|
-
GRS80_ER = 6378137 #Equatorial Radius of earth
|
25
|
-
GRS80_IF = 298.25722210882711 #Inverse Flattening
|
22
|
+
WGS84_ER = 6378137 # Equatorial Radius of earth
|
23
|
+
WGS84_IF = 298.257223563 # Inverse Flattening
|
24
|
+
GRS80_ER = 6378137 # Equatorial Radius of earth
|
25
|
+
GRS80_IF = 298.25722210882711 # Inverse Flattening
|
26
26
|
|
27
|
-
#Great Circle formulae http://en.wikipedia.org/wiki/Great-circle_distance
|
28
|
-
#Reference calculation for testing, assumes the earth is a sphere, which it isn't.
|
29
|
-
#This gives us an approximation to verify Vincenty algorithm.
|
27
|
+
# Great Circle formulae http://en.wikipedia.org/wiki/Great-circle_distance
|
28
|
+
# Reference calculation for testing, assumes the earth is a sphere, which it isn't.
|
29
|
+
# This gives us an approximation to verify Vincenty algorithm.
|
30
30
|
# @param [Coordinate] p2 is target coordinate that we want the bearing to.
|
31
31
|
# @return [TrackAndDistance] with the compass bearing and distance in meters to P2
|
32
32
|
def sphericalDistanceAndAngle( p2, equatorial_radius = WGS84_ER, inverse_flattening = WGS84_IF )
|
33
33
|
if self.latitude == p2.latitude && self.longitude == p2.longitude
|
34
|
-
return TrackAndDistance.new(0, 0, true) #No calculations necessary
|
34
|
+
return TrackAndDistance.new(0, 0, true) # No calculations necessary
|
35
35
|
end
|
36
36
|
|
37
|
-
a = equatorial_radius #equatorial radius in meters (+/-2 m)
|
38
|
-
f = inverse_flattening
|
39
|
-
b = a - a/f #WGS84 = 6356752.314245179 polar radius in meters
|
40
|
-
r = (a+b)/2 #average diametre as a rough estimate for our tests.
|
37
|
+
a = equatorial_radius # equatorial radius in meters (+/-2 m)
|
38
|
+
f = inverse_flattening
|
39
|
+
b = a - a / f # WGS84 = 6356752.314245179 polar radius in meters
|
40
|
+
r = (a + b) / 2 # average diametre as a rough estimate for our tests.
|
41
41
|
|
42
42
|
sin_lat1 = Math.sin(@latitude.to_rad)
|
43
43
|
sin_lat2 = Math.sin(p2.latitude.to_rad)
|
44
44
|
cos_lat1 = Math.cos(@latitude.to_rad)
|
45
45
|
atan1_2 = Math.atan(1) * 2
|
46
|
-
t1 = cos_lat1 * Math.cos(p2.latitude.to_rad) *
|
47
|
-
angular_distance = Math.atan(-t1/Math.sqrt(-t1 * t1 +1)) + atan1_2 #central angle in radians so we can calculate the arc length.
|
46
|
+
t1 = cos_lat1 * Math.cos(p2.latitude.to_rad) * Math.cos(@longitude.to_rad - p2.longitude.to_rad) + sin_lat1 * sin_lat2
|
47
|
+
angular_distance = Math.atan(-t1 / Math.sqrt(-t1 * t1 + 1)) + atan1_2 # central angle in radians so we can calculate the arc length.
|
48
48
|
|
49
49
|
t2 = (sin_lat2 - sin_lat1 * Math.cos(angular_distance)) / (cos_lat1 * Math.sin(angular_distance))
|
50
|
-
if
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
50
|
+
bearing = if Math.sin(p2.longitude.to_rad - @longitude.to_rad) < 0
|
51
|
+
2 * Math::PI - (Math.atan(-t2 / Math.sqrt(-t2 * t2 + 1)) + atan1_2) # Compass Bearing in radians (clockwise)
|
52
|
+
else
|
53
|
+
Math.atan(-t2 / Math.sqrt(-t2 * t2 + 1)) + atan1_2 # Compass Bearing in radians (clockwise)
|
54
|
+
end
|
55
55
|
|
56
|
-
#Note that the bearing is a compass angle. That is angles are positive clockwise.
|
56
|
+
# Note that the bearing is a compass angle. That is angles are positive clockwise.
|
57
57
|
return TrackAndDistance.new(bearing, angular_distance * r, true)
|
58
58
|
end
|
59
59
|
|
60
|
-
#Vincenty's algorithm for finding bearing and distance between to coordinates.
|
61
|
-
#Assumes earth is a WGS-84 Ellipsod.
|
60
|
+
# Vincenty's algorithm for finding bearing and distance between to coordinates.
|
61
|
+
# Assumes earth is a WGS-84 Ellipsod.
|
62
62
|
# @param [Coordinate] p2 is target coordinate that we want the bearing to.
|
63
63
|
# @return [TrackAndDistance] with the compass bearing and distance in meters to P2
|
64
64
|
def distanceAndAngle( p2 )
|
65
65
|
if self.latitude == p2.latitude && self.longitude == p2.longitude
|
66
|
-
return TrackAndDistance.new(0, 0, true) #No calculations necessary. Solv NAN issue
|
66
|
+
return TrackAndDistance.new(0, 0, true) # No calculations necessary. Solv NAN issue
|
67
67
|
end
|
68
68
|
|
69
69
|
# a, b = major & minor semiaxes of the ellipsoid
|
70
|
-
a = 6378137 #equatorial radius in meters (+/-2 m)
|
71
|
-
b = 6356752.31424518 #polar radius in meters
|
72
|
-
f = (a-b)/a # flattening
|
70
|
+
a = 6378137 # equatorial radius in meters (+/-2 m)
|
71
|
+
b = 6356752.31424518 # polar radius in meters
|
72
|
+
f = (a - b) / a # flattening
|
73
73
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
74
|
+
lat1 = @latitude.to_rad
|
75
|
+
lon1 = @longitude.to_rad
|
76
|
+
lat2 = p2.latitude.to_rad
|
77
|
+
lon2 = p2.longitude.to_rad
|
78
|
+
lat1 = lat1.sign * (Math::PI / 2 - 1e-10) if (Math::PI / 2 - lat1.abs).abs < 1.0e-10
|
79
|
+
lat2 = lat2.sign * (Math::PI / 2 - 1e-10) if (Math::PI / 2 - lat2.abs).abs < 1.0e-10
|
80
80
|
|
81
81
|
# lat1, lat2 = geodetic latitude
|
82
82
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
sin_lambda_v = Math.sin(lambda_v)
|
96
|
-
cos_lambda_v = Math.cos(lambda_v)
|
97
|
-
sin_sigma = Math.sqrt( ( cos_u2 * sin_lambda_v ) ** 2 + ( cos_u1 * sin_u2 - sin_u1 * cos_u2 * cos_lambda_v ) ** 2 )
|
98
|
-
cos_sigma = sin_u1 * sin_u2 + cos_u1 * cos_u2 * cos_lambda_v
|
99
|
-
sigma = Math.atan2(sin_sigma, cos_sigma)
|
100
|
-
sin_alpha= cos_u1 * cos_u2 * sin_lambda_v / sin_sigma
|
101
|
-
cos_2_alpha = 1 - sin_alpha * sin_alpha #trig identity
|
102
|
-
cos_2_sigma_m = cos_sigma - 2 * sin_u1 * sin_u2/cos_2_alpha
|
103
|
-
c = f / 16 * cos_2_alpha * (4 + f*(4-3*cos_2_alpha))
|
104
|
-
lambda_dash = lambda_v
|
105
|
-
lambda_v = l + (1-c) * f * sin_alpha * (sigma + c * sin_sigma * (cos_2_sigma_m + c * cos_sigma * (-1 + 2 * cos_2_sigma_m * cos_2_sigma_m) ) ) # use cos_2_sigma_m=0 when over equatorial lines
|
106
|
-
if lambda_v > Math::PI
|
107
|
-
lambda_v = Math::PI
|
108
|
-
break
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
|
-
u_2 = cos_2_alpha * (a * a - b * b) / (b * b)
|
113
|
-
a1 = 1 + u_2 / 16384 * (4096 + u_2 * (-768 + u_2 * (320 - 175 * u_2)))
|
114
|
-
b1 = u_2 / 1024 * (256 + u_2 * (-128 + u_2 * (74 - 47 * u_2)))
|
115
|
-
delta_sigma = b1 * sin_sigma * (cos_2_sigma_m + b1 / 4 * (cos_sigma * (-1 + 2 * cos_2_sigma_m * cos_2_sigma_m) - b1 / 6 * cos_2_sigma_m * (-3 + 4 * sin_sigma * sin_sigma) * (-3 + 4 * cos_2_sigma_m * cos_2_sigma_m)))
|
116
|
-
s = b * a1 * (sigma - delta_sigma)
|
83
|
+
l = (lon2 - lon1).abs # difference in longitude
|
84
|
+
l = 2 * Math::PI - l if l > Math::PI
|
85
|
+
u1 = Math.atan( ( 1 - f) * Math.tan( lat1 ) ) # U is 'reduced latitude'
|
86
|
+
u2 = Math.atan( ( 1 - f) * Math.tan( lat2 ) )
|
87
|
+
sin_u1 = Math.sin(u1)
|
88
|
+
cos_u1 = Math.cos(u1)
|
89
|
+
sin_u2 = Math.sin(u2)
|
90
|
+
cos_u2 = Math.cos(u2)
|
91
|
+
|
92
|
+
lambda_v = l
|
93
|
+
lambda_dash = Math::PI * 2
|
94
|
+
while (lambda_v - lambda_dash).abs > 1.0e-12 # i.e. 0.06 mm error
|
117
95
|
sin_lambda_v = Math.sin(lambda_v)
|
118
96
|
cos_lambda_v = Math.cos(lambda_v)
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
97
|
+
sin_sigma = Math.sqrt( ( cos_u2 * sin_lambda_v )**2 + ( cos_u1 * sin_u2 - sin_u1 * cos_u2 * cos_lambda_v )**2 )
|
98
|
+
cos_sigma = sin_u1 * sin_u2 + cos_u1 * cos_u2 * cos_lambda_v
|
99
|
+
sigma = Math.atan2(sin_sigma, cos_sigma)
|
100
|
+
sin_alpha = cos_u1 * cos_u2 * sin_lambda_v / sin_sigma
|
101
|
+
cos_2_alpha = 1 - sin_alpha * sin_alpha # trig identity
|
102
|
+
cos_2_sigma_m = cos_sigma - 2 * sin_u1 * sin_u2 / cos_2_alpha
|
103
|
+
c = f / 16 * cos_2_alpha * (4 + f * (4 - 3 * cos_2_alpha))
|
104
|
+
lambda_dash = lambda_v
|
105
|
+
lambda_v = l + (1 - c) * f * sin_alpha * (sigma + c * sin_sigma * (cos_2_sigma_m + c * cos_sigma * (-1 + 2 * cos_2_sigma_m * cos_2_sigma_m) ) ) # use cos_2_sigma_m=0 when over equatorial lines
|
106
|
+
if lambda_v > Math::PI
|
107
|
+
lambda_v = Math::PI
|
108
|
+
break
|
128
109
|
end
|
110
|
+
end
|
129
111
|
|
130
|
-
|
131
|
-
|
112
|
+
u_2 = cos_2_alpha * (a * a - b * b) / (b * b)
|
113
|
+
a1 = 1 + u_2 / 16384 * (4096 + u_2 * (-768 + u_2 * (320 - 175 * u_2)))
|
114
|
+
b1 = u_2 / 1024 * (256 + u_2 * (-128 + u_2 * (74 - 47 * u_2)))
|
115
|
+
delta_sigma = b1 * sin_sigma * (cos_2_sigma_m + b1 / 4 * (cos_sigma * (-1 + 2 * cos_2_sigma_m * cos_2_sigma_m) - b1 / 6 * cos_2_sigma_m * (-3 + 4 * sin_sigma * sin_sigma) * (-3 + 4 * cos_2_sigma_m * cos_2_sigma_m)))
|
116
|
+
s = b * a1 * (sigma - delta_sigma)
|
117
|
+
sin_lambda_v = Math.sin(lambda_v)
|
118
|
+
cos_lambda_v = Math.cos(lambda_v)
|
119
|
+
|
120
|
+
# This test isn't in original formulae, and fixes the problem of all angles returned being between 0 - PI (0-180)
|
121
|
+
# Also converts the result to compass bearing, rather than the mathmatical anticlockwise angles.
|
122
|
+
alpha_1 = if Math.sin(p2.longitude.to_rad - @longitude.to_rad) < 0
|
123
|
+
Math::PI * 2 - Math.atan2( cos_u2 * sin_lambda_v, cos_u1 * sin_u2 - sin_u1 * cos_u2 * cos_lambda_v)
|
124
|
+
# alpha_2 = Math::PI*2-Math.atan2(cos_u1 * sin_lambda_v, -sin_u1 * cos_u2 + cos_u1 * sin_u2 * cos_lambda_v)
|
125
|
+
else
|
126
|
+
Math.atan2( cos_u2 * sin_lambda_v, cos_u1 * sin_u2 - sin_u1 * cos_u2 * cos_lambda_v)
|
127
|
+
# alpha_2 = Math.atan2(cos_u1 * sin_lambda_v, -sin_u1 * cos_u2 + cos_u1 * sin_u2 * cos_lambda_v)
|
128
|
+
end
|
129
|
+
|
130
|
+
# Note that the bearing is a compass (i.e. clockwise) angle.
|
131
|
+
return TrackAndDistance.new(alpha_1, s, true) # What to do with alpha_2?
|
132
132
|
end
|
133
133
|
|
134
|
-
#spherical earth estimate of calculation for finding target coordinate from start coordinate, bearing and distance
|
135
|
-
#Used to run checks on the Vincenty algorithm
|
134
|
+
# spherical earth estimate of calculation for finding target coordinate from start coordinate, bearing and distance
|
135
|
+
# Used to run checks on the Vincenty algorithm
|
136
136
|
# @param [TrackAndDistance] track_and_distance specifying bearing and distance.
|
137
137
|
# @return [Vincenty] with the destination coordinates.
|
138
138
|
def sphereDestination( track_and_distance )
|
139
|
-
a = 6378137 #equatorial radius in meters (+/-2 m)
|
140
|
-
b = 6356752.31424518 #polar radius in meters
|
141
|
-
r = (a+b)/2 #average diametre as a rough estimate for our tests.
|
139
|
+
a = 6378137 # equatorial radius in meters (+/-2 m)
|
140
|
+
b = 6356752.31424518 # polar radius in meters
|
141
|
+
r = (a + b) / 2 # average diametre as a rough estimate for our tests.
|
142
142
|
|
143
143
|
d = track_and_distance.distance.abs
|
144
|
-
sin_dor = Math.sin(d/r)
|
145
|
-
cos_dor = Math.cos(d/r)
|
144
|
+
sin_dor = Math.sin(d / r)
|
145
|
+
cos_dor = Math.cos(d / r)
|
146
146
|
sin_lat1 = Math.sin(@latitude.to_rad)
|
147
147
|
cos_lat1 = Math.cos(@latitude.to_rad)
|
148
148
|
lat2 = Math.asin( sin_lat1 * cos_dor + cos_lat1 * sin_dor * Math.cos(track_and_distance.bearing.to_rad) )
|
149
|
-
lon2 = @longitude.to_rad + Math.atan2(Math.sin(track_and_distance.bearing.to_rad) * sin_dor * cos_lat1, cos_dor-sin_lat1 * Math.sin(lat2))
|
149
|
+
lon2 = @longitude.to_rad + Math.atan2(Math.sin(track_and_distance.bearing.to_rad) * sin_dor * cos_lat1, cos_dor - sin_lat1 * Math.sin(lat2))
|
150
150
|
|
151
|
-
Vincenty.new(lat2, lon2, 0, true)
|
151
|
+
Vincenty.new(lat2, lon2, 0, true)
|
152
152
|
end
|
153
153
|
|
154
154
|
#
|
155
155
|
# Calculate destination point given start point lat/long, bearing and distance.
|
156
|
-
#Assumes earth is a WGS-84 Ellipsod.
|
156
|
+
# Assumes earth is a WGS-84 Ellipsod.
|
157
157
|
# @param [TrackAndDistance] specifying bearing and distance.
|
158
158
|
# @return [Vincenty] with the destination coordinates.
|
159
159
|
def destination( track_and_distance )
|
160
160
|
# a, b = major & minor semiaxes of the ellipsoid
|
161
|
-
a = 6378137 #equatorial radius in meters (+/-2 m)
|
162
|
-
b = 6356752.31424518 #polar radius in meters
|
163
|
-
f = (a-b)/a # flattening
|
161
|
+
a = 6378137 # equatorial radius in meters (+/-2 m)
|
162
|
+
b = 6356752.31424518 # polar radius in meters
|
163
|
+
f = (a - b) / a # flattening
|
164
164
|
|
165
|
-
s = track_and_distance.distance.abs
|
165
|
+
s = track_and_distance.distance.abs
|
166
166
|
alpha1 = track_and_distance.bearing.to_rad
|
167
167
|
sin_alpha1 = Math.sin(alpha1)
|
168
168
|
cos_alpha1 = Math.cos(alpha1)
|
169
169
|
|
170
|
-
tanU1 = (1-f)
|
171
|
-
cosU1 = 1 / Math.sqrt((1
|
170
|
+
tanU1 = (1 - f) * Math.tan(@latitude.to_rad)
|
171
|
+
cosU1 = 1 / Math.sqrt((1 + tanU1 * tanU1))
|
172
172
|
sinU1 = tanU1 * cosU1
|
173
173
|
sigma1 = Math.atan2(tanU1, cos_alpha1)
|
174
|
-
sin_alpha = cosU1
|
175
|
-
cos_2_alpha = 1 - sin_alpha * sin_alpha #Trig identity
|
176
|
-
u_2 = cos_2_alpha
|
177
|
-
a1 = 1
|
178
|
-
b1 = u_2/1024
|
174
|
+
sin_alpha = cosU1 * sin_alpha1
|
175
|
+
cos_2_alpha = 1 - sin_alpha * sin_alpha # Trig identity
|
176
|
+
u_2 = cos_2_alpha * (a * a - b * b) / (b * b)
|
177
|
+
a1 = 1 + u_2 / 16384 * (4096 + u_2 * (-768 + u_2 * (320 - 175 * u_2)))
|
178
|
+
b1 = u_2 / 1024 * (256 + u_2 * (-128 + u_2 * (74 - 47 * u_2)))
|
179
179
|
|
180
180
|
sigma = s / (b * a1)
|
181
181
|
sigma_dash = 2 * Math::PI
|
182
|
-
while (
|
183
|
-
cos_2_sigma_m = Math.cos(2 * sigma1
|
182
|
+
while (sigma - sigma_dash).abs > 1.0e-12 # i.e 0.06mm
|
183
|
+
cos_2_sigma_m = Math.cos(2 * sigma1 + sigma)
|
184
184
|
sin_sigma = Math.sin(sigma)
|
185
185
|
cos_sigma = Math.cos(sigma)
|
186
|
-
delta_sigma = b1 * sin_sigma * (cos_2_sigma_m + b1/4 * (cos_sigma * (-1 + 2 * cos_2_sigma_m * cos_2_sigma_m) - b1/6 * cos_2_sigma_m * (-3 + 4 * sin_sigma * sin_sigma) * (-3 + 4 * cos_2_sigma_m * cos_2_sigma_m)))
|
186
|
+
delta_sigma = b1 * sin_sigma * (cos_2_sigma_m + b1 / 4 * (cos_sigma * (-1 + 2 * cos_2_sigma_m * cos_2_sigma_m) - b1 / 6 * cos_2_sigma_m * (-3 + 4 * sin_sigma * sin_sigma) * (-3 + 4 * cos_2_sigma_m * cos_2_sigma_m)))
|
187
187
|
sigma_dash = sigma
|
188
|
-
sigma = s / (b * a1)
|
188
|
+
sigma = s / (b * a1) + delta_sigma
|
189
189
|
end
|
190
190
|
|
191
191
|
tmp = sinU1 * sin_sigma - cosU1 * cos_sigma * cos_alpha1
|
192
|
-
lat2 = Math.atan2(sinU1 * cos_sigma
|
192
|
+
lat2 = Math.atan2(sinU1 * cos_sigma + cosU1 * sin_sigma * cos_alpha1, (1 - f) * Math.sqrt(sin_alpha * sin_alpha + tmp * tmp))
|
193
193
|
lambda_v = Math.atan2(sin_sigma * sin_alpha1, cosU1 * cos_sigma - sinU1 * sin_sigma * cos_alpha1)
|
194
|
-
c = f/16 * cos_2_alpha * (4 + f * (4-3 * cos_2_alpha))
|
195
|
-
l = lambda_v - (1-c)
|
194
|
+
c = f / 16 * cos_2_alpha * (4 + f * (4 - 3 * cos_2_alpha))
|
195
|
+
l = lambda_v - (1 - c) * f * sin_alpha * (sigma + c * sin_sigma * (cos_2_sigma_m + c * cos_sigma * (-1 + 2 * cos_2_sigma_m * cos_2_sigma_m))) # difference in longitude
|
196
196
|
|
197
|
-
#sigma2 = Math.atan2(sin_alpha, -tmp) # reverse azimuth
|
197
|
+
# sigma2 = Math.atan2(sin_alpha, -tmp) # reverse azimuth
|
198
198
|
|
199
|
-
return Vincenty.new(lat2, @longitude + l, 0, true)
|
199
|
+
return Vincenty.new(lat2, @longitude + l, 0, true)
|
200
200
|
end
|
201
|
-
|
202
|
-
|
203
201
|
end
|