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.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +28 -4
  3. data/README.md +19 -5
  4. data/docs/coordinate-systems/bng.md +5 -5
  5. data/docs/coordinate-systems/ecef.md +23 -23
  6. data/docs/coordinate-systems/enu.md +3 -3
  7. data/docs/coordinate-systems/gars.md +246 -0
  8. data/docs/coordinate-systems/georef.md +221 -0
  9. data/docs/coordinate-systems/gh.md +7 -7
  10. data/docs/coordinate-systems/gh36.md +6 -6
  11. data/docs/coordinate-systems/h3.md +312 -0
  12. data/docs/coordinate-systems/ham.md +6 -6
  13. data/docs/coordinate-systems/index.md +40 -34
  14. data/docs/coordinate-systems/lla.md +26 -26
  15. data/docs/coordinate-systems/mgrs.md +3 -3
  16. data/docs/coordinate-systems/ned.md +3 -3
  17. data/docs/coordinate-systems/olc.md +6 -6
  18. data/docs/coordinate-systems/state-plane.md +2 -2
  19. data/docs/coordinate-systems/ups.md +4 -4
  20. data/docs/coordinate-systems/usng.md +2 -2
  21. data/docs/coordinate-systems/utm.md +23 -23
  22. data/docs/coordinate-systems/web-mercator.md +7 -7
  23. data/docs/getting-started/installation.md +17 -17
  24. data/docs/getting-started/quick-start.md +8 -8
  25. data/docs/index.md +22 -19
  26. data/docs/reference/areas.md +15 -15
  27. data/docs/reference/conversions.md +31 -31
  28. data/docs/reference/geoid-height.md +5 -5
  29. data/docs/reference/serialization.md +44 -44
  30. data/examples/01_basic_conversions.rb +10 -10
  31. data/examples/02_all_coordinate_systems.rb +24 -24
  32. data/lib/geodetic/areas/circle.rb +1 -1
  33. data/lib/geodetic/areas/polygon.rb +2 -2
  34. data/lib/geodetic/areas/rectangle.rb +6 -6
  35. data/lib/geodetic/{coordinates → coordinate}/bng.rb +3 -37
  36. data/lib/geodetic/{coordinates → coordinate}/ecef.rb +3 -33
  37. data/lib/geodetic/{coordinates → coordinate}/enu.rb +30 -1
  38. data/lib/geodetic/coordinate/gars.rb +233 -0
  39. data/lib/geodetic/coordinate/georef.rb +204 -0
  40. data/lib/geodetic/coordinate/gh.rb +161 -0
  41. data/lib/geodetic/{coordinates → coordinate}/gh36.rb +28 -187
  42. data/lib/geodetic/coordinate/h3.rb +413 -0
  43. data/lib/geodetic/coordinate/ham.rb +226 -0
  44. data/lib/geodetic/{coordinates → coordinate}/lla.rb +31 -1
  45. data/lib/geodetic/{coordinates → coordinate}/mgrs.rb +3 -33
  46. data/lib/geodetic/{coordinates → coordinate}/ned.rb +30 -1
  47. data/lib/geodetic/{coordinates → coordinate}/olc.rb +19 -225
  48. data/lib/geodetic/coordinate/spatial_hash.rb +342 -0
  49. data/lib/geodetic/{coordinates → coordinate}/state_plane.rb +30 -1
  50. data/lib/geodetic/{coordinates → coordinate}/ups.rb +3 -37
  51. data/lib/geodetic/{coordinates → coordinate}/usng.rb +3 -33
  52. data/lib/geodetic/{coordinates → coordinate}/utm.rb +3 -33
  53. data/lib/geodetic/{coordinates → coordinate}/web_mercator.rb +3 -33
  54. data/lib/geodetic/{coordinates.rb → coordinate.rb} +62 -45
  55. data/lib/geodetic/version.rb +1 -1
  56. data/lib/geodetic.rb +1 -1
  57. data/spatial_hash_idea.md +241 -0
  58. metadata +29 -20
  59. data/lib/geodetic/coordinates/gh.rb +0 -372
  60. 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 Coordinates
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 Coordinates
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 Coordinates
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 Coordinates
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 Coordinates
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