vector_geometry 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|