geodetic 0.0.1

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 (59) hide show
  1. checksums.yaml +7 -0
  2. data/.envrc +1 -0
  3. data/.github/workflows/deploy-github-pages.yml +52 -0
  4. data/CHANGELOG.md +15 -0
  5. data/COMMITS.md +196 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +471 -0
  8. data/Rakefile +8 -0
  9. data/docs/coordinate-systems/bng.md +60 -0
  10. data/docs/coordinate-systems/ecef.md +215 -0
  11. data/docs/coordinate-systems/enu.md +77 -0
  12. data/docs/coordinate-systems/gh36.md +192 -0
  13. data/docs/coordinate-systems/index.md +93 -0
  14. data/docs/coordinate-systems/lla.md +304 -0
  15. data/docs/coordinate-systems/mgrs.md +81 -0
  16. data/docs/coordinate-systems/ned.md +83 -0
  17. data/docs/coordinate-systems/state-plane.md +60 -0
  18. data/docs/coordinate-systems/ups.md +53 -0
  19. data/docs/coordinate-systems/usng.md +74 -0
  20. data/docs/coordinate-systems/utm.md +257 -0
  21. data/docs/coordinate-systems/web-mercator.md +67 -0
  22. data/docs/getting-started/installation.md +65 -0
  23. data/docs/getting-started/quick-start.md +175 -0
  24. data/docs/index.md +58 -0
  25. data/docs/reference/areas.md +195 -0
  26. data/docs/reference/conversions.md +351 -0
  27. data/docs/reference/datums.md +134 -0
  28. data/docs/reference/geoid-height.md +182 -0
  29. data/docs/reference/serialization.md +252 -0
  30. data/examples/01_basic_conversions.rb +187 -0
  31. data/examples/02_all_coordinate_systems.rb +310 -0
  32. data/examples/03_distance_calculations.rb +224 -0
  33. data/examples/04_bearing_calculations.rb +236 -0
  34. data/lib/geodetic/areas/circle.rb +29 -0
  35. data/lib/geodetic/areas/polygon.rb +57 -0
  36. data/lib/geodetic/areas/rectangle.rb +55 -0
  37. data/lib/geodetic/areas.rb +5 -0
  38. data/lib/geodetic/bearing.rb +94 -0
  39. data/lib/geodetic/coordinates/bng.rb +366 -0
  40. data/lib/geodetic/coordinates/ecef.rb +229 -0
  41. data/lib/geodetic/coordinates/enu.rb +244 -0
  42. data/lib/geodetic/coordinates/gh36.rb +384 -0
  43. data/lib/geodetic/coordinates/lla.rb +268 -0
  44. data/lib/geodetic/coordinates/mgrs.rb +317 -0
  45. data/lib/geodetic/coordinates/ned.rb +246 -0
  46. data/lib/geodetic/coordinates/state_plane.rb +451 -0
  47. data/lib/geodetic/coordinates/ups.rb +325 -0
  48. data/lib/geodetic/coordinates/usng.rb +274 -0
  49. data/lib/geodetic/coordinates/utm.rb +261 -0
  50. data/lib/geodetic/coordinates/web_mercator.rb +242 -0
  51. data/lib/geodetic/coordinates.rb +260 -0
  52. data/lib/geodetic/datum.rb +62 -0
  53. data/lib/geodetic/distance.rb +146 -0
  54. data/lib/geodetic/geoid_height.rb +299 -0
  55. data/lib/geodetic/version.rb +5 -0
  56. data/lib/geodetic.rb +13 -0
  57. data/mkdocs.yml +140 -0
  58. data/sig/geodetic.rbs +4 -0
  59. metadata +104 -0
