geodetic 0.1.0 → 0.3.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 +28 -4
- data/README.md +19 -5
- data/docs/coordinate-systems/bng.md +5 -5
- data/docs/coordinate-systems/ecef.md +23 -23
- data/docs/coordinate-systems/enu.md +3 -3
- data/docs/coordinate-systems/gars.md +246 -0
- data/docs/coordinate-systems/georef.md +221 -0
- data/docs/coordinate-systems/gh.md +7 -7
- data/docs/coordinate-systems/gh36.md +6 -6
- data/docs/coordinate-systems/h3.md +312 -0
- data/docs/coordinate-systems/ham.md +6 -6
- data/docs/coordinate-systems/index.md +40 -34
- data/docs/coordinate-systems/lla.md +26 -26
- data/docs/coordinate-systems/mgrs.md +3 -3
- data/docs/coordinate-systems/ned.md +3 -3
- data/docs/coordinate-systems/olc.md +6 -6
- data/docs/coordinate-systems/state-plane.md +2 -2
- data/docs/coordinate-systems/ups.md +4 -4
- data/docs/coordinate-systems/usng.md +2 -2
- data/docs/coordinate-systems/utm.md +23 -23
- data/docs/coordinate-systems/web-mercator.md +7 -7
- data/docs/getting-started/installation.md +17 -17
- data/docs/getting-started/quick-start.md +8 -8
- data/docs/index.md +22 -19
- data/docs/reference/areas.md +15 -15
- data/docs/reference/conversions.md +31 -31
- data/docs/reference/geoid-height.md +5 -5
- data/docs/reference/serialization.md +44 -44
- data/examples/01_basic_conversions.rb +10 -10
- data/examples/02_all_coordinate_systems.rb +24 -24
- data/lib/geodetic/areas/circle.rb +1 -1
- data/lib/geodetic/areas/polygon.rb +2 -2
- data/lib/geodetic/areas/rectangle.rb +6 -6
- data/lib/geodetic/{coordinates → coordinate}/bng.rb +3 -37
- data/lib/geodetic/{coordinates → coordinate}/ecef.rb +3 -33
- data/lib/geodetic/{coordinates → coordinate}/enu.rb +30 -1
- data/lib/geodetic/coordinate/gars.rb +233 -0
- data/lib/geodetic/coordinate/georef.rb +204 -0
- data/lib/geodetic/coordinate/gh.rb +161 -0
- data/lib/geodetic/{coordinates → coordinate}/gh36.rb +28 -187
- data/lib/geodetic/coordinate/h3.rb +413 -0
- data/lib/geodetic/coordinate/ham.rb +226 -0
- data/lib/geodetic/{coordinates → coordinate}/lla.rb +31 -1
- data/lib/geodetic/{coordinates → coordinate}/mgrs.rb +3 -33
- data/lib/geodetic/{coordinates → coordinate}/ned.rb +30 -1
- data/lib/geodetic/{coordinates → coordinate}/olc.rb +19 -225
- data/lib/geodetic/coordinate/spatial_hash.rb +342 -0
- data/lib/geodetic/{coordinates → coordinate}/state_plane.rb +30 -1
- data/lib/geodetic/{coordinates → coordinate}/ups.rb +3 -37
- data/lib/geodetic/{coordinates → coordinate}/usng.rb +3 -33
- data/lib/geodetic/{coordinates → coordinate}/utm.rb +3 -33
- data/lib/geodetic/{coordinates → coordinate}/web_mercator.rb +3 -33
- data/lib/geodetic/{coordinates.rb → coordinate.rb} +62 -45
- data/lib/geodetic/version.rb +1 -1
- data/lib/geodetic.rb +1 -1
- data/spatial_hash_idea.md +241 -0
- metadata +29 -20
- data/lib/geodetic/coordinates/gh.rb +0 -372
- data/lib/geodetic/coordinates/ham.rb +0 -435
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Base class for spatial hash coordinate systems (GH, GH36, HAM, OLC, etc.)
|
|
4
|
+
#
|
|
5
|
+
# Spatial hash systems encode latitude/longitude into a string code that
|
|
6
|
+
# represents a rectangular cell on the globe. They are immutable, 2D (no altitude),
|
|
7
|
+
# and convert to/from other coordinate systems through LLA as intermediary.
|
|
8
|
+
#
|
|
9
|
+
# Subclass contract — MUST implement:
|
|
10
|
+
# encode(lat, lng, precision) → String
|
|
11
|
+
# decode(code_string) → { lat:, lng: }
|
|
12
|
+
# decode_bounds(code_string) → { min_lat:, max_lat:, min_lng:, max_lng: }
|
|
13
|
+
# validate_code!(string) → raises ArgumentError or nil
|
|
14
|
+
# set_code(normalized_string) → sets the internal ivar (@geohash, @code, etc.)
|
|
15
|
+
# code_value → returns the internal ivar
|
|
16
|
+
# self.default_precision → Integer
|
|
17
|
+
# self.hash_system_name → Symbol (:gh, :gh36, :ham, :olc)
|
|
18
|
+
#
|
|
19
|
+
# MAY override:
|
|
20
|
+
# normalize(string) → String (default: identity)
|
|
21
|
+
# to_s(truncate_to)
|
|
22
|
+
# valid?
|
|
23
|
+
# precision
|
|
24
|
+
# neighbors
|
|
25
|
+
# precision_in_meters
|
|
26
|
+
|
|
27
|
+
module Geodetic
|
|
28
|
+
module Coordinate
|
|
29
|
+
class SpatialHash
|
|
30
|
+
require_relative '../datum'
|
|
31
|
+
|
|
32
|
+
# Direction offsets for neighbors: [lat_direction, lng_direction]
|
|
33
|
+
DIRECTIONS = {
|
|
34
|
+
N: [ 1, 0],
|
|
35
|
+
S: [-1, 0],
|
|
36
|
+
E: [ 0, 1],
|
|
37
|
+
W: [ 0, -1],
|
|
38
|
+
NE: [ 1, 1],
|
|
39
|
+
NW: [ 1, -1],
|
|
40
|
+
SE: [-1, 1],
|
|
41
|
+
SW: [-1, -1]
|
|
42
|
+
}.freeze
|
|
43
|
+
|
|
44
|
+
# Registry of spatial hash subclasses for cross-hash conversion generation
|
|
45
|
+
@hash_registry = {}
|
|
46
|
+
|
|
47
|
+
class << self
|
|
48
|
+
attr_reader :hash_registry
|
|
49
|
+
|
|
50
|
+
def register_hash_system(name, klass, default_precision:)
|
|
51
|
+
SpatialHash.hash_registry[name] = {
|
|
52
|
+
class_name: klass.name.split('::').last,
|
|
53
|
+
default_precision: default_precision
|
|
54
|
+
}
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Called once after all spatial hash subclasses are loaded.
|
|
58
|
+
# Generates cross-hash to_*/from_* methods with correct keyword names.
|
|
59
|
+
def finalize_cross_hash_conversions!
|
|
60
|
+
SpatialHash.hash_registry.each do |source_name, _source_config|
|
|
61
|
+
source_klass = Geodetic::Coordinate.const_get(SpatialHash.hash_registry[source_name][:class_name])
|
|
62
|
+
|
|
63
|
+
SpatialHash.hash_registry.each do |target_name, target_config|
|
|
64
|
+
next if source_name == target_name
|
|
65
|
+
|
|
66
|
+
target_class_name = target_config[:class_name]
|
|
67
|
+
precision_kwarg = "#{target_name}_precision"
|
|
68
|
+
default_prec = target_config[:default_precision]
|
|
69
|
+
|
|
70
|
+
# Instance method: to_<target>(datum = WGS84, <target>_precision: <default>)
|
|
71
|
+
unless source_klass.method_defined?(:"to_#{target_name}")
|
|
72
|
+
source_klass.class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
|
73
|
+
def to_#{target_name}(datum = WGS84, #{precision_kwarg}: #{default_prec})
|
|
74
|
+
#{target_class_name}.new(to_lla(datum), precision: #{precision_kwarg})
|
|
75
|
+
end
|
|
76
|
+
RUBY
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Class method: from_<target>(coord, datum = WGS84, precision = default_precision)
|
|
80
|
+
unless source_klass.respond_to?(:"from_#{target_name}")
|
|
81
|
+
source_klass.class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
|
82
|
+
def self.from_#{target_name}(coord, datum = WGS84, precision = default_precision)
|
|
83
|
+
new(coord, precision: precision)
|
|
84
|
+
end
|
|
85
|
+
RUBY
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Generate hash conversion methods (to_gh, from_gh, etc.) on non-hash coordinate classes.
|
|
92
|
+
# Styles:
|
|
93
|
+
# :no_datum — to_gh(precision: N), from_gh(coord, datum = WGS84)
|
|
94
|
+
# :with_datum — to_gh(datum = WGS84, precision: N), from_gh(coord, datum = WGS84)
|
|
95
|
+
# :with_datum_and_precision — same to_* as :with_datum,
|
|
96
|
+
# from_gh(coord, datum = WGS84, precision = 5)
|
|
97
|
+
def generate_hash_conversions_for(target_klass, style: :no_datum)
|
|
98
|
+
SpatialHash.hash_registry.each do |hash_name, config|
|
|
99
|
+
hash_class_name = config[:class_name]
|
|
100
|
+
default_prec = config[:default_precision]
|
|
101
|
+
|
|
102
|
+
# Instance method: to_<hash>
|
|
103
|
+
unless target_klass.method_defined?(:"to_#{hash_name}")
|
|
104
|
+
case style
|
|
105
|
+
when :no_datum
|
|
106
|
+
target_klass.class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
|
107
|
+
def to_#{hash_name}(precision: #{default_prec})
|
|
108
|
+
#{hash_class_name}.new(to_lla, precision: precision)
|
|
109
|
+
end
|
|
110
|
+
RUBY
|
|
111
|
+
when :with_datum, :with_datum_and_precision
|
|
112
|
+
target_klass.class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
|
113
|
+
def to_#{hash_name}(datum = WGS84, precision: #{default_prec})
|
|
114
|
+
#{hash_class_name}.new(to_lla(datum), precision: precision)
|
|
115
|
+
end
|
|
116
|
+
RUBY
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Class method: from_<hash>
|
|
121
|
+
unless target_klass.respond_to?(:"from_#{hash_name}")
|
|
122
|
+
case style
|
|
123
|
+
when :no_datum, :with_datum
|
|
124
|
+
target_klass.class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
|
125
|
+
def self.from_#{hash_name}(coord, datum = WGS84)
|
|
126
|
+
from_lla(coord.to_lla(datum), datum)
|
|
127
|
+
end
|
|
128
|
+
RUBY
|
|
129
|
+
when :with_datum_and_precision
|
|
130
|
+
target_klass.class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
|
131
|
+
def self.from_#{hash_name}(coord, datum = WGS84, precision = 5)
|
|
132
|
+
from_lla(coord.to_lla(datum), datum, precision)
|
|
133
|
+
end
|
|
134
|
+
RUBY
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def initialize(source, precision: self.class.default_precision)
|
|
142
|
+
case source
|
|
143
|
+
when String
|
|
144
|
+
normalized = normalize(source.strip)
|
|
145
|
+
validate_code!(normalized)
|
|
146
|
+
set_code(normalized)
|
|
147
|
+
when LLA
|
|
148
|
+
set_code(encode(source.lat, source.lng, precision))
|
|
149
|
+
else
|
|
150
|
+
if source.respond_to?(:to_lla)
|
|
151
|
+
lla = source.to_lla
|
|
152
|
+
set_code(encode(lla.lat, lla.lng, precision))
|
|
153
|
+
else
|
|
154
|
+
raise ArgumentError,
|
|
155
|
+
"Expected a code String or a coordinate object, got #{source.class}"
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def precision
|
|
161
|
+
code_value.length
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def to_s(truncate_to = nil)
|
|
165
|
+
if truncate_to
|
|
166
|
+
code_value[0, truncate_to.to_i]
|
|
167
|
+
else
|
|
168
|
+
code_value
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def to_a
|
|
173
|
+
coords = decode(code_value)
|
|
174
|
+
[coords[:lat], coords[:lng]]
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def self.from_array(array)
|
|
178
|
+
new(LLA.new(lat: array[0].to_f, lng: array[1].to_f))
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def self.from_string(string)
|
|
182
|
+
new(string.strip)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Decode to LLA (altitude is always 0.0 since spatial hashes are 2D)
|
|
186
|
+
def to_lla(datum = WGS84)
|
|
187
|
+
coords = decode(code_value)
|
|
188
|
+
lat = coords[:lat].clamp(-90.0, 90.0)
|
|
189
|
+
lng = coords[:lng].clamp(-180.0, 180.0)
|
|
190
|
+
LLA.new(lat: lat, lng: lng, alt: 0.0)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def self.from_lla(lla_coord, datum = WGS84, precision = default_precision)
|
|
194
|
+
new(lla_coord, precision: precision)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# --- Standard conversions (all chain through LLA) ---
|
|
198
|
+
|
|
199
|
+
def to_ecef(datum = WGS84)
|
|
200
|
+
to_lla(datum).to_ecef(datum)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def self.from_ecef(ecef_coord, datum = WGS84, precision = default_precision)
|
|
204
|
+
new(ecef_coord, precision: precision)
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def to_utm(datum = WGS84)
|
|
208
|
+
to_lla(datum).to_utm(datum)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def self.from_utm(utm_coord, datum = WGS84, precision = default_precision)
|
|
212
|
+
new(utm_coord, precision: precision)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def to_enu(reference_lla, datum = WGS84)
|
|
216
|
+
to_lla(datum).to_enu(reference_lla)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def self.from_enu(enu_coord, reference_lla, datum = WGS84, precision = default_precision)
|
|
220
|
+
lla_coord = enu_coord.to_lla(reference_lla)
|
|
221
|
+
new(lla_coord, precision: precision)
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def to_ned(reference_lla, datum = WGS84)
|
|
225
|
+
to_lla(datum).to_ned(reference_lla)
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def self.from_ned(ned_coord, reference_lla, datum = WGS84, precision = default_precision)
|
|
229
|
+
lla_coord = ned_coord.to_lla(reference_lla)
|
|
230
|
+
new(lla_coord, precision: precision)
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def to_mgrs(datum = WGS84, mgrs_precision = 5)
|
|
234
|
+
MGRS.from_lla(to_lla(datum), datum, mgrs_precision)
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def self.from_mgrs(mgrs_coord, datum = WGS84, precision = default_precision)
|
|
238
|
+
new(mgrs_coord, precision: precision)
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def to_usng(datum = WGS84, usng_precision = 5)
|
|
242
|
+
USNG.from_lla(to_lla(datum), datum, usng_precision)
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def self.from_usng(usng_coord, datum = WGS84, precision = default_precision)
|
|
246
|
+
new(usng_coord, precision: precision)
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def to_web_mercator(datum = WGS84)
|
|
250
|
+
WebMercator.from_lla(to_lla(datum), datum)
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def self.from_web_mercator(wm_coord, datum = WGS84, precision = default_precision)
|
|
254
|
+
new(wm_coord, precision: precision)
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def to_ups(datum = WGS84)
|
|
258
|
+
UPS.from_lla(to_lla(datum), datum)
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def self.from_ups(ups_coord, datum = WGS84, precision = default_precision)
|
|
262
|
+
new(ups_coord, precision: precision)
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def to_state_plane(zone_code, datum = WGS84)
|
|
266
|
+
StatePlane.from_lla(to_lla(datum), zone_code, datum)
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def self.from_state_plane(sp_coord, datum = WGS84, precision = default_precision)
|
|
270
|
+
new(sp_coord, precision: precision)
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def to_bng(datum = WGS84)
|
|
274
|
+
BNG.from_lla(to_lla(datum), datum)
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def self.from_bng(bng_coord, datum = WGS84, precision = default_precision)
|
|
278
|
+
new(bng_coord, precision: precision)
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
# --- Equality and utility ---
|
|
282
|
+
|
|
283
|
+
def ==(other)
|
|
284
|
+
return false unless other.is_a?(self.class)
|
|
285
|
+
code_value == other.code_value
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
alias_method :to_slug, :to_s
|
|
289
|
+
|
|
290
|
+
# Returns all 8 neighboring cells
|
|
291
|
+
# Keys: :N, :S, :E, :W, :NE, :NW, :SE, :SW
|
|
292
|
+
def neighbors
|
|
293
|
+
bb = decode_bounds(code_value)
|
|
294
|
+
lat_step = bb[:max_lat] - bb[:min_lat]
|
|
295
|
+
lng_step = bb[:max_lng] - bb[:min_lng]
|
|
296
|
+
center_lat = (bb[:min_lat] + bb[:max_lat]) / 2.0
|
|
297
|
+
center_lng = (bb[:min_lng] + bb[:max_lng]) / 2.0
|
|
298
|
+
len = precision
|
|
299
|
+
|
|
300
|
+
DIRECTIONS.each_with_object({}) do |(dir, delta), result|
|
|
301
|
+
nlat = center_lat + delta[0] * lat_step
|
|
302
|
+
nlng = center_lng + delta[1] * lng_step
|
|
303
|
+
|
|
304
|
+
nlat = nlat.clamp(-89.99999999, 89.99999999)
|
|
305
|
+
nlng += 360.0 if nlng < -180.0
|
|
306
|
+
nlng -= 360.0 if nlng > 180.0
|
|
307
|
+
|
|
308
|
+
result[dir] = self.class.new(LLA.new(lat: nlat, lng: nlng), precision: len)
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
# Returns the cell as an Areas::Rectangle
|
|
313
|
+
def to_area
|
|
314
|
+
bb = decode_bounds(code_value)
|
|
315
|
+
nw = LLA.new(lat: bb[:max_lat], lng: bb[:min_lng], alt: 0.0)
|
|
316
|
+
se = LLA.new(lat: bb[:min_lat], lng: bb[:max_lng], alt: 0.0)
|
|
317
|
+
Areas::Rectangle.new(nw: nw, se: se)
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
# Returns precision in meters as {lat:, lng:}
|
|
321
|
+
def precision_in_meters
|
|
322
|
+
bb = decode_bounds(code_value)
|
|
323
|
+
lat_center = (bb[:min_lat] + bb[:max_lat]) / 2.0
|
|
324
|
+
|
|
325
|
+
lat_meters_per_deg = 111_320.0
|
|
326
|
+
lng_meters_per_deg = 111_320.0 * Math.cos(lat_center * Math::PI / 180.0)
|
|
327
|
+
|
|
328
|
+
lat_range = bb[:max_lat] - bb[:min_lat]
|
|
329
|
+
lng_range = bb[:max_lng] - bb[:min_lng]
|
|
330
|
+
|
|
331
|
+
{ lat: lat_range * lat_meters_per_deg, lng: lng_range * lng_meters_per_deg }
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
protected
|
|
335
|
+
|
|
336
|
+
# Default normalize is identity — subclasses override (e.g., downcase, upcase)
|
|
337
|
+
def normalize(string)
|
|
338
|
+
string
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
end
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
# Each state has one or more zones with specific parameters
|
|
6
6
|
|
|
7
7
|
module Geodetic
|
|
8
|
-
module
|
|
8
|
+
module Coordinate
|
|
9
9
|
class StatePlane
|
|
10
10
|
require_relative '../datum'
|
|
11
11
|
|
|
@@ -287,6 +287,33 @@ module Geodetic
|
|
|
287
287
|
from_lla(lla_coord, zone_code, datum)
|
|
288
288
|
end
|
|
289
289
|
|
|
290
|
+
def to_georef(datum = nil, precision: 8)
|
|
291
|
+
GEOREF.new(to_lla(datum), precision: precision)
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def self.from_georef(georef_coord, zone_code, datum = WGS84)
|
|
295
|
+
lla_coord = georef_coord.to_lla(datum)
|
|
296
|
+
from_lla(lla_coord, zone_code, datum)
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
def to_gars(datum = nil, precision: 7)
|
|
300
|
+
GARS.new(to_lla(datum), precision: precision)
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
def self.from_gars(gars_coord, zone_code, datum = WGS84)
|
|
304
|
+
lla_coord = gars_coord.to_lla(datum)
|
|
305
|
+
from_lla(lla_coord, zone_code, datum)
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
def to_h3(datum = nil, precision: 7)
|
|
309
|
+
H3.new(to_lla(datum), precision: precision)
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
def self.from_h3(h3_coord, zone_code, datum = WGS84)
|
|
313
|
+
lla_coord = h3_coord.to_lla(datum)
|
|
314
|
+
from_lla(lla_coord, zone_code, datum)
|
|
315
|
+
end
|
|
316
|
+
|
|
290
317
|
# Unit conversion methods
|
|
291
318
|
def to_meters
|
|
292
319
|
zone_info = ZONES[@zone_code]
|
|
@@ -473,6 +500,8 @@ module Geodetic
|
|
|
473
500
|
|
|
474
501
|
new(easting: x, northing: y, zone_code: zone_code, datum: datum)
|
|
475
502
|
end
|
|
503
|
+
|
|
504
|
+
Coordinate.register_class(self)
|
|
476
505
|
end
|
|
477
506
|
end
|
|
478
507
|
end
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# Used for polar regions not covered by UTM (north of 84°N and south of 80°S)
|
|
5
5
|
|
|
6
6
|
module Geodetic
|
|
7
|
-
module
|
|
7
|
+
module Coordinate
|
|
8
8
|
class UPS
|
|
9
9
|
require_relative '../datum'
|
|
10
10
|
|
|
@@ -260,42 +260,6 @@ module Geodetic
|
|
|
260
260
|
from_lla(lla_coord, datum)
|
|
261
261
|
end
|
|
262
262
|
|
|
263
|
-
def to_gh36(datum = WGS84, precision: 10)
|
|
264
|
-
GH36.new(to_lla(datum), precision: precision)
|
|
265
|
-
end
|
|
266
|
-
|
|
267
|
-
def self.from_gh36(gh36_coord, datum = WGS84)
|
|
268
|
-
lla_coord = gh36_coord.to_lla(datum)
|
|
269
|
-
from_lla(lla_coord, datum)
|
|
270
|
-
end
|
|
271
|
-
|
|
272
|
-
def to_gh(datum = WGS84, precision: 12)
|
|
273
|
-
GH.new(to_lla(datum), precision: precision)
|
|
274
|
-
end
|
|
275
|
-
|
|
276
|
-
def self.from_gh(gh_coord, datum = WGS84)
|
|
277
|
-
lla_coord = gh_coord.to_lla(datum)
|
|
278
|
-
from_lla(lla_coord, datum)
|
|
279
|
-
end
|
|
280
|
-
|
|
281
|
-
def to_ham(datum = WGS84, precision: 6)
|
|
282
|
-
HAM.new(to_lla(datum), precision: precision)
|
|
283
|
-
end
|
|
284
|
-
|
|
285
|
-
def self.from_ham(ham_coord, datum = WGS84)
|
|
286
|
-
lla_coord = ham_coord.to_lla(datum)
|
|
287
|
-
from_lla(lla_coord, datum)
|
|
288
|
-
end
|
|
289
|
-
|
|
290
|
-
def to_olc(datum = WGS84, precision: 10)
|
|
291
|
-
OLC.new(to_lla(datum), precision: precision)
|
|
292
|
-
end
|
|
293
|
-
|
|
294
|
-
def self.from_olc(olc_coord, datum = WGS84)
|
|
295
|
-
lla_coord = olc_coord.to_lla(datum)
|
|
296
|
-
from_lla(lla_coord, datum)
|
|
297
|
-
end
|
|
298
|
-
|
|
299
263
|
def ==(other)
|
|
300
264
|
return false unless other.is_a?(UPS)
|
|
301
265
|
|
|
@@ -347,6 +311,8 @@ module Geodetic
|
|
|
347
311
|
raise ArgumentError, "Invalid UPS zone '#{@zone}' for hemisphere '#{@hemisphere}'"
|
|
348
312
|
end
|
|
349
313
|
end
|
|
314
|
+
|
|
315
|
+
Coordinate.register_class(self)
|
|
350
316
|
end
|
|
351
317
|
end
|
|
352
318
|
end
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
# Used primarily within the United States for emergency services and land management
|
|
6
6
|
|
|
7
7
|
module Geodetic
|
|
8
|
-
module
|
|
8
|
+
module Coordinate
|
|
9
9
|
class USNG
|
|
10
10
|
require_relative '../datum'
|
|
11
11
|
require_relative 'mgrs'
|
|
@@ -139,38 +139,6 @@ module Geodetic
|
|
|
139
139
|
from_lla(sp_coord.to_lla(datum), datum, precision)
|
|
140
140
|
end
|
|
141
141
|
|
|
142
|
-
def to_gh36(datum = WGS84, precision: 10)
|
|
143
|
-
GH36.new(to_lla(datum), precision: precision)
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
def self.from_gh36(gh36_coord, datum = WGS84, precision = 5)
|
|
147
|
-
from_lla(gh36_coord.to_lla(datum), datum, precision)
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
def to_gh(datum = WGS84, precision: 12)
|
|
151
|
-
GH.new(to_lla(datum), precision: precision)
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
def self.from_gh(gh_coord, datum = WGS84, precision = 5)
|
|
155
|
-
from_lla(gh_coord.to_lla(datum), datum, precision)
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
def to_ham(datum = WGS84, precision: 6)
|
|
159
|
-
HAM.new(to_lla(datum), precision: precision)
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
def self.from_ham(ham_coord, datum = WGS84, precision = 5)
|
|
163
|
-
from_lla(ham_coord.to_lla(datum), datum, precision)
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
def to_olc(datum = WGS84, precision: 10)
|
|
167
|
-
OLC.new(to_lla(datum), precision: precision)
|
|
168
|
-
end
|
|
169
|
-
|
|
170
|
-
def self.from_olc(olc_coord, datum = WGS84, precision = 5)
|
|
171
|
-
from_lla(olc_coord.to_lla(datum), datum, precision)
|
|
172
|
-
end
|
|
173
|
-
|
|
174
142
|
def ==(other)
|
|
175
143
|
return false unless other.is_a?(USNG)
|
|
176
144
|
|
|
@@ -293,6 +261,8 @@ module Geodetic
|
|
|
293
261
|
@precision = mgrs_coord.precision
|
|
294
262
|
end
|
|
295
263
|
end
|
|
264
|
+
|
|
265
|
+
Coordinate.register_class(self)
|
|
296
266
|
end
|
|
297
267
|
end
|
|
298
268
|
end
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
require_relative '../datum'
|
|
4
4
|
|
|
5
5
|
module Geodetic
|
|
6
|
-
module
|
|
6
|
+
module Coordinate
|
|
7
7
|
class UTM
|
|
8
8
|
attr_reader :easting, :northing, :altitude, :zone, :hemisphere
|
|
9
9
|
alias_method :x, :easting
|
|
@@ -196,38 +196,6 @@ module Geodetic
|
|
|
196
196
|
from_lla(bng_coord.to_lla, datum)
|
|
197
197
|
end
|
|
198
198
|
|
|
199
|
-
def to_gh36(precision: 10)
|
|
200
|
-
GH36.new(to_lla, precision: precision)
|
|
201
|
-
end
|
|
202
|
-
|
|
203
|
-
def self.from_gh36(gh36_coord, datum = WGS84)
|
|
204
|
-
from_lla(gh36_coord.to_lla, datum)
|
|
205
|
-
end
|
|
206
|
-
|
|
207
|
-
def to_gh(precision: 12)
|
|
208
|
-
GH.new(to_lla, precision: precision)
|
|
209
|
-
end
|
|
210
|
-
|
|
211
|
-
def self.from_gh(gh_coord, datum = WGS84)
|
|
212
|
-
from_lla(gh_coord.to_lla, datum)
|
|
213
|
-
end
|
|
214
|
-
|
|
215
|
-
def to_ham(precision: 6)
|
|
216
|
-
HAM.new(to_lla, precision: precision)
|
|
217
|
-
end
|
|
218
|
-
|
|
219
|
-
def self.from_ham(ham_coord, datum = WGS84)
|
|
220
|
-
from_lla(ham_coord.to_lla, datum)
|
|
221
|
-
end
|
|
222
|
-
|
|
223
|
-
def to_olc(precision: 10)
|
|
224
|
-
OLC.new(to_lla, precision: precision)
|
|
225
|
-
end
|
|
226
|
-
|
|
227
|
-
def self.from_olc(olc_coord, datum = WGS84)
|
|
228
|
-
from_lla(olc_coord.to_lla, datum)
|
|
229
|
-
end
|
|
230
|
-
|
|
231
199
|
def to_s(precision = 2)
|
|
232
200
|
precision = precision.to_i
|
|
233
201
|
if precision == 0
|
|
@@ -280,6 +248,8 @@ module Geodetic
|
|
|
280
248
|
raise ArgumentError, "Easting must be positive" if @easting < 0
|
|
281
249
|
raise ArgumentError, "Northing must be positive" if @northing < 0
|
|
282
250
|
end
|
|
251
|
+
|
|
252
|
+
Coordinate.register_class(self)
|
|
283
253
|
end
|
|
284
254
|
end
|
|
285
255
|
end
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
# Used by Google Maps, OpenStreetMap, Bing Maps, and most web mapping services
|
|
6
6
|
|
|
7
7
|
module Geodetic
|
|
8
|
-
module
|
|
8
|
+
module Coordinate
|
|
9
9
|
class WebMercator
|
|
10
10
|
require_relative '../datum'
|
|
11
11
|
|
|
@@ -153,38 +153,6 @@ module Geodetic
|
|
|
153
153
|
from_lla(bng_coord.to_lla, datum)
|
|
154
154
|
end
|
|
155
155
|
|
|
156
|
-
def to_gh36(precision: 10)
|
|
157
|
-
GH36.new(to_lla, precision: precision)
|
|
158
|
-
end
|
|
159
|
-
|
|
160
|
-
def self.from_gh36(gh36_coord, datum = WGS84)
|
|
161
|
-
from_lla(gh36_coord.to_lla, datum)
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
def to_gh(precision: 12)
|
|
165
|
-
GH.new(to_lla, precision: precision)
|
|
166
|
-
end
|
|
167
|
-
|
|
168
|
-
def self.from_gh(gh_coord, datum = WGS84)
|
|
169
|
-
from_lla(gh_coord.to_lla, datum)
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
def to_ham(precision: 6)
|
|
173
|
-
HAM.new(to_lla, precision: precision)
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
def self.from_ham(ham_coord, datum = WGS84)
|
|
177
|
-
from_lla(ham_coord.to_lla, datum)
|
|
178
|
-
end
|
|
179
|
-
|
|
180
|
-
def to_olc(precision: 10)
|
|
181
|
-
OLC.new(to_lla, precision: precision)
|
|
182
|
-
end
|
|
183
|
-
|
|
184
|
-
def self.from_olc(olc_coord, datum = WGS84)
|
|
185
|
-
from_lla(olc_coord.to_lla, datum)
|
|
186
|
-
end
|
|
187
|
-
|
|
188
156
|
# Tile coordinate methods for web mapping
|
|
189
157
|
def to_tile_coordinates(zoom_level)
|
|
190
158
|
lat_lng = to_lla
|
|
@@ -261,6 +229,8 @@ module Geodetic
|
|
|
261
229
|
east: se.x
|
|
262
230
|
}
|
|
263
231
|
end
|
|
232
|
+
|
|
233
|
+
Coordinate.register_class(self)
|
|
264
234
|
end
|
|
265
235
|
end
|
|
266
236
|
end
|