geo_calc 0.5.3 → 0.6.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.
- data/Changelog.textile +4 -0
- data/README.textile +7 -0
- data/VERSION +1 -1
- data/geo_calc.gemspec +27 -19
- data/lib/geo_calc/calc/bearing.rb +51 -0
- data/lib/geo_calc/calc/destination.rb +30 -0
- data/lib/geo_calc/calc/distance.rb +43 -0
- data/lib/geo_calc/calc/intersection.rb +72 -0
- data/lib/geo_calc/calc/midpoint.rb +29 -0
- data/lib/geo_calc/calc/rhumb.rb +98 -0
- data/lib/geo_calc/calc.rb +27 -0
- data/lib/geo_calc/core_ext.rb +97 -39
- data/lib/geo_calc/geo.rb +3 -2
- data/lib/geo_calc/geo_point.rb +13 -2
- data/lib/geo_calc/pretty_print.rb +52 -0
- data/spec/geo_calc/calculations_spec.rb +3 -2
- data/spec/geo_calc/core_ext/array_ext_spec.rb +47 -0
- data/spec/geo_calc/core_ext/hash_ext_spec.rb +44 -0
- data/spec/geo_calc/core_ext/numeric_geo_ext_spec.rb +73 -0
- data/spec/geo_calc/core_ext/string_ext_spec.rb +77 -0
- data/spec/geo_calc/core_ext_spec.rb +6 -220
- data/spec/geo_calc/geo_point_spec.rb +39 -3
- data/spec/geo_calc/geo_spec.rb +1 -1
- data/{lib/geo_calc/js → vendor/assets/javascript}/geo_calc.js +0 -0
- metadata +28 -16
- data/lib/geo_calc/calculations.rb +0 -333
data/Changelog.textile
ADDED
data/README.textile
CHANGED
@@ -16,6 +16,13 @@ From command line, run bundler
|
|
16
16
|
|
17
17
|
@$ bundle@
|
18
18
|
|
19
|
+
h2. Objetive
|
20
|
+
|
21
|
+
The objective of this library is to act as a base for other geo libraries.
|
22
|
+
|
23
|
+
Please take a look at "geo vectors":https://github.com/kristianmandrup/geo_vectors which builds on _geo calc_ and provides various vector operations on geo points.
|
24
|
+
I also have plans to include _geo calc_ in "mongoid geo":https://github.com/kristianmandrup/mongoid_geo, the geo extension library for _Mongoid 2_.
|
25
|
+
|
19
26
|
h2. Quick start (Usage)
|
20
27
|
|
21
28
|
First define the points on the globe you want to work with.
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.6.0
|
data/geo_calc.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{geo_calc}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.6.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = [%q{Kristian Mandrup}]
|
12
|
-
s.date = %q{2011-05-
|
12
|
+
s.date = %q{2011-05-23}
|
13
13
|
s.description = %q{Geo calculations in ruby and javascript}
|
14
14
|
s.email = %q{kmandrup@gmail.com}
|
15
15
|
s.extra_rdoc_files = [
|
@@ -19,6 +19,7 @@ Gem::Specification.new do |s|
|
|
19
19
|
s.files = [
|
20
20
|
".document",
|
21
21
|
".rspec",
|
22
|
+
"Changelog.textile",
|
22
23
|
"Gemfile",
|
23
24
|
"LICENSE.txt",
|
24
25
|
"README.textile",
|
@@ -26,49 +27,56 @@ Gem::Specification.new do |s|
|
|
26
27
|
"VERSION",
|
27
28
|
"geo_calc.gemspec",
|
28
29
|
"lib/geo_calc.rb",
|
29
|
-
"lib/geo_calc/
|
30
|
+
"lib/geo_calc/calc.rb",
|
31
|
+
"lib/geo_calc/calc/bearing.rb",
|
32
|
+
"lib/geo_calc/calc/destination.rb",
|
33
|
+
"lib/geo_calc/calc/distance.rb",
|
34
|
+
"lib/geo_calc/calc/intersection.rb",
|
35
|
+
"lib/geo_calc/calc/midpoint.rb",
|
36
|
+
"lib/geo_calc/calc/rhumb.rb",
|
30
37
|
"lib/geo_calc/core_ext.rb",
|
31
38
|
"lib/geo_calc/geo.rb",
|
32
39
|
"lib/geo_calc/geo_point.rb",
|
33
|
-
"lib/geo_calc/
|
40
|
+
"lib/geo_calc/pretty_print.rb",
|
34
41
|
"spec/geo_calc/calculations_spec.rb",
|
42
|
+
"spec/geo_calc/core_ext/array_ext_spec.rb",
|
43
|
+
"spec/geo_calc/core_ext/hash_ext_spec.rb",
|
44
|
+
"spec/geo_calc/core_ext/numeric_geo_ext_spec.rb",
|
45
|
+
"spec/geo_calc/core_ext/string_ext_spec.rb",
|
35
46
|
"spec/geo_calc/core_ext_spec.rb",
|
36
47
|
"spec/geo_calc/geo_point_spec.rb",
|
37
48
|
"spec/geo_calc/geo_spec.rb",
|
38
|
-
"spec/spec_helper.rb"
|
49
|
+
"spec/spec_helper.rb",
|
50
|
+
"vendor/assets/javascript/geo_calc.js"
|
39
51
|
]
|
40
52
|
s.homepage = %q{http://github.com/kristianmandrup/geo_calc}
|
41
53
|
s.licenses = [%q{MIT}]
|
42
54
|
s.require_paths = [%q{lib}]
|
43
|
-
s.rubygems_version = %q{1.8.
|
55
|
+
s.rubygems_version = %q{1.8.3}
|
44
56
|
s.summary = %q{Geo calculation library}
|
45
|
-
s.test_files = [
|
46
|
-
"spec/geo_calc/calculations_spec.rb",
|
47
|
-
"spec/geo_calc/core_ext_spec.rb",
|
48
|
-
"spec/geo_calc/geo_point_spec.rb",
|
49
|
-
"spec/geo_calc/geo_spec.rb",
|
50
|
-
"spec/spec_helper.rb"
|
51
|
-
]
|
52
57
|
|
53
58
|
if s.respond_to? :specification_version then
|
54
59
|
s.specification_version = 3
|
55
60
|
|
56
61
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
57
62
|
s.add_development_dependency(%q<rspec>, [">= 2.5.0"])
|
58
|
-
s.add_development_dependency(%q<bundler>, ["
|
59
|
-
s.add_development_dependency(%q<jeweler>, ["
|
63
|
+
s.add_development_dependency(%q<bundler>, [">= 1"])
|
64
|
+
s.add_development_dependency(%q<jeweler>, [">= 1.5.2"])
|
60
65
|
s.add_development_dependency(%q<rcov>, [">= 0"])
|
66
|
+
s.add_development_dependency(%q<rake>, [">= 0.9"])
|
61
67
|
else
|
62
68
|
s.add_dependency(%q<rspec>, [">= 2.5.0"])
|
63
|
-
s.add_dependency(%q<bundler>, ["
|
64
|
-
s.add_dependency(%q<jeweler>, ["
|
69
|
+
s.add_dependency(%q<bundler>, [">= 1"])
|
70
|
+
s.add_dependency(%q<jeweler>, [">= 1.5.2"])
|
65
71
|
s.add_dependency(%q<rcov>, [">= 0"])
|
72
|
+
s.add_dependency(%q<rake>, [">= 0.9"])
|
66
73
|
end
|
67
74
|
else
|
68
75
|
s.add_dependency(%q<rspec>, [">= 2.5.0"])
|
69
|
-
s.add_dependency(%q<bundler>, ["
|
70
|
-
s.add_dependency(%q<jeweler>, ["
|
76
|
+
s.add_dependency(%q<bundler>, [">= 1"])
|
77
|
+
s.add_dependency(%q<jeweler>, [">= 1.5.2"])
|
71
78
|
s.add_dependency(%q<rcov>, [">= 0"])
|
79
|
+
s.add_dependency(%q<rake>, [">= 0.9"])
|
72
80
|
end
|
73
81
|
end
|
74
82
|
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module GeoCalc::Calc
|
2
|
+
module Bearing
|
3
|
+
def bearing_to point
|
4
|
+
GeoCalc::Calc::Bearing.bearing_to self, point
|
5
|
+
end
|
6
|
+
|
7
|
+
# Returns the (initial) bearing from this point to the supplied point, in degrees
|
8
|
+
# see http:#williams.best.vwh.net/avform.htm#Crs
|
9
|
+
#
|
10
|
+
# - Point point: Latitude/longitude of destination point
|
11
|
+
#
|
12
|
+
# Returns - Numeric: Initial bearing in degrees from North
|
13
|
+
|
14
|
+
def self.bearing_to base_point, point
|
15
|
+
lat1 = base_point.lat.to_rad
|
16
|
+
lat2 = point.lat.to_rad
|
17
|
+
dlon = (point.lon - base_point.lon).to_rad
|
18
|
+
|
19
|
+
y = Math.sin(dlon) * Math.cos(lat2)
|
20
|
+
x = Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1) * Math.cos(lat2) * Math.cos(dlon)
|
21
|
+
bearing = Math.atan2(y, x)
|
22
|
+
|
23
|
+
(bearing.to_deg + 360) % 360
|
24
|
+
end
|
25
|
+
|
26
|
+
def final_bearing_to point
|
27
|
+
GeoCalc::Calc::Bearing.final_bearing_to self, point
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns final bearing arriving at supplied destination point from this point; the final bearing
|
31
|
+
# will differ from the initial bearing by varying degrees according to distance and latitude
|
32
|
+
#
|
33
|
+
# - GeoPoint point: Latitude/longitude of destination point
|
34
|
+
#
|
35
|
+
# Returns Numeric: Final bearing in degrees from North
|
36
|
+
|
37
|
+
def self.final_bearing_to base_point, point
|
38
|
+
# get initial bearing from supplied point back to this point...
|
39
|
+
lat1 = point.lat.to_rad
|
40
|
+
lat2 = base_point.lat.to_rad
|
41
|
+
dlon = (base_point.lon - point.lon).to_rad
|
42
|
+
|
43
|
+
y = Math.sin(dlon) * Math.cos(lat2)
|
44
|
+
x = Math.cos(lat1)*Math.sin(lat2) - Math.sin(lat1)*Math.cos(lat2)*Math.cos(dlon)
|
45
|
+
bearing = Math.atan2(y, x)
|
46
|
+
|
47
|
+
# ... & reverse it by adding 180°
|
48
|
+
(bearing.to_deg+180) % 360
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module GeoCalc::Calc
|
2
|
+
module Destination
|
3
|
+
def destination_point brng, dist
|
4
|
+
GeoCalc::Calc::Destination.destination_point self, brng, dist
|
5
|
+
end
|
6
|
+
|
7
|
+
# Returns the destination point from this point having travelled the given distance (in km) on the
|
8
|
+
# given initial bearing (bearing may vary before destination is reached)
|
9
|
+
#
|
10
|
+
# see http:#williams.best.vwh.net/avform.htm#LL
|
11
|
+
#
|
12
|
+
# - Numeric bearing: Initial bearing in degrees
|
13
|
+
# - Numeric dist: Distance in km
|
14
|
+
# Returns GeoPoint: Destination point
|
15
|
+
|
16
|
+
def self.destination_point base_point, brng, dist
|
17
|
+
dist = dist / base_point.radius # convert dist to angular distance in radians
|
18
|
+
brng = brng.to_rad
|
19
|
+
lat1 = base_point.lat.to_rad
|
20
|
+
lon1 = base_point.lon.to_rad
|
21
|
+
|
22
|
+
lat2 = Math.asin( Math.sin(lat1) * Math.cos(dist) + Math.cos(lat1) * Math.sin(dist) * Math.cos(brng) )
|
23
|
+
lon2 = lon1 + Math.atan2(Math.sin(brng) * Math.sin(dist) * Math.cos(lat1), Math.cos(dist) - Math.sin(lat1) * Math.sin(lat2))
|
24
|
+
|
25
|
+
lon2 = (lon2 + 3*Math::PI) % (2*Math::PI) - Math::PI # normalise to -180...+180
|
26
|
+
|
27
|
+
GeoPoint.new lat2.to_deg, lon2.to_deg
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
2
|
+
# Latitude/longitude spherical geodesy formulae & scripts (c) Chris Veness 2002-2010
|
3
|
+
# - www.movable-type.co.uk/scripts/latlong.html
|
4
|
+
#
|
5
|
+
|
6
|
+
module GeoCalc::Calc
|
7
|
+
module Distance
|
8
|
+
def distance_to point, precision = 4
|
9
|
+
GeoCalc::Calc::Distance.distance_to self, point, precision
|
10
|
+
end
|
11
|
+
|
12
|
+
# Returns the distance from this point to the supplied point, in km
|
13
|
+
# (using Haversine formula)
|
14
|
+
#
|
15
|
+
# from: Haversine formula - R. W. Sinnott, "Virtues of the Haversine",
|
16
|
+
# Sky and Telescope, vol 68, no 2, 1984
|
17
|
+
#
|
18
|
+
# GeoPoint point: Latitude/longitude of destination point
|
19
|
+
# - Numeric precision=4: number of significant digits to use for returned value
|
20
|
+
#
|
21
|
+
# Returns - Numeric distance in km between this point and destination point
|
22
|
+
|
23
|
+
def self.distance_to base_point, point, precision = 4
|
24
|
+
# default 4 sig figs reflects typical 0.3% accuracy of spherical model
|
25
|
+
precision ||= 4
|
26
|
+
|
27
|
+
lat1 = base_point.lat.to_rad
|
28
|
+
lon1 = base_point.lon.to_rad
|
29
|
+
|
30
|
+
lat2 = point.lat.to_rad
|
31
|
+
lon2 = point.lon.to_rad
|
32
|
+
|
33
|
+
dlat = lat2 - lat1
|
34
|
+
dlon = lon2 - lon1
|
35
|
+
|
36
|
+
a = Math.sin(dlat/2) * Math.sin(dlat/2) + Math.cos(lat1) * Math.cos(lat2) * Math.sin(dlon/2) * Math.sin(dlon/2)
|
37
|
+
c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a))
|
38
|
+
d = base_point.radius * c
|
39
|
+
d.round(precision)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module GeoCalc::Calc
|
2
|
+
module Intersection
|
3
|
+
def intersection brng1, p2, brng2
|
4
|
+
GeoCalc::Calc::Intersection.intersection self, brng1, p2, brng2
|
5
|
+
end
|
6
|
+
|
7
|
+
# Returns the point of intersection of two paths defined by point and bearing
|
8
|
+
#
|
9
|
+
# see http:#williams.best.vwh.net/avform.htm#Intersection
|
10
|
+
#
|
11
|
+
# @param {LatLon} p1: First point
|
12
|
+
# @param {Number} brng1: Initial bearing from first point
|
13
|
+
# @param {LatLon} p2: Second point
|
14
|
+
# @param {Number} brng2: Initial bearing from second point
|
15
|
+
# @returns {LatLon} Destination point (null if no unique intersection defined)
|
16
|
+
|
17
|
+
def self.intersection p1, brng1, p2, brng2
|
18
|
+
lat1 = p1.lat.to_rad
|
19
|
+
lon1 = p1.lon.to_rad
|
20
|
+
|
21
|
+
lat2 = p2.lat.to_rad
|
22
|
+
lon2 = p2.lon.to_rad
|
23
|
+
|
24
|
+
brng13 = brng1.to_rad
|
25
|
+
brng23 = brng2.to_rad
|
26
|
+
|
27
|
+
dlat = lat2-lat1
|
28
|
+
dlon = lon2-lon1;
|
29
|
+
|
30
|
+
dist12 = 2*Math.asin( Math.sqrt( Math.sin(dlat/2)*Math.sin(dlat/2) + Math.cos(lat1)*Math.cos(lat2)*Math.sin(dlon/2)*Math.sin(dlon/2) ) )
|
31
|
+
return nil if dist12 == 0
|
32
|
+
|
33
|
+
# initial/final bearings between points
|
34
|
+
brng_a = begin
|
35
|
+
Math.acos( ( Math.sin(lat2) - Math.sin(lat1)*Math.cos(dist12) ) / ( Math.sin(dist12)*Math.cos(lat1) ) )
|
36
|
+
rescue # protect against rounding
|
37
|
+
0
|
38
|
+
end
|
39
|
+
|
40
|
+
brng_b = Math.acos( ( Math.sin(lat1) - Math.sin(lat2)*Math.cos(dist12) ) / ( Math.sin(dist12)*Math.cos(lat2) ) )
|
41
|
+
|
42
|
+
brng12, brng21 = if Math.sin(lon2-lon1) > 0
|
43
|
+
[brng_a, 2*Math::PI - brng_b]
|
44
|
+
else
|
45
|
+
[2*Math::PI - brng_a, brng_b]
|
46
|
+
end
|
47
|
+
|
48
|
+
alpha1 = (brng13 - brng12 + Math::PI) % (2*Math::PI) - Math::PI # angle 2-1-3
|
49
|
+
alpha2 = (brng21 - brng23 + Math::PI) % (2*Math::PI) - Math::PI # angle 1-2-3
|
50
|
+
|
51
|
+
return nil if (Math.sin(alpha1)==0 && Math.sin(alpha2)==0) # infinite intersections
|
52
|
+
return nil if (Math.sin(alpha1)*Math.sin(alpha2) < 0) # ambiguous intersection
|
53
|
+
|
54
|
+
# alpha1 = Math.abs(alpha1);
|
55
|
+
# alpha2 = Math.abs(alpha2);
|
56
|
+
# ... Ed Williams takes abs of alpha1/alpha2, but seems to break calculation?
|
57
|
+
|
58
|
+
alpha3 = Math.acos( -Math.cos(alpha1)*Math.cos(alpha2) + Math.sin(alpha1)*Math.sin(alpha2)*Math.cos(dist12) )
|
59
|
+
|
60
|
+
dist13 = Math.atan2( Math.sin(dist12)*Math.sin(alpha1)*Math.sin(alpha2), Math.cos(alpha2)+Math.cos(alpha1)*Math.cos(alpha3) )
|
61
|
+
|
62
|
+
lat3 = Math.asin( Math.sin(lat1)*Math.cos(dist13) + Math.cos(lat1)*Math.sin(dist13)*Math.cos(brng13) )
|
63
|
+
|
64
|
+
dlon13 = Math.atan2( Math.sin(brng13)*Math.sin(dist13)*Math.cos(lat1), Math.cos(dist13)-Math.sin(lat1)*Math.sin(lat3) )
|
65
|
+
|
66
|
+
lon3 = lon1 + dlon13;
|
67
|
+
lon3 = (lon3 + Math::PI) % (2*Math::PI) - Math::PI # normalise to -180..180º
|
68
|
+
|
69
|
+
GeoPoint.new lat3.to_deg, lon3.to_deg
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module GeoCalc::Calc
|
2
|
+
module Midpoint
|
3
|
+
def midpoint_to point
|
4
|
+
GeoCalc::Calc::Midpoint.midpoint_to self, point
|
5
|
+
end
|
6
|
+
|
7
|
+
# Returns the midpoint between this point and the supplied point.
|
8
|
+
# see http:#mathforum.org/library/drmath/view/51822.html for derivation
|
9
|
+
#
|
10
|
+
# - GeoPoint point: Latitude/longitude of destination point
|
11
|
+
# Returns GeoPoint: Midpoint between this point and the supplied point
|
12
|
+
|
13
|
+
def self.midpoint_to base_point, point
|
14
|
+
lat1 = base_point.lat.to_rad
|
15
|
+
lon1 = base_point.lon.to_rad;
|
16
|
+
lat2 = point.lat.to_rad
|
17
|
+
dlon = (point.lon - base_point.lon).to_rad
|
18
|
+
|
19
|
+
bx = Math.cos(lat2) * Math.cos(dlon)
|
20
|
+
by = Math.cos(lat2) * Math.sin(dlon)
|
21
|
+
|
22
|
+
lat3 = Math.atan2(Math.sin(lat1)+Math.sin(lat2), Math.sqrt( (Math.cos(lat1)+bx)*(Math.cos(lat1)+bx) + by*by) )
|
23
|
+
|
24
|
+
lon3 = lon1 + Math.atan2(by, Math.cos(lat1) + bx)
|
25
|
+
|
26
|
+
GeoPoint.new lat3.to_deg, lon3.to_deg
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module GeoCalc::Calc
|
2
|
+
module Rhumb
|
3
|
+
def rhumb_distance_to point
|
4
|
+
GeoCalc::Calc::Rhumb.rhumb_distance_to self, point
|
5
|
+
end
|
6
|
+
|
7
|
+
# Returns the distance from this point to the supplied point, in km, travelling along a rhumb line
|
8
|
+
#
|
9
|
+
# see http:#williams.best.vwh.net/avform.htm#Rhumb
|
10
|
+
#
|
11
|
+
# - GeoPoint point: Latitude/longitude of destination point
|
12
|
+
# Returns Numeric: Distance in km between this point and destination point
|
13
|
+
|
14
|
+
def self.rhumb_distance_to base_point, point
|
15
|
+
lat1 = base_point.lat.to_rad
|
16
|
+
lat2 = point.lat.to_rad
|
17
|
+
|
18
|
+
dlat = (point.lat - base_point.lat).to_rad
|
19
|
+
dlon = (point.lon - base_point.lon).abs.to_rad
|
20
|
+
|
21
|
+
dphi = Math.log(Math.tan(lat2/2 + Math::PI/4) / Math.tan(lat1/2 + Math::PI/4))
|
22
|
+
|
23
|
+
q = begin
|
24
|
+
dlat / dphi
|
25
|
+
rescue
|
26
|
+
Math.cos(lat1) # E-W line gives dPhi=0
|
27
|
+
end
|
28
|
+
|
29
|
+
# if dlon over 180° take shorter rhumb across 180° meridian:
|
30
|
+
dlon = 2*Math::PI - dlon if (dlon > Math::PI)
|
31
|
+
|
32
|
+
dist = Math.sqrt(dlat*dlat + q*q*dlon*dlon) * base_point.radius;
|
33
|
+
|
34
|
+
dist.round(4) # 4 sig figures reflects typical 0.3% accuracy of spherical model
|
35
|
+
end
|
36
|
+
|
37
|
+
def rhumb_bearing_to point
|
38
|
+
GeoCalc::Calc::Rhumb.rhumb_bearing_to self, point
|
39
|
+
end
|
40
|
+
# Returns the bearing from this point to the supplied point along a rhumb line, in degrees
|
41
|
+
#
|
42
|
+
# - GeoPoint point: Latitude/longitude of destination point
|
43
|
+
# Returns Numeric: Bearing in degrees from North
|
44
|
+
|
45
|
+
def self.rhumb_bearing_to base_point, point
|
46
|
+
lat1 = base_point.lat.to_rad
|
47
|
+
lat2 = point.lat.to_rad
|
48
|
+
|
49
|
+
dlon = (point.lon - base_point.lon).to_rad
|
50
|
+
|
51
|
+
dphi = Math.log(Math.tan(lat2/2+Math::PI/4) / Math.tan(lat1/2+Math::PI/4))
|
52
|
+
if dlon.abs > Math::PI
|
53
|
+
dlon = dlon>0 ? -(2*Math::PI-dlon) : (2*Math::PI+dlon);
|
54
|
+
end
|
55
|
+
|
56
|
+
brng = Math.atan2(dlon, dphi);
|
57
|
+
|
58
|
+
(brng.to_deg+360) % 360
|
59
|
+
end
|
60
|
+
|
61
|
+
def rhumb_destination_point brng, dist
|
62
|
+
GeoCalc::Calc::Rhumb.rhumb_destination_point self, brng, dist
|
63
|
+
end
|
64
|
+
# Returns the destination point from this point having travelled the given distance (in km) on the
|
65
|
+
# given bearing along a rhumb line
|
66
|
+
#
|
67
|
+
# @param {Number} brng: Bearing in degrees from North
|
68
|
+
# @param {Number} dist: Distance in km
|
69
|
+
# @returns {LatLon} Destination point
|
70
|
+
|
71
|
+
def self.rhumb_destination_point base_point, brng, dist
|
72
|
+
d = dist / base_point.radius # d = angular distance covered on earth's surface
|
73
|
+
lat1 = base_point.lat.to_rad
|
74
|
+
lon1 = base_point.lon.to_rad
|
75
|
+
brng = brng.to_rad
|
76
|
+
|
77
|
+
lat2 = lat1 + d*Math.cos(brng);
|
78
|
+
dlat = lat2 - lat1;
|
79
|
+
dphi = Math.log(Math.tan(lat2/2+Math::PI/4) / Math.tan(lat1/2+Math::PI/4))
|
80
|
+
|
81
|
+
q = begin
|
82
|
+
dlat / dphi
|
83
|
+
rescue
|
84
|
+
Math.cos(lat1) # E-W line gives dPhi=0
|
85
|
+
end
|
86
|
+
|
87
|
+
dlon = d * Math.sin(brng) / q
|
88
|
+
# check for some daft bugger going past the pole
|
89
|
+
|
90
|
+
if lat2.abs > Math::PI/2
|
91
|
+
lat2 = lat2>0 ? Math::PI-lat2 : -(Math::PI-lat2)
|
92
|
+
end
|
93
|
+
lon2 = (lon1+dlon+3*Math::PI) % (2*Math::PI) - Math::PI
|
94
|
+
|
95
|
+
GeoPoint.new lat2.to_deg, lon2.to_deg
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module GeoCalc
|
2
|
+
autoload :PrettyPrint, 'geo_calc/pretty_print'
|
3
|
+
|
4
|
+
module Calc
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
module GeoCalc::Calc
|
9
|
+
autoload :Bearing, 'geo_calc/calc/bearing'
|
10
|
+
autoload :Destination, 'geo_calc/calc/destination'
|
11
|
+
autoload :Distance, 'geo_calc/calc/distance'
|
12
|
+
autoload :Intersection, 'geo_calc/calc/intersection'
|
13
|
+
autoload :Midpoint, 'geo_calc/calc/midpoint'
|
14
|
+
autoload :Rhumb, 'geo_calc/calc/rhumb'
|
15
|
+
|
16
|
+
module All
|
17
|
+
def self.included base
|
18
|
+
base.send :include, GeoCalc::Calc::Bearing
|
19
|
+
base.send :include, GeoCalc::Calc::Destination
|
20
|
+
base.send :include, GeoCalc::Calc::Distance
|
21
|
+
base.send :include, GeoCalc::Calc::Intersection
|
22
|
+
base.send :include, GeoCalc::Calc::Midpoint
|
23
|
+
base.send :include, GeoCalc::Calc::Rhumb
|
24
|
+
base.send :include, GeoCalc::PrettyPrint
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/geo_calc/core_ext.rb
CHANGED
@@ -36,6 +36,46 @@ module GeoUnits
|
|
36
36
|
alias_method :in_deg, :to_deg
|
37
37
|
alias_method :in_degrees, :to_deg
|
38
38
|
end
|
39
|
+
|
40
|
+
# all degrees between -180 and 180
|
41
|
+
def normalize_lng deg
|
42
|
+
case deg
|
43
|
+
when -360..-180
|
44
|
+
deg % 180
|
45
|
+
when -180..0
|
46
|
+
-180 + (deg % 180)
|
47
|
+
when 0..180
|
48
|
+
deg
|
49
|
+
when 180..360
|
50
|
+
deg % 180
|
51
|
+
else
|
52
|
+
raise ArgumentError, "Degrees #{deg} out of range, must be between -360 to 360"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# all degrees between -90 and 90
|
57
|
+
def normalize_lat deg
|
58
|
+
case deg
|
59
|
+
when -360..-270
|
60
|
+
deg % 90
|
61
|
+
when -270..-180
|
62
|
+
90 - (deg % 90)
|
63
|
+
when -180..-90
|
64
|
+
- (deg % 90)
|
65
|
+
when -90..0
|
66
|
+
-90 + (deg % 90)
|
67
|
+
when 0..90
|
68
|
+
deg
|
69
|
+
when 90..180
|
70
|
+
deg % 90
|
71
|
+
when 180..270
|
72
|
+
- (deg % 90)
|
73
|
+
when 270..360
|
74
|
+
- 90 + (deg % 90)
|
75
|
+
else
|
76
|
+
raise ArgumentError, "Degrees #{deg} out of range, must be between -360 to 360"
|
77
|
+
end
|
78
|
+
end
|
39
79
|
|
40
80
|
def normalize_deg degrees, shift = 0
|
41
81
|
(degrees + shift) % 360
|
@@ -87,46 +127,63 @@ module NumericGeoExt
|
|
87
127
|
# @returns {String} A string representation of number which contains precision significant digits
|
88
128
|
def to_precision precision
|
89
129
|
self.round(precision).to_s
|
90
|
-
|
91
|
-
# numb = self.abs # can't take log of -ve number...
|
92
|
-
# sign = self < 0 ? '-' : '';
|
93
|
-
#
|
94
|
-
# # can't take log of zero
|
95
|
-
# if (numb == 0)
|
96
|
-
# n = '0.'
|
97
|
-
# while (precision -= 1) > 0
|
98
|
-
# n += '0'
|
99
|
-
# end
|
100
|
-
# return n
|
101
|
-
# end
|
102
|
-
#
|
103
|
-
# scale = (Math.log(numb) * Math.log10e).ceil # no of digits before decimal
|
104
|
-
# n = (numb * (precision - scale)**10).round.to_s
|
105
|
-
# if (scale > 0) # add trailing zeros & insert decimal as required
|
106
|
-
# l = scale - n.length
|
107
|
-
#
|
108
|
-
# while (l -= 1) > 0
|
109
|
-
# n += '0'
|
110
|
-
# end
|
111
|
-
#
|
112
|
-
# if scale < n.length
|
113
|
-
# n = n.slice(0,scale) + '.' + n.slice(scale)
|
114
|
-
# else # prefix decimal and leading zeros if required
|
115
|
-
# while (scale += 1) < 0
|
116
|
-
# n = '0' + n
|
117
|
-
# end
|
118
|
-
# n = '0.' + n
|
119
|
-
# end
|
120
|
-
# end
|
121
|
-
# sign + n
|
122
130
|
end
|
123
131
|
alias_method :to_fixed, :to_precision
|
124
132
|
|
133
|
+
# all degrees between -180 and 180
|
134
|
+
def normalize_lng
|
135
|
+
case self
|
136
|
+
when -360, 0, 360
|
137
|
+
0
|
138
|
+
when -360..-180
|
139
|
+
self % 180
|
140
|
+
when -180..0
|
141
|
+
-180 + (self % 180)
|
142
|
+
when 0..180
|
143
|
+
self
|
144
|
+
when 180..360
|
145
|
+
self % 180
|
146
|
+
else
|
147
|
+
return (self % 360).normalize_lng if self > 360
|
148
|
+
return (360 - (self % 360)).normalize_lng if self < -360
|
149
|
+
raise ArgumentError, "Degrees #{self} out of range"
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# all degrees between -90 and 90
|
154
|
+
def normalize_lat
|
155
|
+
case self
|
156
|
+
when -360, 0, 360
|
157
|
+
0
|
158
|
+
when -180, 180
|
159
|
+
0
|
160
|
+
when -360..-270
|
161
|
+
self % 90
|
162
|
+
when -270..-180
|
163
|
+
90 - (self % 90)
|
164
|
+
when -180..-90
|
165
|
+
- (self % 90)
|
166
|
+
when -90..0
|
167
|
+
-90 + (self % 90)
|
168
|
+
when 0..90
|
169
|
+
self
|
170
|
+
when 90..180
|
171
|
+
self % 90
|
172
|
+
when 180..270
|
173
|
+
- (self % 90)
|
174
|
+
when 270..360
|
175
|
+
- 90 + (self % 90)
|
176
|
+
else
|
177
|
+
return (self % 360).normalize_lat if self > 360
|
178
|
+
return (360 - (self % 360)).normalize_lat if self < -360
|
179
|
+
raise ArgumentError, "Degrees #{self} out of range"
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
125
183
|
def normalize_deg shift = 0
|
126
184
|
(self + shift) % 360
|
127
185
|
end
|
128
|
-
alias_method :normalize_degrees, :normalize_deg
|
129
|
-
|
186
|
+
alias_method :normalize_degrees, :normalize_deg
|
130
187
|
end
|
131
188
|
|
132
189
|
module Math
|
@@ -137,9 +194,12 @@ end
|
|
137
194
|
|
138
195
|
module NumericLatLngExt
|
139
196
|
def to_lat
|
140
|
-
|
197
|
+
normalize_lat
|
198
|
+
end
|
199
|
+
|
200
|
+
def to_lng
|
201
|
+
normalize_lng
|
141
202
|
end
|
142
|
-
alias_method :to_lng, :to_lat
|
143
203
|
|
144
204
|
def is_between? lower, upper
|
145
205
|
(lower..upper).cover? self
|
@@ -247,8 +307,6 @@ class String
|
|
247
307
|
end
|
248
308
|
end
|
249
309
|
|
250
|
-
|
251
|
-
|
252
310
|
class Fixnum
|
253
311
|
include NumericGeoExt
|
254
312
|
include NumericLatLngExt
|
@@ -258,4 +316,4 @@ class Float
|
|
258
316
|
include NumericGeoExt
|
259
317
|
include NumericLatLngExt
|
260
318
|
end
|
261
|
-
|
319
|
+
|
data/lib/geo_calc/geo.rb
CHANGED
@@ -61,7 +61,7 @@ module Geo
|
|
61
61
|
# @returns {String} deg formatted as deg/min/secs according to specified format
|
62
62
|
# @throws {TypeError} deg is an object, perhaps DOM object without .value?
|
63
63
|
|
64
|
-
def to_dms deg, format = :dms, dp = nil
|
64
|
+
def to_dms deg, format = :dms, dp = nil
|
65
65
|
deg = begin
|
66
66
|
deg.to_f
|
67
67
|
rescue
|
@@ -128,6 +128,7 @@ module Geo
|
|
128
128
|
# @returns {String} Deg/min/seconds
|
129
129
|
|
130
130
|
def to_lat deg, format = :dms, dp = 0
|
131
|
+
deg = deg.normalize_lat
|
131
132
|
_lat = to_dms deg, format, dp
|
132
133
|
_lat == '' ? '' : _lat[1..-1] + (deg<0 ? 'S' : 'N') # knock off initial '0' for lat!
|
133
134
|
end
|
@@ -141,7 +142,7 @@ module Geo
|
|
141
142
|
# @returns {String} Deg/min/seconds
|
142
143
|
|
143
144
|
def to_lon deg, format = :dms, dp = 0
|
144
|
-
deg =
|
145
|
+
deg = deg.normalize_lng
|
145
146
|
lon = to_dms deg, format, dp
|
146
147
|
lon == '' ? '' : lon + (deg<0 ? 'W' : 'E')
|
147
148
|
end
|