geodetic 0.3.0 → 0.3.2
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 +60 -8
- data/README.md +108 -25
- data/docs/coordinate-systems/gars.md +0 -4
- data/docs/coordinate-systems/georef.md +0 -4
- data/docs/coordinate-systems/gh.md +0 -4
- data/docs/coordinate-systems/gh36.md +0 -4
- data/docs/coordinate-systems/h3.md +0 -4
- data/docs/coordinate-systems/ham.md +0 -4
- data/docs/coordinate-systems/index.md +2 -2
- data/docs/coordinate-systems/olc.md +0 -4
- data/docs/index.md +5 -0
- data/docs/reference/conversions.md +15 -15
- data/docs/reference/feature.md +117 -0
- data/docs/reference/map-rendering.md +32 -0
- data/docs/reference/path.md +269 -0
- data/docs/reference/serialization.md +4 -4
- data/examples/02_all_coordinate_systems.rb +0 -3
- data/examples/03_distance_calculations.rb +1 -0
- data/examples/04_bearing_calculations.rb +1 -0
- data/examples/05_map_rendering/.gitignore +2 -0
- data/examples/05_map_rendering/demo.rb +264 -0
- data/examples/05_map_rendering/icons/bridge.png +0 -0
- data/examples/05_map_rendering/icons/building.png +0 -0
- data/examples/05_map_rendering/icons/landmark.png +0 -0
- data/examples/05_map_rendering/icons/monument.png +0 -0
- data/examples/05_map_rendering/icons/park.png +0 -0
- data/examples/05_map_rendering/nyc_landmarks.png +0 -0
- data/examples/06_path_operations.rb +368 -0
- data/examples/README.md +85 -0
- data/fiddle_pointer_buffer_pool.md +119 -0
- data/lib/geodetic/coordinate/bng.rb +14 -33
- data/lib/geodetic/coordinate/ecef.rb +5 -1
- data/lib/geodetic/coordinate/enu.rb +4 -0
- data/lib/geodetic/coordinate/gars.rb +2 -3
- data/lib/geodetic/coordinate/georef.rb +2 -3
- data/lib/geodetic/coordinate/gh.rb +2 -4
- data/lib/geodetic/coordinate/gh36.rb +4 -5
- data/lib/geodetic/coordinate/h3.rb +2 -3
- data/lib/geodetic/coordinate/ham.rb +2 -3
- data/lib/geodetic/coordinate/lla.rb +7 -1
- data/lib/geodetic/coordinate/mgrs.rb +1 -1
- data/lib/geodetic/coordinate/ned.rb +4 -0
- data/lib/geodetic/coordinate/olc.rb +0 -1
- data/lib/geodetic/coordinate/spatial_hash.rb +2 -2
- data/lib/geodetic/coordinate/ups.rb +1 -1
- data/lib/geodetic/coordinate/usng.rb +1 -1
- data/lib/geodetic/coordinate/utm.rb +1 -1
- data/lib/geodetic/coordinate/web_mercator.rb +1 -1
- data/lib/geodetic/coordinate.rb +30 -26
- data/lib/geodetic/feature.rb +52 -0
- data/lib/geodetic/geoid_height.rb +11 -6
- data/lib/geodetic/path.rb +599 -0
- data/lib/geodetic/version.rb +1 -1
- data/lib/geodetic.rb +2 -0
- data/mkdocs.yml +3 -0
- metadata +17 -1
|
@@ -76,12 +76,12 @@ module Geodetic
|
|
|
76
76
|
VALID_LENGTHS.include?(@code.length) && valid_characters?(@code)
|
|
77
77
|
end
|
|
78
78
|
|
|
79
|
+
protected
|
|
80
|
+
|
|
79
81
|
def code_value
|
|
80
82
|
@code
|
|
81
83
|
end
|
|
82
84
|
|
|
83
|
-
protected
|
|
84
|
-
|
|
85
85
|
def normalize(string)
|
|
86
86
|
result = String.new(capacity: string.length)
|
|
87
87
|
string.each_char.with_index do |ch, i|
|
|
@@ -227,7 +227,6 @@ module Geodetic
|
|
|
227
227
|
end
|
|
228
228
|
|
|
229
229
|
register_hash_system(:gars, self, default_precision: 7)
|
|
230
|
-
Coordinate.register_class(self)
|
|
231
230
|
end
|
|
232
231
|
end
|
|
233
232
|
end
|
|
@@ -55,12 +55,12 @@ module Geodetic
|
|
|
55
55
|
VALID_LENGTHS.include?(@code.length) && valid_characters?(@code)
|
|
56
56
|
end
|
|
57
57
|
|
|
58
|
+
protected
|
|
59
|
+
|
|
58
60
|
def code_value
|
|
59
61
|
@code
|
|
60
62
|
end
|
|
61
63
|
|
|
62
|
-
protected
|
|
63
|
-
|
|
64
64
|
def normalize(string)
|
|
65
65
|
string.upcase
|
|
66
66
|
end
|
|
@@ -198,7 +198,6 @@ module Geodetic
|
|
|
198
198
|
end
|
|
199
199
|
|
|
200
200
|
register_hash_system(:georef, self, default_precision: 8)
|
|
201
|
-
Coordinate.register_class(self)
|
|
202
201
|
end
|
|
203
202
|
end
|
|
204
203
|
end
|
|
@@ -39,13 +39,12 @@ module Geodetic
|
|
|
39
39
|
@geohash.length > 0 && @geohash.each_char.all? { |c| CHAR_INDEX.key?(c) }
|
|
40
40
|
end
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
protected
|
|
43
|
+
|
|
43
44
|
def code_value
|
|
44
45
|
@geohash
|
|
45
46
|
end
|
|
46
47
|
|
|
47
|
-
protected
|
|
48
|
-
|
|
49
48
|
def normalize(string)
|
|
50
49
|
string.downcase
|
|
51
50
|
end
|
|
@@ -155,7 +154,6 @@ module Geodetic
|
|
|
155
154
|
alias_method :validate_code!, :validate_geohash!
|
|
156
155
|
|
|
157
156
|
register_hash_system(:gh, self, default_precision: 12)
|
|
158
|
-
Coordinate.register_class(self)
|
|
159
157
|
end
|
|
160
158
|
end
|
|
161
159
|
end
|
|
@@ -74,10 +74,6 @@ module Geodetic
|
|
|
74
74
|
@geohash.length > 0 && @geohash.each_char.all? { |c| VALID_CHARS_SET.include?(c) }
|
|
75
75
|
end
|
|
76
76
|
|
|
77
|
-
def code_value
|
|
78
|
-
@geohash
|
|
79
|
-
end
|
|
80
|
-
|
|
81
77
|
# --- GH36-specific overrides (matrix-based algorithms) ---
|
|
82
78
|
|
|
83
79
|
# Uses recursive matrix-based neighbor calculation instead of bounds-based
|
|
@@ -105,6 +101,10 @@ module Geodetic
|
|
|
105
101
|
|
|
106
102
|
protected
|
|
107
103
|
|
|
104
|
+
def code_value
|
|
105
|
+
@geohash
|
|
106
|
+
end
|
|
107
|
+
|
|
108
108
|
def set_code(value)
|
|
109
109
|
@geohash = value
|
|
110
110
|
end
|
|
@@ -243,7 +243,6 @@ module Geodetic
|
|
|
243
243
|
alias_method :validate_code!, :validate_geohash!
|
|
244
244
|
|
|
245
245
|
register_hash_system(:gh36, self, default_precision: 10)
|
|
246
|
-
Coordinate.register_class(self)
|
|
247
246
|
end
|
|
248
247
|
end
|
|
249
248
|
end
|
|
@@ -358,12 +358,12 @@ module Geodetic
|
|
|
358
358
|
end
|
|
359
359
|
end
|
|
360
360
|
|
|
361
|
+
protected
|
|
362
|
+
|
|
361
363
|
def code_value
|
|
362
364
|
@code
|
|
363
365
|
end
|
|
364
366
|
|
|
365
|
-
protected
|
|
366
|
-
|
|
367
367
|
def normalize(string)
|
|
368
368
|
string.downcase.delete_prefix('0x')
|
|
369
369
|
end
|
|
@@ -407,7 +407,6 @@ module Geodetic
|
|
|
407
407
|
alias_method :validate_code!, :validate_h3!
|
|
408
408
|
|
|
409
409
|
register_hash_system(:h3, self, default_precision: 7)
|
|
410
|
-
Coordinate.register_class(self)
|
|
411
410
|
end
|
|
412
411
|
end
|
|
413
412
|
end
|
|
@@ -74,12 +74,12 @@ module Geodetic
|
|
|
74
74
|
valid_characters?(@locator)
|
|
75
75
|
end
|
|
76
76
|
|
|
77
|
+
protected
|
|
78
|
+
|
|
77
79
|
def code_value
|
|
78
80
|
@locator
|
|
79
81
|
end
|
|
80
82
|
|
|
81
|
-
protected
|
|
82
|
-
|
|
83
83
|
def normalize(string)
|
|
84
84
|
result = String.new(capacity: string.length)
|
|
85
85
|
string.each_char.with_index do |ch, i|
|
|
@@ -220,7 +220,6 @@ module Geodetic
|
|
|
220
220
|
end
|
|
221
221
|
|
|
222
222
|
register_hash_system(:ham, self, default_precision: 6)
|
|
223
|
-
Coordinate.register_class(self)
|
|
224
223
|
end
|
|
225
224
|
end
|
|
226
225
|
end
|
|
@@ -305,6 +305,12 @@ module Geodetic
|
|
|
305
305
|
new(lat: parts[0].to_f, lng: parts[1].to_f, alt: parts[2].to_f)
|
|
306
306
|
end
|
|
307
307
|
|
|
308
|
+
def valid?
|
|
309
|
+
@lat.finite? && @lng.finite? && @alt.finite? &&
|
|
310
|
+
@lat >= -90 && @lat <= 90 &&
|
|
311
|
+
@lng >= -180 && @lng <= 180
|
|
312
|
+
end
|
|
313
|
+
|
|
308
314
|
def ==(other)
|
|
309
315
|
return false unless other.is_a?(LLA)
|
|
310
316
|
|
|
@@ -325,7 +331,7 @@ module Geodetic
|
|
|
325
331
|
raise ArgumentError, "Longitude must be between -180 and 180 degrees" if @lng < -180 || @lng > 180
|
|
326
332
|
end
|
|
327
333
|
|
|
328
|
-
Coordinate.register_class(self)
|
|
334
|
+
Coordinate.register_class(self, hash_conversion_style: :no_datum)
|
|
329
335
|
end
|
|
330
336
|
end
|
|
331
337
|
end
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
# decode_bounds(code_string) → { min_lat:, max_lat:, min_lng:, max_lng: }
|
|
13
13
|
# validate_code!(string) → raises ArgumentError or nil
|
|
14
14
|
# set_code(normalized_string) → sets the internal ivar (@geohash, @code, etc.)
|
|
15
|
-
# code_value → returns the internal ivar
|
|
15
|
+
# code_value → returns the internal ivar (protected)
|
|
16
16
|
# self.default_precision → Integer
|
|
17
17
|
# self.hash_system_name → Symbol (:gh, :gh36, :ham, :olc)
|
|
18
18
|
#
|
|
@@ -52,6 +52,7 @@ module Geodetic
|
|
|
52
52
|
class_name: klass.name.split('::').last,
|
|
53
53
|
default_precision: default_precision
|
|
54
54
|
}
|
|
55
|
+
Coordinate.register_class(klass)
|
|
55
56
|
end
|
|
56
57
|
|
|
57
58
|
# Called once after all spatial hash subclasses are loaded.
|
|
@@ -285,7 +286,6 @@ module Geodetic
|
|
|
285
286
|
code_value == other.code_value
|
|
286
287
|
end
|
|
287
288
|
|
|
288
|
-
alias_method :to_slug, :to_s
|
|
289
289
|
|
|
290
290
|
# Returns all 8 neighboring cells
|
|
291
291
|
# Keys: :N, :S, :E, :W, :NE, :NW, :SE, :SW
|
data/lib/geodetic/coordinate.rb
CHANGED
|
@@ -3,13 +3,39 @@
|
|
|
3
3
|
module Geodetic
|
|
4
4
|
module Coordinate
|
|
5
5
|
# Registry for coordinate classes — each class registers itself at load time
|
|
6
|
+
# Each entry is [klass, options_hash] where options may include :hash_conversion_style
|
|
6
7
|
@registered_classes = []
|
|
8
|
+
@finalized = false
|
|
7
9
|
|
|
8
10
|
class << self
|
|
9
11
|
attr_reader :registered_classes
|
|
10
12
|
|
|
11
|
-
def
|
|
12
|
-
@registered_classes
|
|
13
|
+
def systems
|
|
14
|
+
@registered_classes.map(&:first).freeze
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def register_class(klass, hash_conversion_style: nil)
|
|
18
|
+
@registered_classes << [klass, { hash_conversion_style: hash_conversion_style }]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def finalize!
|
|
22
|
+
raise "Geodetic::Coordinate already finalized!" if @finalized
|
|
23
|
+
@finalized = true
|
|
24
|
+
|
|
25
|
+
# Phase 1: Generate cross-hash conversion methods between spatial hash subclasses
|
|
26
|
+
SpatialHash.finalize_cross_hash_conversions!
|
|
27
|
+
|
|
28
|
+
# Phase 2: Generate hash conversion methods on non-hash coordinate classes
|
|
29
|
+
@registered_classes.each do |klass, opts|
|
|
30
|
+
next unless opts[:hash_conversion_style]
|
|
31
|
+
SpatialHash.generate_hash_conversions_for(klass, style: opts[:hash_conversion_style])
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Phase 3: Include distance/bearing mixins in all registered coordinate classes
|
|
35
|
+
@registered_classes.each do |klass, _opts|
|
|
36
|
+
klass.include(DistanceMethods)
|
|
37
|
+
klass.include(BearingMethods)
|
|
38
|
+
end
|
|
13
39
|
end
|
|
14
40
|
end
|
|
15
41
|
end
|
|
@@ -257,27 +283,5 @@ module Geodetic
|
|
|
257
283
|
end
|
|
258
284
|
end
|
|
259
285
|
|
|
260
|
-
#
|
|
261
|
-
Geodetic::Coordinate
|
|
262
|
-
|
|
263
|
-
# Generate hash conversion methods (to_gh, from_gh, etc.) on non-hash coordinate classes
|
|
264
|
-
sh = Geodetic::Coordinate::SpatialHash
|
|
265
|
-
sh.generate_hash_conversions_for(Geodetic::Coordinate::LLA, style: :no_datum)
|
|
266
|
-
sh.generate_hash_conversions_for(Geodetic::Coordinate::ECEF, style: :no_datum)
|
|
267
|
-
sh.generate_hash_conversions_for(Geodetic::Coordinate::UTM, style: :no_datum)
|
|
268
|
-
sh.generate_hash_conversions_for(Geodetic::Coordinate::WebMercator, style: :no_datum)
|
|
269
|
-
sh.generate_hash_conversions_for(Geodetic::Coordinate::BNG, style: :with_datum)
|
|
270
|
-
sh.generate_hash_conversions_for(Geodetic::Coordinate::UPS, style: :with_datum)
|
|
271
|
-
sh.generate_hash_conversions_for(Geodetic::Coordinate::MGRS, style: :with_datum_and_precision)
|
|
272
|
-
sh.generate_hash_conversions_for(Geodetic::Coordinate::USNG, style: :with_datum_and_precision)
|
|
273
|
-
|
|
274
|
-
# Include distance/bearing mixins in all registered coordinate classes
|
|
275
|
-
ALL_COORD_CLASSES = Geodetic::Coordinate.registered_classes.freeze
|
|
276
|
-
|
|
277
|
-
ALL_COORD_CLASSES.each do |klass|
|
|
278
|
-
klass.include(Geodetic::Coordinate::DistanceMethods)
|
|
279
|
-
klass.include(Geodetic::Coordinate::BearingMethods)
|
|
280
|
-
end
|
|
281
|
-
|
|
282
|
-
# GCS is a convenience alias for Geodetic::Coordinate, available after require "geodetic"
|
|
283
|
-
GCS = Geodetic::Coordinate
|
|
286
|
+
# All classes loaded and registered — finalize conversions and mixins
|
|
287
|
+
Geodetic::Coordinate.finalize!
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Geodetic
|
|
4
|
+
class Feature
|
|
5
|
+
attr_accessor :label, :geometry, :metadata
|
|
6
|
+
|
|
7
|
+
def initialize(label:, geometry:, metadata: {})
|
|
8
|
+
@label = label
|
|
9
|
+
@geometry = geometry
|
|
10
|
+
@metadata = metadata
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def distance_to(other)
|
|
14
|
+
if @geometry.is_a?(Path)
|
|
15
|
+
@geometry.distance_to(other)
|
|
16
|
+
else
|
|
17
|
+
resolve_point.distance_to(resolve_point_from(other))
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def bearing_to(other)
|
|
22
|
+
if @geometry.is_a?(Path)
|
|
23
|
+
@geometry.bearing_to(other)
|
|
24
|
+
else
|
|
25
|
+
resolve_point.bearing_to(resolve_point_from(other))
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def to_s
|
|
30
|
+
"#{@label} (#{@geometry})"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def inspect
|
|
34
|
+
"#<Geodetic::Feature name=#{@label.inspect} geometry=#{@geometry.inspect} metadata=#{@metadata.inspect}>"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def resolve_point
|
|
40
|
+
@geometry.respond_to?(:centroid) ? @geometry.centroid : @geometry
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def resolve_point_from(other)
|
|
44
|
+
case other
|
|
45
|
+
when Feature
|
|
46
|
+
other.send(:resolve_point)
|
|
47
|
+
else
|
|
48
|
+
other.respond_to?(:centroid) ? other.centroid : other
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -107,14 +107,14 @@ module Geodetic
|
|
|
107
107
|
raise ArgumentError, "Unknown vertical datum: #{to_datum}" unless to_info
|
|
108
108
|
|
|
109
109
|
if from_info[:type] == 'orthometric'
|
|
110
|
-
geoid_model = GeoidHeight.
|
|
110
|
+
geoid_model = GeoidHeight.for(from_info[:reference_geoid])
|
|
111
111
|
ellipsoidal_height = geoid_model.orthometric_to_ellipsoidal(lat, lng, height)
|
|
112
112
|
else
|
|
113
113
|
ellipsoidal_height = height
|
|
114
114
|
end
|
|
115
115
|
|
|
116
116
|
if to_info[:type] == 'orthometric'
|
|
117
|
-
geoid_model = GeoidHeight.
|
|
117
|
+
geoid_model = GeoidHeight.for(to_info[:reference_geoid])
|
|
118
118
|
geoid_model.ellipsoidal_to_orthometric(lat, lng, ellipsoidal_height)
|
|
119
119
|
else
|
|
120
120
|
ellipsoidal_height
|
|
@@ -175,6 +175,11 @@ module Geodetic
|
|
|
175
175
|
end
|
|
176
176
|
end
|
|
177
177
|
|
|
178
|
+
def self.for(geoid_model = 'EGM2008')
|
|
179
|
+
@instances ||= {}
|
|
180
|
+
@instances[geoid_model] ||= new(geoid_model: geoid_model)
|
|
181
|
+
end
|
|
182
|
+
|
|
178
183
|
def self.available_models
|
|
179
184
|
GEOID_MODELS.keys
|
|
180
185
|
end
|
|
@@ -271,7 +276,7 @@ module Geodetic
|
|
|
271
276
|
def convert_height_datum(from_datum, to_datum, geoid_model = 'EGM2008')
|
|
272
277
|
return self unless respond_to?(:lat) && respond_to?(:lng) && respond_to?(:alt)
|
|
273
278
|
|
|
274
|
-
geoid = GeoidHeight.
|
|
279
|
+
geoid = GeoidHeight.for(geoid_model)
|
|
275
280
|
new_height = geoid.convert_vertical_datum(self.lat, self.lng, self.alt, from_datum, to_datum)
|
|
276
281
|
|
|
277
282
|
self.class.new(lat: self.lat, lng: self.lng, alt: new_height)
|
|
@@ -280,19 +285,19 @@ module Geodetic
|
|
|
280
285
|
def geoid_height(geoid_model = 'EGM2008')
|
|
281
286
|
return nil unless respond_to?(:lat) && respond_to?(:lng)
|
|
282
287
|
|
|
283
|
-
geoid = GeoidHeight.
|
|
288
|
+
geoid = GeoidHeight.for(geoid_model)
|
|
284
289
|
geoid.geoid_height_at(self.lat, self.lng)
|
|
285
290
|
end
|
|
286
291
|
|
|
287
292
|
def orthometric_height(geoid_model = 'EGM2008')
|
|
288
293
|
return nil unless respond_to?(:alt) && respond_to?(:lat) && respond_to?(:lng)
|
|
289
294
|
|
|
290
|
-
geoid = GeoidHeight.
|
|
295
|
+
geoid = GeoidHeight.for(geoid_model)
|
|
291
296
|
geoid.ellipsoidal_to_orthometric(self.lat, self.lng, self.alt)
|
|
292
297
|
end
|
|
293
298
|
|
|
294
299
|
def self.from_orthometric_height(lat, lng, orthometric_height, geoid_model = 'EGM2008')
|
|
295
|
-
geoid = GeoidHeight.
|
|
300
|
+
geoid = GeoidHeight.for(geoid_model)
|
|
296
301
|
geoid.orthometric_to_ellipsoidal(lat, lng, orthometric_height)
|
|
297
302
|
end
|
|
298
303
|
end
|