vector_geometry 0.0.1
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/COPYING +20 -0
- data/Gemfile +9 -0
- data/README.md +153 -0
- data/Rakefile +47 -0
- data/VERSION +1 -0
- data/lib/geometry/geometry.rb +47 -0
- data/lib/geometry/spheroid/base.rb +106 -0
- data/lib/geometry/spheroid/sphere.rb +29 -0
- data/lib/geometry/vector/earth_vector.rb +7 -0
- data/lib/geometry/vector/geo_vector.rb +234 -0
- data/lib/geometry/vector/vector.rb +223 -0
- data/lib/vector_geometry.rb +6 -0
- data/spec/exercises_spec.rb +210 -0
- data/spec/geo_vector_spec.rb +38 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/vector_spec.rb +274 -0
- data/vector_geometry.gemspec +61 -0
- metadata +114 -0
data/COPYING
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2012 by Andrew Berkeley (andrew.berkeley.is@googlemail.com)
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
20
|
+
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,153 @@
|
|
1
|
+
Vector geometry
|
2
|
+
========
|
3
|
+
|
4
|
+
Author: Andrew Berkeley (andrew.berkeley.is@googlemail.com)
|
5
|
+
|
6
|
+
Introduction
|
7
|
+
------------
|
8
|
+
This library provides an interface for handling vector geometry in 2- and 3-dimensional space.
|
9
|
+
|
10
|
+
The following classes are defined
|
11
|
+
|
12
|
+
<table>
|
13
|
+
<tr>
|
14
|
+
<td>Geometry::Vector</td>
|
15
|
+
<td> Basic 2- or 3D vector and operations</td>
|
16
|
+
</tr>
|
17
|
+
<tr>
|
18
|
+
<td>Geometry::GeoVector</td>
|
19
|
+
<td>Subclass of Vector providing additional geometric operations relating to the surface of a spheroid</td>
|
20
|
+
</tr>
|
21
|
+
<tr>
|
22
|
+
<td>Geometry::EarthVector</td>
|
23
|
+
<td>Subclass of GeoVector relating specifically to Earth</td>
|
24
|
+
</tr>
|
25
|
+
<tr>
|
26
|
+
<td>Geometry::Spheroid::Base</td>
|
27
|
+
<td>Create a representation of an arbitrary spheriod for associating with a given instance of GeoVector</td>
|
28
|
+
</tr>
|
29
|
+
</table>
|
30
|
+
|
31
|
+
For example, calculating the distance between two points on the Earth's surface
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
# Instantiate using geographic coordinates.
|
35
|
+
vector_1 = Geometry::EarthVector.from_geographic(80.0,0.0) #=> <EarthVector [1111.1648732245053, 0.0, 6259.542946575613]>
|
36
|
+
vector_2 = Geometry::EarthVector.from_geographic(80.0,1.0) #=> <EarthVector [1110.9956374322653, 19.392500986346672, 6259.542946575613]>
|
37
|
+
|
38
|
+
# Distance across 1 degree longitude at latitude of 80 degrees (km)
|
39
|
+
vector_1.great_circle_distance(vector_2) #=> 19.43475314292549
|
40
|
+
```
|
41
|
+
|
42
|
+
Or the distance of point from a line described by two points
|
43
|
+
```ruby
|
44
|
+
vector_3 = Geometry::EarthVector.from_geographic(75.0,5.0) #=> <EarthVector [1649.6615168294907, 144.3266813775607, 6138.7656676742445]>
|
45
|
+
|
46
|
+
vector_3.great_circle_distance_from_arc(vector_1,vector_2) #=> 567.3634356328112
|
47
|
+
|
48
|
+
```
|
49
|
+
|
50
|
+
Vector
|
51
|
+
------
|
52
|
+
|
53
|
+
Basic vector operations can be performed as follows.
|
54
|
+
|
55
|
+
Initialize a vector.
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
# 2D - pass x, y
|
59
|
+
vector = Geometry::Vector.new(15,20)
|
60
|
+
|
61
|
+
# 3D - pass x, y, z
|
62
|
+
vector = Geometry::Vector.new(15,20,4)
|
63
|
+
|
64
|
+
# 2D - pass magnitude and angle
|
65
|
+
vector = Geometry::Vector.from_polar(25, Math::PI)
|
66
|
+
```
|
67
|
+
|
68
|
+
Vector attributes.
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
vector = Geometry::Vector.new(15,20,4)
|
72
|
+
|
73
|
+
vector.x #=> 15
|
74
|
+
vector.y #=> 20
|
75
|
+
vector.z #=> 4
|
76
|
+
|
77
|
+
```
|
78
|
+
|
79
|
+
Vector operations.
|
80
|
+
|
81
|
+
```ruby
|
82
|
+
|
83
|
+
vector = Geometry::Vector.new(3,4,5)
|
84
|
+
|
85
|
+
# Magnitude
|
86
|
+
vector.magnitude #=> 7.0710678118654755
|
87
|
+
|
88
|
+
# Normalize
|
89
|
+
vector.normalize #=> <Vector [0.4242640687119285, 0.565685424949238, 0.7071067811865475]>
|
90
|
+
|
91
|
+
vector_1 = Geometry::Vector.new(2,2,1)
|
92
|
+
vector_2 = Geometry::Vector.new(2,3,10)
|
93
|
+
|
94
|
+
# Addition
|
95
|
+
vector_1 + vector_2 #=> <Vector [4.0, 5.0, 11.0]>
|
96
|
+
|
97
|
+
# Subtraction
|
98
|
+
vector_1 - vector_2 #=> <Vector [0.0, -1.0, -9.0]>
|
99
|
+
|
100
|
+
# Multiply by scalar value
|
101
|
+
vector_1 * 4 #=> <Vector [8.0, 8.0, 4.0]>
|
102
|
+
|
103
|
+
# Divide by scalar value
|
104
|
+
vector * 4 #=> <Vector [0.5, 0.5, 0.25]>
|
105
|
+
|
106
|
+
# Calculate dot product of 2 vectors
|
107
|
+
vector_1.dot(vector_2) #=> 20.0
|
108
|
+
|
109
|
+
# Calculate cross product of 2 vectors
|
110
|
+
vector_1.cross(vector_2) #=> <Vector [17.0, -18.0, 2.0]>
|
111
|
+
|
112
|
+
# Calculate angle between 2 vectors (in radians)
|
113
|
+
vector_1.angle(vector_2) #=> 0.8929110789963546
|
114
|
+
|
115
|
+
```
|
116
|
+
|
117
|
+
Compare vectors.
|
118
|
+
|
119
|
+
```ruby
|
120
|
+
|
121
|
+
# Are two vectors parallel or orthogonal?
|
122
|
+
vector_1 = Geometry::Vector.new(10,0,0)
|
123
|
+
vector_2 = Geometry::Vector.new(20,0,0)
|
124
|
+
vector_3 = Geometry::Vector.new(0,10,0)
|
125
|
+
|
126
|
+
vector_1.parallel?(vector_2) #=> true
|
127
|
+
vector_1.parallel?(vector_3) #=> false
|
128
|
+
|
129
|
+
vector_1.orthogonal?(vector_2) #=> false
|
130
|
+
vector_1.orthogonal?(vector_3) #=> true
|
131
|
+
|
132
|
+
# Are two vectors equal
|
133
|
+
vector_1 == vector_2 #=> false
|
134
|
+
```
|
135
|
+
|
136
|
+
Contributing
|
137
|
+
------------
|
138
|
+
|
139
|
+
If you find a bug or think that you improve on the code, feel free to contribute.
|
140
|
+
|
141
|
+
You can:
|
142
|
+
|
143
|
+
* Send the author a message ("andrew.berkeley.is@googlemail.com":mailto:andrew.berkeley.is@googlemail.com)
|
144
|
+
* Create an issue
|
145
|
+
* Fork the project and submit a pull request.
|
146
|
+
|
147
|
+
|
148
|
+
License
|
149
|
+
-------
|
150
|
+
|
151
|
+
© Copyright 2012 Andrew Berkeley.
|
152
|
+
|
153
|
+
Licensed under the MIT license (See COPYING file for details)
|
data/Rakefile
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
|
6
|
+
begin
|
7
|
+
Bundler.setup(:default, :development)
|
8
|
+
rescue Bundler::BundlerError => e
|
9
|
+
$stderr.puts e.message
|
10
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
11
|
+
exit e.status_code
|
12
|
+
end
|
13
|
+
|
14
|
+
require 'rake'
|
15
|
+
require 'rspec'
|
16
|
+
require 'rspec/core/rake_task'
|
17
|
+
|
18
|
+
task :default => [:spec]
|
19
|
+
|
20
|
+
desc "Run specs"
|
21
|
+
RSpec::Core::RakeTask.new do |t|
|
22
|
+
# Put spec opts in a file named .rspec in root
|
23
|
+
end
|
24
|
+
|
25
|
+
require 'jeweler'
|
26
|
+
|
27
|
+
Jeweler::Tasks.new do |gem|
|
28
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
29
|
+
gem.name = "vector_geometry"
|
30
|
+
gem.homepage = "https://github.com/spatchcock/vector_geometry"
|
31
|
+
gem.license = "GNU Affero General Public License"
|
32
|
+
gem.summary = %Q{Cartesian and spherical geometry using vectors}
|
33
|
+
gem.description = %Q{Cartesian and spherical geometry using vectors}
|
34
|
+
gem.email = "andrew.berkeley.is@googlemail.com"
|
35
|
+
gem.authors = ["Andrew Berkeley"]
|
36
|
+
# dependencies defined in Gemfile
|
37
|
+
end
|
38
|
+
|
39
|
+
Jeweler::RubygemsDotOrgTasks.new
|
40
|
+
|
41
|
+
require 'rdoc/task'
|
42
|
+
RDoc::Task.new do |rd|
|
43
|
+
rd.title = "Vector Geometry"
|
44
|
+
rd.rdoc_dir = 'doc'
|
45
|
+
rd.main = "README"
|
46
|
+
rd.rdoc_files.include("README", "lib/**/*.rb")
|
47
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Geometry
|
2
|
+
|
3
|
+
def self.deg_to_rad(deg)
|
4
|
+
deg * Math::PI / 180.0
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.rad_to_deg(rad)
|
8
|
+
(rad * 180.0) / Math::PI
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.haversine_distance(point_1,point_2,radius, options = {})
|
12
|
+
lat_1 = point_1[0]
|
13
|
+
lng_1 = point_1[1]
|
14
|
+
lat_2 = point_2[0]
|
15
|
+
lng_2 = point_2[1]
|
16
|
+
|
17
|
+
if options[:unit] = :deg
|
18
|
+
lat_1 = deg_to_rad(lat_1)
|
19
|
+
lng_1 = deg_to_rad(lng_1)
|
20
|
+
lat_2 = deg_to_rad(lat_2)
|
21
|
+
lng_2 = deg_to_rad(lng_2)
|
22
|
+
end
|
23
|
+
|
24
|
+
lat_diff = 0.5 * (lat_2 - lat_1);
|
25
|
+
lat_diff = Math.sin(lat_diff);
|
26
|
+
lat_diff = lat_diff * lat_diff;
|
27
|
+
|
28
|
+
lng_diff = 0.5 * (lng_2 - lng_1);
|
29
|
+
lng_diff = Math.sin(lng_diff);
|
30
|
+
lng_diff = lng_diff * lng_diff;
|
31
|
+
|
32
|
+
result = lat_diff;
|
33
|
+
result += Math.cos(lat_1) * Math.cos(lat_2) * lng_diff;
|
34
|
+
result = Math.sqrt(result);
|
35
|
+
|
36
|
+
return radius * 2 * Math.asin(result);
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.pythagorean_distance(point_1, point_2)
|
41
|
+
x = point_2[0] - point_1[0]
|
42
|
+
y = point_2[1] - point_1[1]
|
43
|
+
|
44
|
+
Math.sqrt(x * x + y * y).abs
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
module Geometry
|
2
|
+
|
3
|
+
module Spheroid
|
4
|
+
|
5
|
+
# Ellipsoid - 3D analogue of an ellipse. Has 3 axis: a,b and c
|
6
|
+
# Spheroid - Ellipsoid with two equal semi-diameters (a = b)
|
7
|
+
# Sphere - Ellipsoid with three equal semi-diameters (a = b = c)
|
8
|
+
# Geoid -
|
9
|
+
|
10
|
+
# class Ellipsoid
|
11
|
+
|
12
|
+
# def initialize(a,b,c)
|
13
|
+
# @a_radius = a
|
14
|
+
# @b_radius = b
|
15
|
+
# @c_radius = c
|
16
|
+
# end
|
17
|
+
|
18
|
+
# def volume
|
19
|
+
# (4.0/3.0) * (Math::PI * a_radius * b_radius * c_radius)
|
20
|
+
# end
|
21
|
+
|
22
|
+
# end
|
23
|
+
|
24
|
+
class Base
|
25
|
+
|
26
|
+
attr_accessor :polar_radius # semi-minor axis
|
27
|
+
attr_accessor :equatorial_radius # semi-major axis
|
28
|
+
attr_accessor :unit
|
29
|
+
|
30
|
+
# Unit is not functional but is simply memoized so that the basis for any calculations
|
31
|
+
# is clear
|
32
|
+
def initialize(equatorial_radius, polar_radius, unit = :km)
|
33
|
+
@polar_radius = polar_radius
|
34
|
+
@equatorial_radius = equatorial_radius
|
35
|
+
@unit = unit
|
36
|
+
end
|
37
|
+
|
38
|
+
def mean_radius
|
39
|
+
# http://en.wikipedia.org/wiki/Earth_radius
|
40
|
+
@mean_radius ||= (@polar_radius + 2 * @equatorial_radius) / 3.0
|
41
|
+
end
|
42
|
+
|
43
|
+
def flattening
|
44
|
+
# http://en.wikipedia.org/wiki/Reference_ellipsoid
|
45
|
+
@flattening ||= (@equatorial_radius - @polar_radius) / @equatorial_radius
|
46
|
+
end
|
47
|
+
|
48
|
+
def inverse_flattening
|
49
|
+
@inverse_flattening ||= 1.0 / flattening
|
50
|
+
end
|
51
|
+
|
52
|
+
def flattening_complement_squared
|
53
|
+
# http://www.mathworks.co.uk/help/aeroblks/geodetictogeocentriclatitude.html
|
54
|
+
@flattening_complement_squared ||= (1.0 - flattening) * (1.0 - flattening)
|
55
|
+
end
|
56
|
+
|
57
|
+
def volume
|
58
|
+
@volume ||= (4.0 / 3.0) * (Math::PI * @polar_radius * @equatorial_radius ** 2)
|
59
|
+
end
|
60
|
+
|
61
|
+
# http://en.wikipedia.org/wiki/Earth_radius
|
62
|
+
def radius_at_geodetic_latitude(lat, options = {})
|
63
|
+
lat = Geometry.deg_to_rad(lat) unless options[:unit] == :radians
|
64
|
+
|
65
|
+
numerator = (@equatorial_radius**2 * Math.cos(lat))**2 + (@polar_radius**2 * Math.sin(lat))**2
|
66
|
+
denominator = (@equatorial_radius * Math.cos(lat))**2 + (@polar_radius * Math.sin(lat))**2
|
67
|
+
|
68
|
+
Math.sqrt(numerator/denominator)
|
69
|
+
end
|
70
|
+
|
71
|
+
def geodetic_to_geocentric_latitude(lat, options = {})
|
72
|
+
lat = Geometry.deg_to_rad(lat) unless options[:unit] == :radians
|
73
|
+
|
74
|
+
Math.atan(Math.tan(lat) * flattening_complement_squared)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Invoke the haversine formula in the context of the spheroid represented by self
|
78
|
+
def haversine_distance(point_1, point_2, options = {})
|
79
|
+
Geometry.haversine_distance(point_1, point_2, mean_radius, options)
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
Mercury = Geometry::Spheroid::Base.new(2439.7, 2439.7)
|
85
|
+
|
86
|
+
Venus = Geometry::Spheroid::Base.new(6051.8, 6051.8)
|
87
|
+
|
88
|
+
Earth = Geometry::Spheroid::Base.new(6378.1370, 6356.7523)
|
89
|
+
|
90
|
+
Moon = Geometry::Spheroid::Base.new(1738.1, 1736.0)
|
91
|
+
|
92
|
+
Mars = Geometry::Spheroid::Base.new(3396.2, 3376.2)
|
93
|
+
|
94
|
+
Jupiter = Geometry::Spheroid::Base.new(71492, 66854)
|
95
|
+
|
96
|
+
Saturn = Geometry::Spheroid::Base.new(60268, 54364)
|
97
|
+
|
98
|
+
Uranus = Geometry::Spheroid::Base.new(25559, 24973)
|
99
|
+
|
100
|
+
Neptune = Geometry::Spheroid::Base.new(24764, 24341)
|
101
|
+
|
102
|
+
Pluto = Geometry::Spheroid::Base.new(1195, 1195)
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Geometry
|
2
|
+
|
3
|
+
module Spheroid
|
4
|
+
|
5
|
+
class Sphere < Spheroid::Base
|
6
|
+
|
7
|
+
def initialize(radius, unit = :km)
|
8
|
+
super(radius,radius,unit)
|
9
|
+
end
|
10
|
+
|
11
|
+
def radius
|
12
|
+
@equatorial_radius
|
13
|
+
end
|
14
|
+
|
15
|
+
def diameter
|
16
|
+
@diameter ||= radius * 2.0
|
17
|
+
end
|
18
|
+
|
19
|
+
def circumference
|
20
|
+
@circumference ||= 2.0 * Math::PI * radius
|
21
|
+
end
|
22
|
+
|
23
|
+
def surface_area
|
24
|
+
@surface_area ||= 4.0 * (Math::PI * radius ** 2)
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,234 @@
|
|
1
|
+
module Geometry
|
2
|
+
|
3
|
+
class GeoVector < Vector
|
4
|
+
|
5
|
+
@@spheroid = nil
|
6
|
+
|
7
|
+
def self.from_great_circle_intersection(vector_1,vector_2,vector_3,vector_4)
|
8
|
+
normal_to_great_circle_1 = vector_1.cross_normal(vector_2)
|
9
|
+
normal_to_great_circle_2 = vector_3.cross_normal(vector_4)
|
10
|
+
|
11
|
+
unit_vector = normal_to_great_circle_1.cross_normal(normal_to_great_circle_2)
|
12
|
+
|
13
|
+
if @@spheroid
|
14
|
+
unit_vector.scale(@@spheroid.mean_radius)
|
15
|
+
else
|
16
|
+
unit_vector
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Return a 3-dimensional cartesian vector representing the given latitude and longitude.
|
21
|
+
def self.from_geographic(lat, lng, options = {})
|
22
|
+
|
23
|
+
spheroid = @@spheroid || options[:spheroid]
|
24
|
+
|
25
|
+
unless options[:unit] == :radians
|
26
|
+
lat = Geometry.deg_to_rad(lat)
|
27
|
+
lng = Geometry.deg_to_rad(lng)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Convert the geodetic latitude to geocentric if required
|
31
|
+
geocentric_latitude = options[:geocentric] ? lat : spheroid.geodetic_to_geocentric_latitude(lat, :unit => :radians)
|
32
|
+
|
33
|
+
# Find the projection of the point on the equatorial plane.
|
34
|
+
equatorial_plane_projection = Math.cos(geocentric_latitude)
|
35
|
+
|
36
|
+
x = Math.cos(lng) * equatorial_plane_projection
|
37
|
+
y = Math.sin(lng) * equatorial_plane_projection
|
38
|
+
z = Math.sin(geocentric_latitude)
|
39
|
+
|
40
|
+
unit_vector = self.new(x,y,z)
|
41
|
+
|
42
|
+
if options[:unit_vector]
|
43
|
+
unit_vector
|
44
|
+
else
|
45
|
+
raise ArgumentError, "No spheroid defined" unless spheroid
|
46
|
+
|
47
|
+
geodetic_radius = spheroid.radius_at_geodetic_latitude(lat, :unit => :radians)
|
48
|
+
unit_vector.scale(geodetic_radius)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
attr_accessor :geodetic_radius
|
53
|
+
attr_accessor :latitude
|
54
|
+
attr_accessor :longitude
|
55
|
+
|
56
|
+
def latitude(options = {})
|
57
|
+
@latitude ||= begin
|
58
|
+
lat = Math.atan2(@z,@x)
|
59
|
+
|
60
|
+
lat = Math::PI - lat if lat > Math::PI/2.0
|
61
|
+
lat = lat.abs if lat == -0.0
|
62
|
+
|
63
|
+
lat
|
64
|
+
end
|
65
|
+
|
66
|
+
options[:unit] == :radians ? @latitude : Geometry.rad_to_deg(@latitude)
|
67
|
+
end
|
68
|
+
|
69
|
+
def longitude(options = {})
|
70
|
+
@longitude ||= Math.atan2(@y,@x)
|
71
|
+
|
72
|
+
options[:unit] == :deg ? Geometry.rad_to_deg(@longitude) : @longitude
|
73
|
+
end
|
74
|
+
|
75
|
+
def geodetic_radius(spheroid = @@spheroid)
|
76
|
+
@geodetic_radius ||= spheroid.radius_at_geodetic_latitude(latitude)
|
77
|
+
end
|
78
|
+
|
79
|
+
def antipode
|
80
|
+
scale(-1.0)
|
81
|
+
end
|
82
|
+
|
83
|
+
def mean_geodetic_radius(other)
|
84
|
+
(self.geodetic_radius + other.geodetic_radius) / 2.0
|
85
|
+
end
|
86
|
+
|
87
|
+
def great_circle_distance(other, options = {})
|
88
|
+
|
89
|
+
angular_distance = angle(other)
|
90
|
+
|
91
|
+
if @@spheroid && !options[:unit_vector]
|
92
|
+
if options[:use_mean_geodetic_radius]
|
93
|
+
return angular_distance * mean_geodetic_radius(other)
|
94
|
+
else
|
95
|
+
return angular_distance * @@spheroid.mean_radius
|
96
|
+
end
|
97
|
+
else
|
98
|
+
angular_distance
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Calculate the distance of self from a great circle defined by two points.
|
103
|
+
def great_circle_distance_from_great_circle(point_a,point_b)
|
104
|
+
|
105
|
+
# The shortest distance from a great circle is the perpendicular distance.
|
106
|
+
|
107
|
+
# Find the vector which is normal to the plane described by the (curved) line together
|
108
|
+
# with the origin.
|
109
|
+
|
110
|
+
normal_to_line = point_a.cross_normal(point_b).scale(@@spheroid.mean_radius)
|
111
|
+
|
112
|
+
# The line between self and the normal vector is perpendicular to the line.
|
113
|
+
#
|
114
|
+
# Next, find the intersection between the two lines which represents the shortest distance
|
115
|
+
# from self to the line.
|
116
|
+
|
117
|
+
intersection = GeoVector.from_great_circle_intersection(point_a, point_b, self, normal_to_line)
|
118
|
+
|
119
|
+
# There are actually two valid intersections (which are antipodes of one another) and we have
|
120
|
+
# found one.
|
121
|
+
#
|
122
|
+
# Return the smallest distance from self to either intersection
|
123
|
+
|
124
|
+
[self.great_circle_distance(intersection), self.great_circle_distance(intersection.antipode)].min
|
125
|
+
end
|
126
|
+
|
127
|
+
# Calculate the distance of self from a finite line segment defined by two points
|
128
|
+
def great_circle_distance_from_arc(point_a,point_b, options = {})
|
129
|
+
|
130
|
+
# Distance from a line segment is similar to the distance from the infinte line (above)
|
131
|
+
# with a modification.
|
132
|
+
#
|
133
|
+
# The distance from an infinitely long line calculates the shortest distance to an infintitely
|
134
|
+
# long line. This is the perpendicular distance. In the case of the line segment, this perpendicular
|
135
|
+
# may or may not fall on the line segment part of the more general infinte line.
|
136
|
+
#
|
137
|
+
# If it does, we can keep the perpendicular distance. If not, the shortest distance will be
|
138
|
+
# to either of the two line segments end. Determine which.
|
139
|
+
|
140
|
+
normal_to_line = point_a.cross_normal(point_b).scale(@@spheroid.mean_radius)
|
141
|
+
intersection = GeoVector.from_great_circle_intersection(point_a, point_b, self, normal_to_line)
|
142
|
+
|
143
|
+
# The point which intersects the two great circles is actually one of two such unique points. We
|
144
|
+
# know both fall on the great circle described by the points but we need to establish whether either
|
145
|
+
# fall on our line segment, i.e. between them. If so, we can take the perpendicular distance from
|
146
|
+
# the intersection point.
|
147
|
+
|
148
|
+
line_length = point_a.great_circle_distance(point_b, options)
|
149
|
+
|
150
|
+
if line_length >= intersection.great_circle_distance(point_a, options) &&
|
151
|
+
line_length >= intersection.great_circle_distance(point_b, options)
|
152
|
+
|
153
|
+
# The intersection falls on the line segment.
|
154
|
+
# Calculate the perpendicular distance.
|
155
|
+
|
156
|
+
return self.great_circle_distance(intersection, options)
|
157
|
+
|
158
|
+
elsif line_length >= intersection.antipode.great_circle_distance(point_a, options) &&
|
159
|
+
line_length >= intersection.antipode.great_circle_distance(point_b, options)
|
160
|
+
|
161
|
+
# The *other* intersection falls on the line segment.
|
162
|
+
# Calculate the perpendicular distance.
|
163
|
+
|
164
|
+
return self.great_circle_distance(intersection.antipode, options)
|
165
|
+
|
166
|
+
else
|
167
|
+
|
168
|
+
# Neither intersection falls on the line segment.
|
169
|
+
# Calculate the distance to the closer of the line segment ends.
|
170
|
+
|
171
|
+
return [ self.great_circle_distance(point_a, options), self.great_circle_distance(point_b, options) ].min
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# Supports a polyline based on either cartesian or angular coordinates. Specify which using the :basis
|
176
|
+
# option
|
177
|
+
#
|
178
|
+
# :cartesian => cartesian coordinates (x,y)
|
179
|
+
#
|
180
|
+
# otherwise lat/lng pairs are assumed
|
181
|
+
#
|
182
|
+
def great_circle_distance_from_polyline(polyline, options = {})
|
183
|
+
|
184
|
+
constructor = Proc.new do |vertex|
|
185
|
+
if options[:basis] == :cartesian
|
186
|
+
self.class.new(vertex[0], vertex[1], vertex[2])
|
187
|
+
else
|
188
|
+
self.class.from_geographic(vertex[0], vertex[1], options)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
# memoize the last processed point as both array and vector objects
|
193
|
+
last_array = polyline.first
|
194
|
+
last_vector = constructor.call(last_array)
|
195
|
+
|
196
|
+
minimum_distance = 999999999999
|
197
|
+
|
198
|
+
for vertex in polyline[1..-1]
|
199
|
+
|
200
|
+
next if vertex == last_array
|
201
|
+
|
202
|
+
start_vector = last_vector
|
203
|
+
end_vector = constructor.call(vertex)
|
204
|
+
|
205
|
+
this_segment_distance = great_circle_distance_from_arc(start_vector, end_vector, options)
|
206
|
+
|
207
|
+
if(this_segment_distance < minimum_distance)
|
208
|
+
minimum_distance = this_segment_distance
|
209
|
+
end
|
210
|
+
|
211
|
+
last_array = vertex
|
212
|
+
last_vector = end_vector
|
213
|
+
end
|
214
|
+
|
215
|
+
return minimum_distance
|
216
|
+
end
|
217
|
+
|
218
|
+
# Convert self to a geographic lat/lng pair.
|
219
|
+
def to_geographic(options = {})
|
220
|
+
[latitude(options), longitude(options)]
|
221
|
+
end
|
222
|
+
|
223
|
+
protected
|
224
|
+
|
225
|
+
# Return true of point is closer to both points than the points are to each other
|
226
|
+
def within_both_radii?(point_a,point_b)
|
227
|
+
line_length = point_a.great_circle_distance(point_b)
|
228
|
+
self.great_circle_distance(point_a) < line_length && self.great_circle_distance(point_b) < line_length
|
229
|
+
end
|
230
|
+
|
231
|
+
end
|
232
|
+
|
233
|
+
end
|
234
|
+
|