geo_calc 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.rspec +1 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +20 -0
- data/README.textile +181 -0
- data/Rakefile +50 -0
- data/VERSION +1 -0
- data/geo_calc.gemspec +74 -0
- data/lib/geo_calc/calculations.rb +333 -0
- data/lib/geo_calc/core_ext.rb +228 -0
- data/lib/geo_calc/geo.rb +170 -0
- data/lib/geo_calc/geo_point.rb +103 -0
- data/lib/geo_calc/js/geo_calc.js +551 -0
- data/lib/geo_calc.rb +1 -0
- data/spec/geo_calc/calculations_spec.rb +174 -0
- data/spec/geo_calc/core_ext_spec.rb +272 -0
- data/spec/geo_calc/geo_point_spec.rb +228 -0
- data/spec/geo_calc/geo_spec.rb +99 -0
- data/spec/spec_helper.rb +12 -0
- metadata +118 -0
data/.document
ADDED
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
|
+
|