geo_calc 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Add dependencies to develop your gem here.
4
+ # Include everything needed to run rake, tests, features, etc.
5
+ group :development do
6
+ gem "rspec", ">= 2.5.0"
7
+ gem "bundler", "~> 1.0.0"
8
+ gem "jeweler", "~> 1.5.2"
9
+ gem "rcov", ">= 0"
10
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Kristian Mandrup
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.textile ADDED
@@ -0,0 +1,181 @@
1
+ h1. Geo calculations
2
+
3
+ Geo Calculation library. Useful functions to add to your geo arsenal, fx when designing your own Geo library.
4
+
5
+ h2. Install
6
+
7
+ @require 'geo_calc'@
8
+
9
+ h3. Gemfile
10
+
11
+ Insert in your Gemfile:
12
+
13
+ @gem 'geo_calc'@
14
+
15
+ From command line, run bundler
16
+
17
+ @$ bundle@
18
+
19
+ h2. Quick start (Usage)
20
+
21
+ First define the points on the globe you want to work with.
22
+ The GeoPoint initializer is very flexible with regards to the arguments it can handle.
23
+
24
+ <pre>
25
+ # factory method on core ruby classes
26
+ "51 12 03 N, 24 10 02 E".geo_point
27
+ [51.5136, -0.0983].geo_point
28
+ {:latitude => 27.3, :longitude => "24 10 02 E"}.geo_point
29
+
30
+ # two arguments
31
+ p1 = GeoPoint.new 51.5136, -0.0983
32
+ p2 = GeoPoint.new "14 11 01 N", "-0.0983"
33
+ p3 = GeoPoint.new 51.5136, "24 10 02 E"
34
+
35
+ # a String
36
+ p1 = GeoPoint.new "51.5136, -0.0983"
37
+ p1 = GeoPoint.new "51.5136, 24 10 02 E"
38
+ p3 = GeoPoint.new "51.4778, -0.0015"
39
+ p1 = GeoPoint.new "51 12 03 N, 24 10 02 E"
40
+
41
+ # an Array
42
+ p2 = GeoPoint.new [51.5136, -0.0983]
43
+ p2 = GeoPoint.new [51.5136, "24 10 02 E"]
44
+ p2 = GeoPoint.new [51.5136, {:lon => 27.3}]
45
+
46
+ # a Hash
47
+ p4 = GeoPoint.new {:lat => 27.3, :lng => "24 10 02 E"}
48
+ p4 = GeoPoint.new {:latitude => 27.3, :longitude => "24 10 02 E"}
49
+ </pre>
50
+
51
+ h3. Shortes distance
52
+
53
+ Calculate *distance in kms between points p1 and p2*
54
+
55
+ <pre>
56
+ dist = p1.distance_to(p2) # in km
57
+ </pre>
58
+
59
+ h3. Initial bearing (direction)
60
+
61
+ Calculate the initial bearing (direction in degrees which p1 points at p2)
62
+
63
+ <pre>
64
+ brng = p1.bearing_to(p2) # in degrees clockwise from north
65
+ </pre>
66
+
67
+ h3. Final bearing (direction)
68
+
69
+ Calculate the final bearing (direction in degrees) between p1 -> p2)
70
+
71
+ <pre>
72
+ final_brng = p1.final_bearing_to(p2) # final bearing in degrees from north
73
+ </pre>
74
+
75
+ h3. Midpoint
76
+
77
+ Find the midpoint between points p1 and p2
78
+
79
+ <pre>
80
+ mid = p1.midpoint_to point(p2) # midpoint between p1 and p2
81
+ </pre>
82
+
83
+ h3. Destination point
84
+
85
+ Find the destination point from walking a distance in a given bearing (direction in degrees from p1)
86
+
87
+ <pre>
88
+ dest = p1.destination_point bearing, dist # Bearing in degrees, Distance in km
89
+ </pre>
90
+
91
+ h3. Intersection point
92
+
93
+ Find the intersection point pcross between a path from p1 and a path from p2
94
+
95
+ <pre>
96
+ pcross = GeoPoint.intersection p1, brng1, p2, brng2 # intersection between two paths
97
+ </pre>
98
+
99
+ h2. Rhumb lines
100
+
101
+ <pre>
102
+ p1.rhumb_distance_to(p2)
103
+ </pre>
104
+
105
+ <pre>
106
+ p1.bearing_to(p2)
107
+ </pre>
108
+
109
+ <pre>
110
+ p1.rhumb_destination_point(brng, dist)
111
+ </pre>
112
+
113
+ h2. Utility methods
114
+
115
+ These are some of the utility methods you can use on a GeoPoint object
116
+
117
+ <pre>
118
+ p1 = GeoPoint.new 5.1, -7
119
+ p1.lat # latitude
120
+ p1.lon # longitude
121
+ p1.to_arr # array representation of [lat, lng]
122
+ p1.reverse_arr! # reverse to_arr to instead return [lng, lat]
123
+ p1.normal_arr! # return to normal to_arr functionality: [lat, lng]
124
+ p1.to_s # string representation
125
+ </pre>
126
+
127
+ h2. Core Extensions
128
+
129
+ The library also extends core Ruby classes with geo related functions (see _core_ext_spec.rb_)
130
+
131
+ h3. Radians to degrees
132
+
133
+ <pre>
134
+ (6.28).to_deg # almost 360 deg
135
+ </pre>
136
+
137
+ h3. Degrees to radians
138
+
139
+ <pre>
140
+ (360).to_deg # about 6.28, or 2 * PI
141
+ </pre>
142
+
143
+ h3. DMS to degrees
144
+
145
+ Convert (degrees-minutes-seconds) to degrees (Float)
146
+
147
+ <pre>
148
+ "24 10 02 E".parse_dms(:dm, 2) # dms format and precision
149
+ </pre>
150
+
151
+ h3. Degrees to DMS
152
+
153
+ Convert degrees into to DMS format
154
+
155
+ <pre>
156
+ 53.2.to_dms # can also take dms format and precision args
157
+ </pre>
158
+
159
+ And many more...
160
+
161
+ h2. Javascript
162
+
163
+ The libary also comes wih a javascript file with the same functionality.
164
+
165
+ See _js/geo_calc.js_ in the _/lib_ folder
166
+
167
+ h2. Contributing to geo_calc
168
+
169
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
170
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
171
+ * Fork the project
172
+ * Start a feature/bugfix branch
173
+ * Commit and push until you are happy with your contribution
174
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
175
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
176
+
177
+ h2. Copyright
178
+
179
+ Copyright (c) 2011 Kristian Mandrup. See LICENSE.txt for
180
+ further details.
181
+
data/Rakefile ADDED
@@ -0,0 +1,50 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'rake'
11
+
12
+ require 'jeweler'
13
+ Jeweler::Tasks.new do |gem|
14
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
15
+ gem.name = "geo_calc"
16
+ gem.homepage = "http://github.com/kristianmandrup/geo_calc"
17
+ gem.license = "MIT"
18
+ gem.summary = %Q{Geo calculation library}
19
+ gem.description = %Q{Geo calculations in ruby and javascript}
20
+ gem.email = "kmandrup@gmail.com"
21
+ gem.authors = ["Kristian Mandrup"]
22
+ # Include your dependencies below. Runtime dependencies are required when using your gem,
23
+ # and development dependencies are only needed for development (ie running rake tasks, tests, etc)
24
+ # gem.add_runtime_dependency 'jabber4r', '> 0.1'
25
+ # gem.add_development_dependency 'rspec', '> 1.2.3'
26
+ end
27
+ Jeweler::RubygemsDotOrgTasks.new
28
+
29
+ require 'rspec/core'
30
+ require 'rspec/core/rake_task'
31
+ RSpec::Core::RakeTask.new(:spec) do |spec|
32
+ spec.pattern = FileList['spec/**/*_spec.rb']
33
+ end
34
+
35
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
36
+ spec.pattern = 'spec/**/*_spec.rb'
37
+ spec.rcov = true
38
+ end
39
+
40
+ task :default => :spec
41
+
42
+ require 'rake/rdoctask'
43
+ Rake::RDocTask.new do |rdoc|
44
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
45
+
46
+ rdoc.rdoc_dir = 'rdoc'
47
+ rdoc.title = "geo_calc #{version}"
48
+ rdoc.rdoc_files.include('README*')
49
+ rdoc.rdoc_files.include('lib/**/*.rb')
50
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.5.1
data/geo_calc.gemspec ADDED
@@ -0,0 +1,74 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{geo_calc}
8
+ s.version = "0.5.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = [%q{Kristian Mandrup}]
12
+ s.date = %q{2011-05-13}
13
+ s.description = %q{Geo calculations in ruby and javascript}
14
+ s.email = %q{kmandrup@gmail.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE.txt",
17
+ "README.textile"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".rspec",
22
+ "Gemfile",
23
+ "LICENSE.txt",
24
+ "README.textile",
25
+ "Rakefile",
26
+ "VERSION",
27
+ "geo_calc.gemspec",
28
+ "lib/geo_calc.rb",
29
+ "lib/geo_calc/calculations.rb",
30
+ "lib/geo_calc/core_ext.rb",
31
+ "lib/geo_calc/geo.rb",
32
+ "lib/geo_calc/geo_point.rb",
33
+ "lib/geo_calc/js/geo_calc.js",
34
+ "spec/geo_calc/calculations_spec.rb",
35
+ "spec/geo_calc/core_ext_spec.rb",
36
+ "spec/geo_calc/geo_point_spec.rb",
37
+ "spec/geo_calc/geo_spec.rb",
38
+ "spec/spec_helper.rb"
39
+ ]
40
+ s.homepage = %q{http://github.com/kristianmandrup/geo_calc}
41
+ s.licenses = [%q{MIT}]
42
+ s.require_paths = [%q{lib}]
43
+ s.rubygems_version = %q{1.8.0}
44
+ 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
+
53
+ if s.respond_to? :specification_version then
54
+ s.specification_version = 3
55
+
56
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
57
+ s.add_development_dependency(%q<rspec>, [">= 2.5.0"])
58
+ s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
59
+ s.add_development_dependency(%q<jeweler>, ["~> 1.5.2"])
60
+ s.add_development_dependency(%q<rcov>, [">= 0"])
61
+ else
62
+ s.add_dependency(%q<rspec>, [">= 2.5.0"])
63
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
64
+ s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
65
+ s.add_dependency(%q<rcov>, [">= 0"])
66
+ end
67
+ else
68
+ s.add_dependency(%q<rspec>, [">= 2.5.0"])
69
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
70
+ s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
71
+ s.add_dependency(%q<rcov>, [">= 0"])
72
+ end
73
+ end
74
+
@@ -0,0 +1,333 @@
1
+ require 'geo_calc/geo'
2
+ require 'geo_calc/core_ext'
3
+
4
+ # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
5
+ # Latitude/longitude spherical geodesy formulae & scripts (c) Chris Veness 2002-2010
6
+ # - www.movable-type.co.uk/scripts/latlong.html
7
+ #
8
+
9
+ module GeoCalc
10
+ # Returns the distance from this point to the supplied point, in km
11
+ # (using Haversine formula)
12
+ #
13
+ # from: Haversine formula - R. W. Sinnott, "Virtues of the Haversine",
14
+ # Sky and Telescope, vol 68, no 2, 1984
15
+ #
16
+ # GeoPoint point: Latitude/longitude of destination point
17
+ # - Numeric precision=4: number of significant digits to use for returned value
18
+ #
19
+ # Returns - Numeric distance in km between this point and destination point
20
+
21
+ def distance_to point, precision = 4
22
+ # default 4 sig figs reflects typical 0.3% accuracy of spherical model
23
+ precision ||= 4
24
+
25
+ lat1 = lat.to_rad
26
+ lon1 = lon.to_rad
27
+
28
+ lat2 = point.lat.to_rad
29
+ lon2 = point.lon.to_rad
30
+
31
+ dlat = lat2 - lat1
32
+ dlon = lon2 - lon1
33
+
34
+ a = Math.sin(dlat/2) * Math.sin(dlat/2) + Math.cos(lat1) * Math.cos(lat2) * Math.sin(dlon/2) * Math.sin(dlon/2)
35
+ c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a))
36
+ d = radius * c
37
+ d.round(precision)
38
+ end
39
+
40
+
41
+ # Returns the (initial) bearing from this point to the supplied point, in degrees
42
+ # see http:#williams.best.vwh.net/avform.htm#Crs
43
+ #
44
+ # - Point point: Latitude/longitude of destination point
45
+ #
46
+ # Returns - Numeric: Initial bearing in degrees from North
47
+
48
+ def bearing_to point
49
+ lat1 = lat.to_rad
50
+ lat2 = point.lat.to_rad
51
+ dlon = (point.lon - lon).to_rad
52
+
53
+ y = Math.sin(dlon) * Math.cos(lat2)
54
+ x = Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1) * Math.cos(lat2) * Math.cos(dlon)
55
+ bearing = Math.atan2(y, x)
56
+
57
+ (bearing.to_deg + 360) % 360
58
+ end
59
+
60
+
61
+ # Returns final bearing arriving at supplied destination point from this point; the final bearing
62
+ # will differ from the initial bearing by varying degrees according to distance and latitude
63
+ #
64
+ # - GeoPoint point: Latitude/longitude of destination point
65
+ #
66
+ # Returns Numeric: Final bearing in degrees from North
67
+
68
+ def final_bearing_to point
69
+ # get initial bearing from supplied point back to this point...
70
+ lat1 = point.lat.to_rad
71
+ lat2 = lat.to_rad
72
+ dlon = (lon - point.lon).to_rad
73
+
74
+ y = Math.sin(dlon) * Math.cos(lat2)
75
+ x = Math.cos(lat1)*Math.sin(lat2) - Math.sin(lat1)*Math.cos(lat2)*Math.cos(dlon)
76
+ bearing = Math.atan2(y, x)
77
+
78
+ # ... & reverse it by adding 180°
79
+ (bearing.to_deg+180) % 360
80
+ end
81
+
82
+
83
+ # Returns the midpoint between this point and the supplied point.
84
+ # see http:#mathforum.org/library/drmath/view/51822.html for derivation
85
+ #
86
+ # - GeoPoint point: Latitude/longitude of destination point
87
+ # Returns GeoPoint: Midpoint between this point and the supplied point
88
+
89
+ def midpoint_to point
90
+ lat1 = lat.to_rad
91
+ lon1 = lon.to_rad;
92
+ lat2 = point.lat.to_rad
93
+ dlon = (point.lon - lon).to_rad
94
+
95
+ bx = Math.cos(lat2) * Math.cos(dlon)
96
+ by = Math.cos(lat2) * Math.sin(dlon)
97
+
98
+ lat3 = Math.atan2(Math.sin(lat1)+Math.sin(lat2), Math.sqrt( (Math.cos(lat1)+bx)*(Math.cos(lat1)+bx) + by*by) )
99
+
100
+ lon3 = lon1 + Math.atan2(by, Math.cos(lat1) + bx)
101
+
102
+ GeoPoint.new lat3.to_deg, lon3.to_deg
103
+ end
104
+
105
+
106
+ # Returns the destination point from this point having travelled the given distance (in km) on the
107
+ # given initial bearing (bearing may vary before destination is reached)
108
+ #
109
+ # see http:#williams.best.vwh.net/avform.htm#LL
110
+ #
111
+ # - Numeric bearing: Initial bearing in degrees
112
+ # - Numeric dist: Distance in km
113
+ # Returns GeoPoint: Destination point
114
+
115
+ def destination_point brng, dist
116
+ dist = dist / radius # convert dist to angular distance in radians
117
+ brng = brng.to_rad
118
+ lat1 = lat.to_rad
119
+ lon1 = lon.to_rad
120
+
121
+ lat2 = Math.asin( Math.sin(lat1) * Math.cos(dist) + Math.cos(lat1) * Math.sin(dist) * Math.cos(brng) )
122
+ lon2 = lon1 + Math.atan2(Math.sin(brng) * Math.sin(dist) * Math.cos(lat1), Math.cos(dist) - Math.sin(lat1) * Math.sin(lat2))
123
+
124
+ lon2 = (lon2 + 3*Math::PI) % (2*Math::PI) - Math::PI # normalise to -180...+180
125
+
126
+ GeoPoint.new lat2.to_deg, lon2.to_deg
127
+ end
128
+
129
+
130
+ # Returns the point of intersection of two paths defined by point and bearing
131
+ #
132
+ # see http:#williams.best.vwh.net/avform.htm#Intersection
133
+ #
134
+ # @param {LatLon} p1: First point
135
+ # @param {Number} brng1: Initial bearing from first point
136
+ # @param {LatLon} p2: Second point
137
+ # @param {Number} brng2: Initial bearing from second point
138
+ # @returns {LatLon} Destination point (null if no unique intersection defined)
139
+
140
+ def self.intersection p1, brng1, p2, brng2
141
+ lat1 = p1.lat.to_rad
142
+ lon1 = p1.lon.to_rad
143
+
144
+ lat2 = p2.lat.to_rad
145
+ lon2 = p2.lon.to_rad
146
+
147
+ brng13 = brng1.to_rad
148
+ brng23 = brng2.to_rad
149
+
150
+ dlat = lat2-lat1
151
+ dlon = lon2-lon1;
152
+
153
+ 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) ) )
154
+ return nil if dist12 == 0
155
+
156
+ # initial/final bearings between points
157
+ brng_a = begin
158
+ Math.acos( ( Math.sin(lat2) - Math.sin(lat1)*Math.cos(dist12) ) / ( Math.sin(dist12)*Math.cos(lat1) ) )
159
+ rescue # protect against rounding
160
+ 0
161
+ end
162
+
163
+ brng_b = Math.acos( ( Math.sin(lat1) - Math.sin(lat2)*Math.cos(dist12) ) / ( Math.sin(dist12)*Math.cos(lat2) ) )
164
+
165
+ brng12, brng21 = if Math.sin(lon2-lon1) > 0
166
+ [brng_a, 2*Math::PI - brng_b]
167
+ else
168
+ [2*Math::PI - brng_a, brng_b]
169
+ end
170
+
171
+ alpha1 = (brng13 - brng12 + Math::PI) % (2*Math::PI) - Math::PI # angle 2-1-3
172
+ alpha2 = (brng21 - brng23 + Math::PI) % (2*Math::PI) - Math::PI # angle 1-2-3
173
+
174
+ return nil if (Math.sin(alpha1)==0 && Math.sin(alpha2)==0) # infinite intersections
175
+ return nil if (Math.sin(alpha1)*Math.sin(alpha2) < 0) # ambiguous intersection
176
+
177
+ # alpha1 = Math.abs(alpha1);
178
+ # alpha2 = Math.abs(alpha2);
179
+ # ... Ed Williams takes abs of alpha1/alpha2, but seems to break calculation?
180
+
181
+ alpha3 = Math.acos( -Math.cos(alpha1)*Math.cos(alpha2) + Math.sin(alpha1)*Math.sin(alpha2)*Math.cos(dist12) )
182
+
183
+ dist13 = Math.atan2( Math.sin(dist12)*Math.sin(alpha1)*Math.sin(alpha2), Math.cos(alpha2)+Math.cos(alpha1)*Math.cos(alpha3) )
184
+
185
+ lat3 = Math.asin( Math.sin(lat1)*Math.cos(dist13) + Math.cos(lat1)*Math.sin(dist13)*Math.cos(brng13) )
186
+
187
+ dlon13 = Math.atan2( Math.sin(brng13)*Math.sin(dist13)*Math.cos(lat1), Math.cos(dist13)-Math.sin(lat1)*Math.sin(lat3) )
188
+
189
+ lon3 = lon1 + dlon13;
190
+ lon3 = (lon3 + Math::PI) % (2*Math::PI) - Math::PI # normalise to -180..180º
191
+
192
+ GeoPoint.new lat3.to_deg, lon3.to_deg
193
+ end
194
+
195
+
196
+ # Returns the distance from this point to the supplied point, in km, travelling along a rhumb line
197
+ #
198
+ # see http:#williams.best.vwh.net/avform.htm#Rhumb
199
+ #
200
+ # - GeoPoint point: Latitude/longitude of destination point
201
+ # Returns Numeric: Distance in km between this point and destination point
202
+
203
+ def rhumb_distance_to point
204
+ lat1 = lat.to_rad
205
+ lat2 = point.lat.to_rad
206
+
207
+ dlat = (point.lat-lat).to_rad
208
+ dlon = (point.lon-lon).abs.to_rad
209
+
210
+ dphi = Math.log(Math.tan(lat2/2 + Math::PI/4) / Math.tan(lat1/2 + Math::PI/4))
211
+
212
+ q = begin
213
+ dlat / dphi
214
+ rescue
215
+ Math.cos(lat1) # E-W line gives dPhi=0
216
+ end
217
+
218
+ # if dlon over 180° take shorter rhumb across 180° meridian:
219
+ dlon = 2*Math::PI - dlon if (dlon > Math::PI)
220
+
221
+ dist = Math.sqrt(dlat*dlat + q*q*dlon*dlon) * radius;
222
+
223
+ dist.round(4) # 4 sig figures reflects typical 0.3% accuracy of spherical model
224
+ end
225
+
226
+
227
+ # Returns the bearing from this point to the supplied point along a rhumb line, in degrees
228
+ #
229
+ # - GeoPoint point: Latitude/longitude of destination point
230
+ # Returns Numeric: Bearing in degrees from North
231
+
232
+ def rhumb_bearing_to point
233
+ lat1 = lat.to_rad
234
+ lat2 = point.lat.to_rad
235
+
236
+ dlon = (point.lon - lon).to_rad
237
+
238
+ dphi = Math.log(Math.tan(lat2/2+Math::PI/4) / Math.tan(lat1/2+Math::PI/4))
239
+ if dlon.abs > Math::PI
240
+ dlon = dlon>0 ? -(2*Math::PI-dlon) : (2*Math::PI+dlon);
241
+ end
242
+
243
+ brng = Math.atan2(dlon, dphi);
244
+
245
+ (brng.to_deg+360) % 360
246
+ end
247
+
248
+
249
+ # Returns the destination point from this point having travelled the given distance (in km) on the
250
+ # given bearing along a rhumb line
251
+ #
252
+ # @param {Number} brng: Bearing in degrees from North
253
+ # @param {Number} dist: Distance in km
254
+ # @returns {LatLon} Destination point
255
+
256
+ def rhumb_destination_point brng, dist
257
+ d = dist / radius # d = angular distance covered on earth's surface
258
+ lat1 = lat.to_rad
259
+ lon1 = lon.to_rad
260
+ brng = brng.to_rad
261
+
262
+ lat2 = lat1 + d*Math.cos(brng);
263
+ dlat = lat2-lat1;
264
+ dphi = Math.log(Math.tan(lat2/2+Math::PI/4)/Math.tan(lat1/2+Math::PI/4))
265
+
266
+ q = begin
267
+ dlat/dphi
268
+ rescue
269
+ Math.cos(lat1) # E-W line gives dPhi=0
270
+ end
271
+
272
+ dlon = d*Math.sin(brng)/q
273
+ # check for some daft bugger going past the pole
274
+
275
+ if lat2.abs > Math::PI/2
276
+ lat2 = lat2>0 ? Math::PI-lat2 : -(Math::PI-lat2)
277
+ end
278
+ lon2 = (lon1+dlon+3*Math::PI) % (2*Math::PI) - Math::PI
279
+
280
+ GeoPoint.new lat2.to_deg, lon2.to_deg
281
+ end
282
+
283
+
284
+ # Returns the latitude of this point; signed numeric degrees if no format, otherwise format & dp
285
+ # as per Geo.to_lat
286
+ #
287
+ # - String [format]: Return value as 'd', 'dm', 'dms'
288
+ # - Numeric [dp=0|2|4]: No of decimal places to display
289
+ #
290
+ # Returns {Numeric|String}: Numeric degrees if no format specified, otherwise deg/min/sec
291
+ #
292
+
293
+ def to_lat format = :dms, dp = 0
294
+ return lat if !format
295
+ Geo.to_lat lat, format, dp
296
+ end
297
+
298
+
299
+ # Returns the longitude of this point; signed numeric degrees if no format, otherwise format & dp
300
+ # as per Geo.toLon()
301
+ #
302
+ # @param {String} [format]: Return value as 'd', 'dm', 'dms'
303
+ # @param {Number} [dp=0|2|4]: No of decimal places to display
304
+ # @returns {Number|String} Numeric degrees if no format specified, otherwise deg/min/sec
305
+ #
306
+ # @requires Geo
307
+
308
+ def to_lon format, dp
309
+ return lon if !format
310
+ Geo.to_lon lon, format, dp
311
+ end
312
+
313
+
314
+ # Returns a string representation of this point; format and dp as per lat()/lon()
315
+ #
316
+ # @param {String} [format]: Return value as 'd', 'dm', 'dms'
317
+ # @param {Number} [dp=0|2|4]: No of decimal places to display
318
+
319
+ # @returns {String} Comma-separated latitude/longitude
320
+ #
321
+
322
+ def to_s format = :dms, dp = 0
323
+ format ||= :dms
324
+
325
+ return '-,-' if !lat || !lon
326
+
327
+ _lat = Geo.to_lat lat, format, dp
328
+ _lon = Geo.to_lon lon, format, dp
329
+
330
+ "#{_lat}, #{_lon}"
331
+ end
332
+ end
333
+