geodetic 0.3.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +84 -2
- data/README.md +121 -8
- data/docs/coordinate-systems/gars.md +2 -2
- data/docs/coordinate-systems/georef.md +2 -2
- data/docs/coordinate-systems/gh.md +2 -2
- data/docs/coordinate-systems/gh36.md +2 -2
- data/docs/coordinate-systems/h3.md +2 -2
- data/docs/coordinate-systems/ham.md +2 -2
- data/docs/coordinate-systems/olc.md +2 -2
- data/docs/index.md +4 -2
- data/docs/reference/areas.md +140 -14
- data/docs/reference/arithmetic.md +368 -0
- data/docs/reference/feature.md +2 -2
- data/docs/reference/path.md +3 -3
- data/docs/reference/segment.md +181 -0
- data/docs/reference/vector.md +256 -0
- data/examples/02_all_coordinate_systems.rb +6 -6
- data/examples/06_path_operations.rb +2 -4
- data/examples/07_segments_and_shapes.rb +258 -0
- data/examples/08_geodetic_arithmetic.rb +393 -0
- data/examples/README.md +35 -1
- data/lib/geodetic/areas/bounding_box.rb +56 -0
- data/lib/geodetic/areas/circle.rb +8 -0
- data/lib/geodetic/areas/hexagon.rb +11 -0
- data/lib/geodetic/areas/octagon.rb +11 -0
- data/lib/geodetic/areas/pentagon.rb +11 -0
- data/lib/geodetic/areas/polygon.rb +64 -14
- data/lib/geodetic/areas/rectangle.rb +85 -35
- data/lib/geodetic/areas/regular_polygon.rb +59 -0
- data/lib/geodetic/areas/triangle.rb +180 -0
- data/lib/geodetic/areas.rb +6 -0
- data/lib/geodetic/coordinate/gh36.rb +1 -1
- data/lib/geodetic/coordinate/h3.rb +1 -1
- data/lib/geodetic/coordinate/spatial_hash.rb +2 -2
- data/lib/geodetic/coordinate.rb +26 -1
- data/lib/geodetic/distance.rb +5 -1
- data/lib/geodetic/path.rb +85 -153
- data/lib/geodetic/segment.rb +193 -0
- data/lib/geodetic/vector.rb +242 -0
- data/lib/geodetic/version.rb +1 -1
- data/lib/geodetic.rb +2 -0
- data/mkdocs.yml +1 -0
- metadata +14 -1
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Geodetic
|
|
4
|
+
class Vector
|
|
5
|
+
include Comparable
|
|
6
|
+
|
|
7
|
+
attr_reader :distance, :bearing
|
|
8
|
+
|
|
9
|
+
def initialize(distance:, bearing:)
|
|
10
|
+
@distance = distance.is_a?(Distance) ? distance : Distance.new(distance.to_f)
|
|
11
|
+
@bearing = bearing.is_a?(Bearing) ? bearing : Bearing.new(bearing.to_f)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# --- Components (meters) ---
|
|
15
|
+
|
|
16
|
+
def north
|
|
17
|
+
@distance.meters * Math.cos(@bearing.to_radians)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def east
|
|
21
|
+
@distance.meters * Math.sin(@bearing.to_radians)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# --- Factory ---
|
|
25
|
+
|
|
26
|
+
ZERO_TOLERANCE = 1e-9
|
|
27
|
+
|
|
28
|
+
def self.from_components(north:, east:)
|
|
29
|
+
meters = Math.sqrt(north**2 + east**2)
|
|
30
|
+
|
|
31
|
+
if meters < ZERO_TOLERANCE
|
|
32
|
+
new(distance: Distance.new(0.0), bearing: Bearing.new(0.0))
|
|
33
|
+
else
|
|
34
|
+
new(distance: Distance.new(meters), bearing: Bearing.new(Math.atan2(east, north) * DEG_PER_RAD))
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.from_segment(segment)
|
|
39
|
+
new(distance: segment.length, bearing: segment.bearing)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# --- Arithmetic ---
|
|
43
|
+
|
|
44
|
+
def +(other)
|
|
45
|
+
case other
|
|
46
|
+
when Vector
|
|
47
|
+
self.class.from_components(north: north + other.north, east: east + other.east)
|
|
48
|
+
when Segment
|
|
49
|
+
new_start = reverse.destination_from(other.start_point)
|
|
50
|
+
Path.new(coordinates: [new_start, other.start_point, other.end_point])
|
|
51
|
+
when ->(o) { Coordinate.systems.any? { |k| o.is_a?(k) } }
|
|
52
|
+
Segment.new(reverse.destination_from(other), other)
|
|
53
|
+
else
|
|
54
|
+
raise ArgumentError, "Cannot add #{other.class} to Vector"
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def -(other)
|
|
59
|
+
case other
|
|
60
|
+
when Vector
|
|
61
|
+
self.class.from_components(north: north - other.north, east: east - other.east)
|
|
62
|
+
else
|
|
63
|
+
raise ArgumentError, "Cannot subtract #{other.class} from Vector"
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def *(scalar)
|
|
68
|
+
raise ArgumentError, "Cannot multiply Vector by #{scalar.class}" unless scalar.is_a?(Numeric)
|
|
69
|
+
|
|
70
|
+
if scalar < 0
|
|
71
|
+
self.class.new(distance: Distance.new(@distance.meters * scalar.abs), bearing: @bearing.reverse)
|
|
72
|
+
else
|
|
73
|
+
self.class.new(distance: Distance.new(@distance.meters * scalar), bearing: @bearing)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def /(scalar)
|
|
78
|
+
raise ArgumentError, "Cannot divide Vector by #{scalar.class}" unless scalar.is_a?(Numeric)
|
|
79
|
+
raise ZeroDivisionError, "Cannot divide Vector by zero" if scalar == 0
|
|
80
|
+
|
|
81
|
+
self * (1.0 / scalar)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def -@
|
|
85
|
+
self.class.new(distance: @distance, bearing: @bearing.reverse)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def coerce(other)
|
|
89
|
+
case other
|
|
90
|
+
when Numeric then [ScalarCoerce.new(other), self]
|
|
91
|
+
else raise TypeError, "#{other.class} can't be coerced into Vector"
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# @private
|
|
96
|
+
ScalarCoerce = Struct.new(:value) do
|
|
97
|
+
def *(vector)
|
|
98
|
+
vector * value
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# --- Properties ---
|
|
103
|
+
|
|
104
|
+
def magnitude
|
|
105
|
+
@distance.meters
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def zero?
|
|
109
|
+
@distance.zero?
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def reverse
|
|
113
|
+
-self
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
alias inverse reverse
|
|
117
|
+
|
|
118
|
+
def normalize
|
|
119
|
+
return self if zero?
|
|
120
|
+
|
|
121
|
+
self.class.new(distance: Distance.new(1.0), bearing: @bearing)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def dot(other)
|
|
125
|
+
raise ArgumentError, "Cannot dot Vector with #{other.class}" unless other.is_a?(Vector)
|
|
126
|
+
|
|
127
|
+
north * other.north + east * other.east
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def cross(other)
|
|
131
|
+
raise ArgumentError, "Cannot cross Vector with #{other.class}" unless other.is_a?(Vector)
|
|
132
|
+
|
|
133
|
+
north * other.east - east * other.north
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def angle_between(other)
|
|
137
|
+
raise ArgumentError, "expected a Vector" unless other.is_a?(Vector)
|
|
138
|
+
|
|
139
|
+
Bearing.new(other.bearing.degrees - @bearing.degrees)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# --- Vincenty direct ---
|
|
143
|
+
|
|
144
|
+
def destination_from(origin)
|
|
145
|
+
lla = origin.is_a?(Coordinate::LLA) ? origin : origin.to_lla
|
|
146
|
+
|
|
147
|
+
a = WGS84.a
|
|
148
|
+
f = WGS84.f
|
|
149
|
+
b = WGS84.b
|
|
150
|
+
s = @distance.meters
|
|
151
|
+
|
|
152
|
+
return lla if s == 0.0
|
|
153
|
+
|
|
154
|
+
alpha1 = @bearing.degrees * RAD_PER_DEG
|
|
155
|
+
sin_alpha1 = Math.sin(alpha1)
|
|
156
|
+
cos_alpha1 = Math.cos(alpha1)
|
|
157
|
+
|
|
158
|
+
tan_u1 = (1 - f) * Math.tan(lla.lat * RAD_PER_DEG)
|
|
159
|
+
cos_u1 = 1.0 / Math.sqrt(1 + tan_u1**2)
|
|
160
|
+
sin_u1 = tan_u1 * cos_u1
|
|
161
|
+
|
|
162
|
+
sigma1 = Math.atan2(tan_u1, cos_alpha1)
|
|
163
|
+
sin_alpha = cos_u1 * sin_alpha1
|
|
164
|
+
cos2_alpha = 1 - sin_alpha**2
|
|
165
|
+
|
|
166
|
+
u_sq = cos2_alpha * (a**2 - b**2) / b**2
|
|
167
|
+
aa = 1 + u_sq / 16384 * (4096 + u_sq * (-768 + u_sq * (320 - 175 * u_sq)))
|
|
168
|
+
bb = u_sq / 1024 * (256 + u_sq * (-128 + u_sq * (74 - 47 * u_sq)))
|
|
169
|
+
|
|
170
|
+
sigma = s / (b * aa)
|
|
171
|
+
|
|
172
|
+
sin_sigma = 0.0
|
|
173
|
+
cos_sigma = 0.0
|
|
174
|
+
cos_2sigma_m = 0.0
|
|
175
|
+
|
|
176
|
+
100.times do
|
|
177
|
+
cos_2sigma_m = Math.cos(2 * sigma1 + sigma)
|
|
178
|
+
sin_sigma = Math.sin(sigma)
|
|
179
|
+
cos_sigma = Math.cos(sigma)
|
|
180
|
+
|
|
181
|
+
delta_sigma = bb * sin_sigma * (cos_2sigma_m + bb / 4 *
|
|
182
|
+
(cos_sigma * (-1 + 2 * cos_2sigma_m**2) -
|
|
183
|
+
bb / 6 * cos_2sigma_m * (-3 + 4 * sin_sigma**2) * (-3 + 4 * cos_2sigma_m**2)))
|
|
184
|
+
|
|
185
|
+
sigma_prev = sigma
|
|
186
|
+
sigma = s / (b * aa) + delta_sigma
|
|
187
|
+
|
|
188
|
+
break if (sigma - sigma_prev).abs < 1e-12
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
sin_sigma = Math.sin(sigma)
|
|
192
|
+
cos_sigma = Math.cos(sigma)
|
|
193
|
+
cos_2sigma_m = Math.cos(2 * sigma1 + sigma)
|
|
194
|
+
|
|
195
|
+
lat2 = Math.atan2(
|
|
196
|
+
sin_u1 * cos_sigma + cos_u1 * sin_sigma * cos_alpha1,
|
|
197
|
+
(1 - f) * Math.sqrt(sin_alpha**2 +
|
|
198
|
+
(sin_u1 * sin_sigma - cos_u1 * cos_sigma * cos_alpha1)**2)
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
lambda_val = Math.atan2(
|
|
202
|
+
sin_sigma * sin_alpha1,
|
|
203
|
+
cos_u1 * cos_sigma - sin_u1 * sin_sigma * cos_alpha1
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
c = f / 16 * cos2_alpha * (4 + f * (4 - 3 * cos2_alpha))
|
|
207
|
+
|
|
208
|
+
l = lambda_val - (1 - c) * f * sin_alpha *
|
|
209
|
+
(sigma + c * sin_sigma * (cos_2sigma_m + c * cos_sigma * (-1 + 2 * cos_2sigma_m**2)))
|
|
210
|
+
|
|
211
|
+
Coordinate::LLA.new(
|
|
212
|
+
lat: lat2 * DEG_PER_RAD,
|
|
213
|
+
lng: lla.lng + l * DEG_PER_RAD,
|
|
214
|
+
alt: lla.alt
|
|
215
|
+
)
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# --- Comparison ---
|
|
219
|
+
|
|
220
|
+
def <=>(other)
|
|
221
|
+
case other
|
|
222
|
+
when Vector then @distance <=> other.distance
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def ==(other)
|
|
227
|
+
other.is_a?(Vector) &&
|
|
228
|
+
@distance == other.distance &&
|
|
229
|
+
@bearing == other.bearing
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# --- Display ---
|
|
233
|
+
|
|
234
|
+
def to_s
|
|
235
|
+
"Vector(#{@distance}, #{@bearing})"
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def inspect
|
|
239
|
+
"#<Geodetic::Vector distance=#{@distance.inspect} bearing=#{@bearing.inspect}>"
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
end
|
data/lib/geodetic/version.rb
CHANGED
data/lib/geodetic.rb
CHANGED
|
@@ -7,6 +7,8 @@ require_relative "geodetic/bearing"
|
|
|
7
7
|
require_relative "geodetic/geoid_height"
|
|
8
8
|
require_relative "geodetic/coordinate"
|
|
9
9
|
require_relative "geodetic/areas"
|
|
10
|
+
require_relative "geodetic/vector"
|
|
11
|
+
require_relative "geodetic/segment"
|
|
10
12
|
require_relative "geodetic/path"
|
|
11
13
|
require_relative "geodetic/feature"
|
|
12
14
|
|
data/mkdocs.yml
CHANGED
|
@@ -136,6 +136,7 @@ nav:
|
|
|
136
136
|
- Datums: reference/datums.md
|
|
137
137
|
- Geoid Height: reference/geoid-height.md
|
|
138
138
|
- Areas: reference/areas.md
|
|
139
|
+
- Segment: reference/segment.md
|
|
139
140
|
- Path: reference/path.md
|
|
140
141
|
- Feature: reference/feature.md
|
|
141
142
|
- Serialization: reference/serialization.md
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: geodetic
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dewayne VanHoozer
|
|
@@ -51,13 +51,16 @@ files:
|
|
|
51
51
|
- docs/getting-started/quick-start.md
|
|
52
52
|
- docs/index.md
|
|
53
53
|
- docs/reference/areas.md
|
|
54
|
+
- docs/reference/arithmetic.md
|
|
54
55
|
- docs/reference/conversions.md
|
|
55
56
|
- docs/reference/datums.md
|
|
56
57
|
- docs/reference/feature.md
|
|
57
58
|
- docs/reference/geoid-height.md
|
|
58
59
|
- docs/reference/map-rendering.md
|
|
59
60
|
- docs/reference/path.md
|
|
61
|
+
- docs/reference/segment.md
|
|
60
62
|
- docs/reference/serialization.md
|
|
63
|
+
- docs/reference/vector.md
|
|
61
64
|
- examples/01_basic_conversions.rb
|
|
62
65
|
- examples/02_all_coordinate_systems.rb
|
|
63
66
|
- examples/03_distance_calculations.rb
|
|
@@ -71,13 +74,21 @@ files:
|
|
|
71
74
|
- examples/05_map_rendering/icons/park.png
|
|
72
75
|
- examples/05_map_rendering/nyc_landmarks.png
|
|
73
76
|
- examples/06_path_operations.rb
|
|
77
|
+
- examples/07_segments_and_shapes.rb
|
|
78
|
+
- examples/08_geodetic_arithmetic.rb
|
|
74
79
|
- examples/README.md
|
|
75
80
|
- fiddle_pointer_buffer_pool.md
|
|
76
81
|
- lib/geodetic.rb
|
|
77
82
|
- lib/geodetic/areas.rb
|
|
83
|
+
- lib/geodetic/areas/bounding_box.rb
|
|
78
84
|
- lib/geodetic/areas/circle.rb
|
|
85
|
+
- lib/geodetic/areas/hexagon.rb
|
|
86
|
+
- lib/geodetic/areas/octagon.rb
|
|
87
|
+
- lib/geodetic/areas/pentagon.rb
|
|
79
88
|
- lib/geodetic/areas/polygon.rb
|
|
80
89
|
- lib/geodetic/areas/rectangle.rb
|
|
90
|
+
- lib/geodetic/areas/regular_polygon.rb
|
|
91
|
+
- lib/geodetic/areas/triangle.rb
|
|
81
92
|
- lib/geodetic/bearing.rb
|
|
82
93
|
- lib/geodetic/coordinate.rb
|
|
83
94
|
- lib/geodetic/coordinate/bng.rb
|
|
@@ -104,6 +115,8 @@ files:
|
|
|
104
115
|
- lib/geodetic/feature.rb
|
|
105
116
|
- lib/geodetic/geoid_height.rb
|
|
106
117
|
- lib/geodetic/path.rb
|
|
118
|
+
- lib/geodetic/segment.rb
|
|
119
|
+
- lib/geodetic/vector.rb
|
|
107
120
|
- lib/geodetic/version.rb
|
|
108
121
|
- mkdocs.yml
|
|
109
122
|
- sig/geodetic.rbs
|