@@ -0,0 +1,215 @@
1
+ # Geodetic::Coordinates::ECEF
2
+
3
+ Earth-Centered, Earth-Fixed -- a Cartesian coordinate system with its origin at the center of mass of the Earth. The X axis points toward the intersection of the Prime Meridian and the Equator, the Y axis points toward 90 degrees East longitude on the Equator, and the Z axis points toward the North Pole. All values are in meters.
4
+
5
+ ECEF is useful for satellite positioning, radar tracking, and any application requiring a simple Cartesian frame tied to the rotating Earth.
6
+
7
+ ## Constructor
8
+
9
+ ```ruby
10
+ Geodetic::Coordinates::ECEF.new(x: 0.0, y: 0.0, z: 0.0)
11
+ ```
12
+
13
+ | Parameter | Type | Default | Description |
14
+ |-----------|-------|---------|---------------------------------|
15
+ | `x` | Float | `0.0` | X coordinate in meters |
16
+ | `y` | Float | `0.0` | Y coordinate in meters |
17
+ | `z` | Float | `0.0` | Z coordinate in meters |
18
+
19
+ All values are coerced to `Float` via `.to_f`. There is no range validation -- any real-valued triple is accepted.
20
+
21
+ ## Attributes
22
+
23
+ | Attribute | Access |
24
+ |-----------|------------|
25
+ | `x` | read/write |
26
+ | `y` | read/write |
27
+ | `z` | read/write |
28
+
29
+ ## Conversions
30
+
31
+ All conversion methods accept an optional `datum` parameter (defaults to `Geodetic::WGS84`).
32
+
33
+ ### to_lla(datum = WGS84)
34
+
35
+ Converts to geodetic Latitude, Longitude, Altitude coordinates using an iterative algorithm. The iteration converges when both latitude and altitude changes are below `1e-12`, with a maximum of 100 iterations.
36
+
37
+ ```ruby
38
+ ecef = Geodetic::Coordinates::ECEF.new(x: 1130730.0, y: -4828583.0, z: 3991570.0)
39
+ lla = ecef.to_lla
40
+ # => Geodetic::Coordinates::LLA
41
+ ```
42
+
43
+ ### ECEF.from_lla(lla, datum = WGS84)
44
+
45
+ Creates an ECEF from an LLA instance. Raises `ArgumentError` if the argument is not an `LLA`.
46
+
47
+ ```ruby
48
+ ecef = Geodetic::Coordinates::ECEF.from_lla(lla)
49
+ ```
50
+
51
+ ### to_utm(datum = WGS84)
52
+
53
+ Converts to Universal Transverse Mercator coordinates. Internally converts to LLA first, then to UTM.
54
+
55
+ ```ruby
56
+ utm = ecef.to_utm
57
+ # => Geodetic::Coordinates::UTM
58
+ ```
59
+
60
+ ### ECEF.from_utm(utm, datum = WGS84)
61
+
62
+ Creates an ECEF from a UTM instance. Raises `ArgumentError` if the argument is not a `UTM`.
63
+
64
+ ### to_enu(reference_ecef, reference_lla = nil)
65
+
66
+ Converts to East-North-Up local tangent plane coordinates relative to a reference ECEF position. If `reference_lla` is not provided, it is computed from `reference_ecef` via `to_lla`.
67
+
68
+ ```ruby
69
+ ref_ecef = Geodetic::Coordinates::ECEF.new(x: 1130730.0, y: -4828583.0, z: 3991570.0)
70
+ point_ecef = Geodetic::Coordinates::ECEF.new(x: 1130740.0, y: -4828573.0, z: 3991580.0)
71
+ enu = point_ecef.to_enu(ref_ecef)
72
+ # => Geodetic::Coordinates::ENU
73
+ ```
74
+
75
+ Raises `ArgumentError` if `reference_ecef` is not an `ECEF`.
76
+
77
+ ### ECEF.from_enu(enu, reference_ecef, reference_lla = nil)
78
+
79
+ Creates an ECEF from an ENU instance and a reference ECEF origin. Raises `ArgumentError` if the arguments are not the expected types.
80
+
81
+ ### to_ned(reference_ecef, reference_lla = nil)
82
+
83
+ Converts to North-East-Down local tangent plane coordinates. Internally converts to ENU first, then swaps axes to NED.
84
+
85
+ ```ruby
86
+ ned = point_ecef.to_ned(ref_ecef)
87
+ # => Geodetic::Coordinates::NED
88
+ ```
89
+
90
+ ### ECEF.from_ned(ned, reference_ecef, reference_lla = nil)
91
+
92
+ Creates an ECEF from a NED instance and a reference ECEF origin. Raises `ArgumentError` if the arguments are not the expected types.
93
+
94
+ ## Serialization
95
+
96
+ ### to_s
97
+
98
+ Returns a comma-separated string of `x, y, z`.
99
+
100
+ ```ruby
101
+ ecef = Geodetic::Coordinates::ECEF.new(x: 1130730.0, y: -4828583.0, z: 3991570.0)
102
+ ecef.to_s
103
+ # => "1130730.0, -4828583.0, 3991570.0"
104
+ ```
105
+
106
+ ### to_a
107
+
108
+ Returns a three-element array `[x, y, z]`.
109
+
110
+ ```ruby
111
+ ecef.to_a
112
+ # => [1130730.0, -4828583.0, 3991570.0]
113
+ ```
114
+
115
+ ### ECEF.from_string(string)
116
+
117
+ Parses a comma-separated string into an ECEF.
118
+
119
+ ```ruby
120
+ ecef = Geodetic::Coordinates::ECEF.from_string("1130730.0, -4828583.0, 3991570.0")
121
+ ```
122
+
123
+ ### ECEF.from_array(array)
124
+
125
+ Creates an ECEF from a three-element array `[x, y, z]`.
126
+
127
+ ```ruby
128
+ ecef = Geodetic::Coordinates::ECEF.from_array([1130730.0, -4828583.0, 3991570.0])
129
+ ```
130
+
131
+ ## Additional Methods
132
+
133
+ ### ==(other)
134
+
135
+ Compares two ECEF instances for approximate equality. Returns `true` if the absolute difference for each of `x`, `y`, and `z` is `<= 1e-6` meters. Returns `false` if `other` is not an `ECEF`.
136
+
137
+ ```ruby
138
+ a = Geodetic::Coordinates::ECEF.new(x: 1130730.0, y: -4828583.0, z: 3991570.0)
139
+ b = Geodetic::Coordinates::ECEF.new(x: 1130730.0, y: -4828583.0, z: 3991570.0)
140
+ a == b
141
+ # => true
142
+ ```
143
+
144
+ ### distance_to(other, *others)
145
+
146
+ Computes the Vincenty great-circle distance to one or more other coordinates. Accepts any coordinate type (coordinates are converted to LLA internally). Returns a `Distance` for a single target or an Array of `Distance` objects for multiple targets (radial distances from the receiver).
147
+
148
+ ```ruby
149
+ a = Geodetic::Coordinates::ECEF.new(x: 1130730.0, y: -4828583.0, z: 3991570.0)
150
+ b = Geodetic::Coordinates::ECEF.new(x: 1130740.0, y: -4828573.0, z: 3991580.0)
151
+ a.distance_to(b)
152
+ # => Distance (meters, great-circle distance)
153
+ ```
154
+
155
+ ### straight_line_distance_to(other, *others)
156
+
157
+ Computes the Euclidean (straight-line) distance between two points in ECEF space. Accepts any coordinate type (coordinates are converted to ECEF internally). Returns a `Distance` for a single target or an Array of `Distance` objects for multiple targets.
158
+
159
+ ```ruby
160
+ a.straight_line_distance_to(b)
161
+ # => Distance (17.320508075688775 m)
162
+ ```
163
+
164
+ ## Code Examples
165
+
166
+ ### Round-trip conversion
167
+
168
+ ```ruby
169
+ require 'geodetic'
170
+
171
+ # Create an ECEF coordinate
172
+ ecef = Geodetic::Coordinates::ECEF.new(x: 1130730.0, y: -4828583.0, z: 3991570.0)
173
+
174
+ # Convert to LLA and back
175
+ lla = ecef.to_lla
176
+ ecef_roundtrip = lla.to_ecef
177
+ ecef == ecef_roundtrip
178
+ # => true
179
+ ```
180
+
181
+ ### Distance between two points
182
+
183
+ ```ruby
184
+ station_a = Geodetic::Coordinates::ECEF.new(x: 1130730.0, y: -4828583.0, z: 3991570.0)
185
+ station_b = Geodetic::Coordinates::ECEF.new(x: 1131000.0, y: -4828300.0, z: 3991800.0)
186
+
187
+ # Great-circle distance (Vincenty)
188
+ distance = station_a.distance_to(station_b)
189
+ puts "Great-circle distance: #{distance.meters} meters"
190
+
191
+ # Straight-line (Euclidean) distance
192
+ straight = station_a.straight_line_distance_to(station_b)
193
+ puts "Straight-line distance: #{straight.meters} meters"
194
+ ```
195
+
196
+ ### Local tangent plane from ECEF
197
+
198
+ ```ruby
199
+ origin = Geodetic::Coordinates::ECEF.new(x: 1130730.0, y: -4828583.0, z: 3991570.0)
200
+ target = Geodetic::Coordinates::ECEF.new(x: 1130740.0, y: -4828573.0, z: 3991580.0)
201
+
202
+ # Provide reference LLA to avoid recomputing it
203
+ ref_lla = origin.to_lla
204
+
205
+ enu = target.to_enu(origin, ref_lla)
206
+ ned = target.to_ned(origin, ref_lla)
207
+ ```
208
+
209
+ ### Using a non-default datum
210
+
211
+ ```ruby
212
+ clarke66 = Geodetic::Datum.new(name: 'CLARKE_1866')
213
+ ecef = Geodetic::Coordinates::ECEF.new(x: 1130730.0, y: -4828583.0, z: 3991570.0)
214
+ lla = ecef.to_lla(clarke66)
215
+ ```
@@ -0,0 +1,77 @@
1
+ # Geodetic::Coordinates::ENU - East, North, Up
2
+
3
+ ## Overview
4
+
5
+ ENU (East, North, Up) is a local tangent plane coordinate system. It defines positions relative to a local reference point on the Earth's surface, with axes pointing East, North, and Up.
6
+
7
+ ## Constructor
8
+
9
+ ```ruby
10
+ ENU.new(e: 0.0, n: 0.0, u: 0.0)
11
+ ```
12
+
13
+ All parameters are in **meters**.
14
+
15
+ ## Attribute Aliases
16
+
17
+ | Primary | Alias |
18
+ |---------|---------|
19
+ | `e` | `east` |
20
+ | `n` | `north` |
21
+ | `u` | `up` |
22
+
23
+ ## Reference Point
24
+
25
+ ENU is a local coordinate system. Conversions to and from global coordinate systems (such as LLA or ECEF) require a **reference point** specified as an LLA coordinate. This reference point defines the origin of the local tangent plane.
26
+
27
+ The one exception is the conversion between ENU and NED, which does not require a reference point.
28
+
29
+ ## Conversions
30
+
31
+ ### Direct (no reference point needed)
32
+
33
+ - **ENU <-> NED** — A simple axis remap: `ENU(e, n, u)` corresponds to `NED(n, e, -u)`.
34
+
35
+ ### Via reference point
36
+
37
+ - **ENU -> LLA** — Requires a reference LLA.
38
+ - **ENU -> ECEF** — Requires a reference LLA.
39
+
40
+ ## Methods
41
+
42
+ | Method | Description |
43
+ |---------------------------------|--------------------------------------------------------------------|
44
+ | `horizontal_distance_to(other)` | Horizontal (E-N plane) distance to another ENU point (meters) |
45
+ | `local_bearing_to(other)` | Bearing from this point to another ENU point (degrees from north, 0-360) |
46
+ | `distance_to_origin` | Euclidean distance from this point to the origin (meters) |
47
+ | `bearing_from_origin` | Bearing from the origin to this point (degrees from north, 0-360) |
48
+ | `horizontal_distance_to_origin` | Horizontal distance from this point to the origin (meters) |
49
+
50
+ ### Universal Distance and Bearing Methods
51
+
52
+ ENU is a relative coordinate system. The universal `distance_to`, `straight_line_distance_to`, `bearing_to`, and `elevation_to` methods raise `ArgumentError` because ENU cannot be converted to an absolute system without a reference point. Convert to an absolute system first:
53
+
54
+ ```ruby
55
+ ref = Geodetic::Coordinates::LLA.new(lat: 47.62, lng: -122.35, alt: 0.0)
56
+ lla = enu.to_lla(ref)
57
+ lla.distance_to(other_lla) # Vincenty great-circle distance
58
+ lla.bearing_to(other_lla) # Great-circle forward azimuth (Bearing object)
59
+ ```
60
+
61
+ ## Bearing Convention
62
+
63
+ Bearing is measured in **degrees from north**, clockwise, in the range **0-360**.
64
+
65
+ ## Example
66
+
67
+ ```ruby
68
+ point = Geodetic::Coordinates::ENU.new(e: 100.0, n: 200.0, u: 50.0)
69
+
70
+ point.east # => 100.0
71
+ point.north # => 200.0
72
+ point.up # => 50.0
73
+
74
+ point.distance_to_origin # => Euclidean distance in meters
75
+ point.bearing_from_origin # => Bearing in degrees from north
76
+ point.horizontal_distance_to_origin # => Horizontal distance in meters
77
+ ```
@@ -0,0 +1,192 @@
1
+ # Geodetic::Coordinates::GH36
2
+
3
+ ## Geohash-36
4
+
5
+ Geohash-36 is a hierarchical spatial hashing algorithm that encodes latitude/longitude into a compact, URL-friendly string using a case-sensitive 36-character alphabet. It uses a 6x6 grid subdivision (radix-36) providing higher precision per character than standard Geohash (radix-32).
6
+
7
+ Character set: `23456789bBCdDFgGhHjJKlLMnNPqQrRtTVWX`
8
+ (avoids vowels, vowel-like numbers, and ambiguous characters like 0/O, 1/I/l)
9
+
10
+ GH36 is a **2D coordinate system** (no altitude). Conversions to/from other systems go through LLA as the intermediary. Each geohash string represents a rectangular cell; the coordinate's point value is the cell's midpoint.
11
+
12
+ ## Constructor
13
+
14
+ ```ruby
15
+ # From a geohash string
16
+ coord = Geodetic::Coordinates::GH36.new("bdrdC26BqH")
17
+
18
+ # From any coordinate (converts via LLA)
19
+ coord = Geodetic::Coordinates::GH36.new(lla_coord)
20
+ coord = Geodetic::Coordinates::GH36.new(utm_coord, precision: 8)
21
+ ```
22
+
23
+ | Parameter | Type | Default | Description |
24
+ |-------------|-------------------|---------|----------------------------------------------|
25
+ | `source` | String or Coord | — | A geohash string or any coordinate object |
26
+ | `precision` | Integer | 10 | Hash length (ignored when source is a String) |
27
+
28
+ Raises `ArgumentError` if the source is an empty string, contains invalid characters, or is not a recognized coordinate type.
29
+
30
+ ## Attributes
31
+
32
+ | Attribute | Type | Access | Description |
33
+ |-----------|--------|-----------|---------------------------------|
34
+ | `geohash` | String | read-only | The geohash-36 encoded string |
35
+
36
+ GH36 is **immutable** — there are no setter methods.
37
+
38
+ ## Precision
39
+
40
+ The precision (hash length) determines the size of the cell:
41
+
42
+ | Length | Approximate Resolution |
43
+ |--------|----------------------|
44
+ | 5 | ~1.4 km |
45
+ | 8 | ~6.5 m |
46
+ | 10 | ~0.3 m (default) |
47
+
48
+ ```ruby
49
+ coord.precision # => 10 (hash length)
50
+ coord.precision_in_meters # => { lat: 0.31, lng: 0.62 }
51
+ ```
52
+
53
+ Longer hashes yield finer precision. Longitude precision is always coarser than latitude (cells are wider than tall).
54
+
55
+ ## Conversions
56
+
57
+ All conversions chain through LLA. The datum parameter defaults to `Geodetic::WGS84`.
58
+
59
+ ### Instance Methods
60
+
61
+ ```ruby
62
+ coord.to_lla # => LLA (midpoint of the cell)
63
+ coord.to_ecef
64
+ coord.to_utm
65
+ coord.to_enu(reference_lla)
66
+ coord.to_ned(reference_lla)
67
+ coord.to_mgrs
68
+ coord.to_usng
69
+ coord.to_web_mercator
70
+ coord.to_ups
71
+ coord.to_state_plane(zone_code)
72
+ coord.to_bng
73
+ coord.to_gh36 # identity (via mixin)
74
+ ```
75
+
76
+ ### Class Methods
77
+
78
+ ```ruby
79
+ GH36.from_lla(lla_coord)
80
+ GH36.from_ecef(ecef_coord)
81
+ GH36.from_utm(utm_coord)
82
+ GH36.from_web_mercator(wm_coord)
83
+ # ... and all other coordinate systems
84
+ ```
85
+
86
+ ### LLA Convenience Methods
87
+
88
+ ```ruby
89
+ lla = Geodetic::Coordinates::LLA.new(lat: 40.689167, lng: -74.044444)
90
+ gh36 = lla.to_gh36 # default precision 10
91
+ gh36 = lla.to_gh36(precision: 5) # custom precision
92
+
93
+ lla = Geodetic::Coordinates::LLA.from_gh36(gh36)
94
+ ```
95
+
96
+ ## Serialization
97
+
98
+ ### `to_s(truncate_to = nil)`
99
+
100
+ Returns the geohash string. An optional integer truncates to that length.
101
+
102
+ ```ruby
103
+ coord = GH36.new("bdrdC26BqH")
104
+ coord.to_s # => "bdrdC26BqH"
105
+ coord.to_s(5) # => "bdrdC"
106
+ ```
107
+
108
+ ### `to_slug`
109
+
110
+ Alias for `to_s`. The geohash is already URL-safe.
111
+
112
+ ### `to_a`
113
+
114
+ Returns `[lat, lng]` of the cell midpoint.
115
+
116
+ ```ruby
117
+ coord.to_a # => [40.689, -74.044]
118
+ ```
119
+
120
+ ### `from_string` / `from_array`
121
+
122
+ ```ruby
123
+ GH36.from_string("bdrdC26BqH") # from geohash string
124
+ GH36.from_array([40.689, -74.044]) # from [lat, lng]
125
+ ```
126
+
127
+ ## Neighbors
128
+
129
+ Returns all 8 adjacent geohash cells as GH36 instances.
130
+
131
+ ```ruby
132
+ coord = GH36.new(LLA.new(lat: 40.0, lng: -74.0))
133
+ neighbors = coord.neighbors
134
+ # => { N: GH36, S: GH36, E: GH36, W: GH36, NE: GH36, NW: GH36, SE: GH36, SW: GH36 }
135
+
136
+ neighbors[:N].to_lla.lat > coord.to_lla.lat # => true
137
+ neighbors[:E].to_lla.lng > coord.to_lla.lng # => true
138
+ ```
139
+
140
+ Neighbor computation propagates carries when the adjustment wraps beyond the matrix edge, recursing on the parent prefix.
141
+
142
+ ## Area
143
+
144
+ The `to_area` method returns the geohash cell as a `Geodetic::Areas::Rectangle`.
145
+
146
+ ```ruby
147
+ area = coord.to_area
148
+ # => Geodetic::Areas::Rectangle
149
+
150
+ area.includes?(coord.to_lla) # => true (midpoint is inside the cell)
151
+ area.nw # => LLA (northwest corner)
152
+ area.se # => LLA (southeast corner)
153
+ ```
154
+
155
+ ## Equality
156
+
157
+ Two GH36 instances are equal if their geohash strings match exactly.
158
+
159
+ ```ruby
160
+ GH36.new("bdrdC26BqH") == GH36.new("bdrdC26BqH") # => true
161
+ GH36.new("bdrdC26BqH") == GH36.new("bdrdC26Bq2") # => false
162
+ ```
163
+
164
+ ## `valid?`
165
+
166
+ Returns `true` if all characters are in the valid Geohash-36 alphabet.
167
+
168
+ ```ruby
169
+ coord.valid? # => true
170
+ ```
171
+
172
+ ## Universal Distance and Bearing Methods
173
+
174
+ GH36 supports all universal distance and bearing methods via the `DistanceMethods` and `BearingMethods` mixins:
175
+
176
+ ```ruby
177
+ a = GH36.new(LLA.new(lat: 40.689167, lng: -74.044444))
178
+ b = GH36.new(LLA.new(lat: 51.504444, lng: -0.086666))
179
+
180
+ a.distance_to(b) # => Distance (~5,570 km)
181
+ a.straight_line_distance_to(b) # => Distance
182
+ a.bearing_to(b) # => Bearing (~51°)
183
+ a.elevation_to(b) # => Float (degrees)
184
+ ```
185
+
186
+ Cross-system distances work too:
187
+
188
+ ```ruby
189
+ utm = seattle_lla.to_utm
190
+ gh36 = GH36.new(portland_lla)
191
+ utm.distance_to(gh36) # => Distance
192
+ ```
@@ -0,0 +1,93 @@
1
+ # Coordinate Systems Overview
2
+
3
+ The Geodetic gem supports 12 coordinate systems organized into six categories. All coordinate classes live under `Geodetic::Coordinates`.
4
+
5
+ ## Global Systems
6
+
7
+ | System | Class | Description |
8
+ |--------|-------|-------------|
9
+ | **LLA** | `Geodetic::Coordinates::LLA` | Latitude, Longitude, Altitude. The most common geographic coordinate system, expressing positions in decimal degrees with altitude in meters. Negative longitude is the Western hemisphere; negative latitude is the Southern hemisphere. |
10
+ | **ECEF** | `Geodetic::Coordinates::ECEF` | Earth-Centered, Earth-Fixed. A Cartesian coordinate system with the origin at the Earth's center of mass. Positions are expressed as X, Y, Z in meters. Commonly used in satellite navigation and aerospace applications. |
11
+ | **UTM** | `Geodetic::Coordinates::UTM` | Universal Transverse Mercator. Divides the Earth into 60 zones (each 6 degrees of longitude), projecting positions as easting/northing in meters within a zone and hemisphere. Covers latitudes 80S to 84N. |
12
+
13
+ ## Local Tangent Plane Systems
14
+
15
+ | System | Class | Description |
16
+ |--------|-------|-------------|
17
+ | **ENU** | `Geodetic::Coordinates::ENU` | East, North, Up. A local tangent plane coordinate system centered on a reference point. Axes point East, North, and Up relative to the reference. Distances are in meters. Used in robotics, surveying, and local navigation. |
18
+ | **NED** | `Geodetic::Coordinates::NED` | North, East, Down. A local tangent plane coordinate system centered on a reference point. Axes point North, East, and Down. Used extensively in aerospace and aviation applications. Mathematically related to ENU by axis reordering and sign inversion. |
19
+
20
+ ## Military and Grid Systems
21
+
22
+ | System | Class | Description |
23
+ |--------|-------|-------------|
24
+ | **MGRS** | `Geodetic::Coordinates::MGRS` | Military Grid Reference System. An alphanumeric system based on UTM that identifies positions using grid zone designator, 100km square identifier, and numeric easting/northing. Variable precision from 10km down to 1m. |
25
+ | **USNG** | `Geodetic::Coordinates::USNG` | United States National Grid. Based on MGRS but formatted with spaces for readability. Used primarily within the United States for emergency services and land management. |
26
+
27
+ ## Web Mapping
28
+
29
+ | System | Class | Description |
30
+ |--------|-------|-------------|
31
+ | **WebMercator** | `Geodetic::Coordinates::WebMercator` | Web Mercator (EPSG:3857). Also known as Pseudo-Mercator or Spherical Mercator. The projection used by Google Maps, OpenStreetMap, and Bing Maps. Positions are X/Y in meters. Latitude is clamped to approximately +/-85.05 degrees. Includes tile and pixel coordinate methods for web mapping applications. |
32
+
33
+ ## Polar
34
+
35
+ | System | Class | Description |
36
+ |--------|-------|-------------|
37
+ | **UPS** | `Geodetic::Coordinates::UPS` | Universal Polar Stereographic. Covers the polar regions not handled by UTM (north of 84N and south of 80S). Uses a stereographic projection centered on each pole with zones Y/Z (north) and A/B (south). |
38
+
39
+ ## Spatial Hashing
40
+
41
+ | System | Class | Description |
42
+ |--------|-------|-------------|
43
+ | **GH36** | `Geodetic::Coordinates::GH36` | Geohash-36. A hierarchical spatial hashing algorithm that encodes latitude/longitude into a compact, URL-friendly string using a case-sensitive 36-character alphabet (radix-36). Each hash represents a rectangular cell; the coordinate value is the cell midpoint. Supports neighbor lookup, area extraction via `to_area`, and configurable precision (default 10 characters for sub-meter resolution). |
44
+
45
+ ## Regional Systems
46
+
47
+ | System | Class | Description |
48
+ |--------|-------|-------------|
49
+ | **StatePlane** | `Geodetic::Coordinates::StatePlane` | State Plane Coordinate System. US state-based coordinate systems using Lambert Conformal Conic or Transverse Mercator projections. Each state has one or more zones with specific parameters. Coordinates are typically in US Survey Feet. |
50
+ | **BNG** | `Geodetic::Coordinates::BNG` | British National Grid. The official coordinate system for Great Britain, based on the OSGB36 datum with the Airy 1830 ellipsoid. Uses a Transverse Mercator projection and an alphanumeric grid reference system (e.g., "TQ 30 80"). |
51
+
52
+ ---
53
+
54
+ ## Conversion Matrix
55
+
56
+ Every coordinate system can convert to every other coordinate system. The table below confirms full interoperability:
57
+
58
+ | From \ To | LLA | ECEF | UTM | ENU | NED | MGRS | USNG | WebMercator | UPS | StatePlane | BNG | GH36 |
59
+ |-----------|-----|------|-----|-----|-----|------|------|-------------|-----|------------|-----|------|
60
+ | **LLA** | -- | Y | Y | Y | Y | Y | Y | Y | Y | Y | Y | Y |
61
+ | **ECEF** | Y | -- | Y | Y | Y | Y | Y | Y | Y | Y | Y | Y |
62
+ | **UTM** | Y | Y | -- | Y | Y | Y | Y | Y | Y | Y | Y | Y |
63
+ | **ENU** | Y | Y | Y | -- | Y | Y | Y | Y | Y | Y | Y | Y |
64
+ | **NED** | Y | Y | Y | Y | -- | Y | Y | Y | Y | Y | Y | Y |
65
+ | **MGRS** | Y | Y | Y | Y | Y | -- | Y | Y | Y | Y | Y | Y |
66
+ | **USNG** | Y | Y | Y | Y | Y | Y | -- | Y | Y | Y | Y | Y |
67
+ | **WebMercator**| Y | Y | Y | Y | Y | Y | Y | -- | Y | Y | Y | Y |
68
+ | **UPS** | Y | Y | Y | Y | Y | Y | Y | Y | -- | Y | Y | Y |
69
+ | **StatePlane** | Y | Y | Y | Y | Y | Y | Y | Y | Y | -- | Y | Y |
70
+ | **BNG** | Y | Y | Y | Y | Y | Y | Y | Y | Y | Y | -- | Y |
71
+ | **GH36** | Y | Y | Y | Y | Y | Y | Y | Y | Y | Y | Y | -- |
72
+
73
+ ## Universal Distance and Bearing Calculations
74
+
75
+ All coordinate systems support universal distance calculations via `distance_to` (Vincenty great-circle) and `straight_line_distance_to` (ECEF Euclidean). These methods work across different coordinate types -- for example, computing the distance from a UTM coordinate to an MGRS coordinate. Class-level methods `GCS.distance_between` and `GCS.straight_line_distance_between` compute consecutive chain distances across a sequence of coordinates.
76
+
77
+ All coordinate systems also support universal bearing calculations via `bearing_to` (great-circle forward azimuth) and `elevation_to` (vertical look angle). These return `Bearing` and `Float` objects respectively. The class-level method `GCS.bearing_between` computes consecutive chain bearings.
78
+
79
+ See the [Conversions Reference](../reference/conversions.md#distance-calculations) for details on distances and [Bearing Calculations](../reference/conversions.md#bearing-calculations) for bearings.
80
+
81
+ > **Note:** ENU and NED are relative systems and must be converted to an absolute system (e.g., LLA) before using universal distance and bearing methods. They retain `local_bearing_to` and `local_elevation_angle_to` for tangent-plane operations.
82
+
83
+ ## Conversion Paths
84
+
85
+ Conversions typically route through **LLA** or **ECEF** as intermediate steps:
86
+
87
+ - **LLA** serves as the universal hub. Most systems convert to LLA first, then from LLA to the target system.
88
+ - **ECEF** is the intermediate for local tangent plane systems (ENU, NED), since the rotation from global Cartesian to local frames is straightforward in ECEF.
89
+ - **ENU and NED** convert between each other directly by reordering axes and inverting the vertical component.
90
+ - **MGRS and USNG** route through UTM, which in turn routes through LLA.
91
+ - **WebMercator, UPS, BNG, StatePlane, and GH36** all convert through LLA.
92
+
93
+ For example, converting from BNG to NED follows the chain: `BNG -> LLA -> ECEF -> ENU -> NED`. The gem handles this automatically when you call a conversion method.