geodetic 0.1.0 → 0.2.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 +23 -4
- data/README.md +5 -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/ham.md +6 -6
- data/docs/coordinate-systems/index.md +38 -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 +21 -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 +21 -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/ham.rb +226 -0
- data/lib/geodetic/{coordinates → coordinate}/lla.rb +23 -1
- data/lib/geodetic/{coordinates → coordinate}/mgrs.rb +3 -33
- data/lib/geodetic/{coordinates → coordinate}/ned.rb +21 -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 +21 -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} +61 -45
- data/lib/geodetic/version.rb +1 -1
- data/lib/geodetic.rb +1 -1
- data/spatial_hash_idea.md +241 -0
- metadata +27 -20
- data/lib/geodetic/coordinates/gh.rb +0 -372
- data/lib/geodetic/coordinates/ham.rb +0 -435
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 782ba82d99727dfa9a075fcdfb9dea89f631ebc78e0e105293745c64f77f9dae
|
|
4
|
+
data.tar.gz: 59e9c844470d77026a9929c8b9ffd01a5d065fd491d8efb67106b0903267c681
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 38543deb9e8d62aa20b4a90c9602b0e97806d7d2f55dd9d308a138b9aea2b033e6f51656a9d247ce750d5abcd5298f170825a3f0e4f013a63198bc467e1f3d8c
|
|
7
|
+
data.tar.gz: fcb62fac876d350bd784d08bb17d45b7c14218a31ff6cfcce4be7e384e3f9a6a2c711ae3731b4a4241e5b34c062a8ddcdc2fb8ce6786ac114ff35d872ad8a7f3
|
data/CHANGELOG.md
CHANGED
|
@@ -10,15 +10,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
10
10
|
|
|
11
11
|
## [Unreleased]
|
|
12
12
|
|
|
13
|
+
|
|
14
|
+
## [0.2.0] - 2026-03-08
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- **2 new coordinate systems** bringing the total from 15 to 17:
|
|
19
|
+
- `Geodetic::Coordinate::GEOREF` — World Geographic Reference System (aviation/military geocode with variable precision from 15-degree tiles to 0.001-minute resolution)
|
|
20
|
+
- `Geodetic::Coordinate::GARS` — Global Area Reference System (NGA standard with 30-minute cells, 15-minute quadrants, and 5-minute keypads)
|
|
21
|
+
- Full cross-system conversions for GEOREF and GARS — all 17 coordinate systems convert to/from every other system (289 conversion paths)
|
|
22
|
+
- Spatial hash features for GEOREF and GARS: `neighbors`, `to_area`, `precision_in_meters`, `to_slug`, configurable precision
|
|
23
|
+
- Documentation pages: `docs/coordinate-systems/georef.md` and `docs/coordinate-systems/gars.md`
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- **Namespace renamed**: `Geodetic::Coordinates` is now `Geodetic::Coordinate` (singular)
|
|
28
|
+
- **SpatialHash base class** (`lib/geodetic/coordinate/spatial_hash.rb`) — GH36, GH, HAM, OLC, GEOREF, and GARS now inherit from a shared base class that provides common behavior (neighbors, to_area, precision_in_meters, serialization, encoding/decoding contract)
|
|
29
|
+
- **Auto-generated hash conversions** — `SpatialHash.generate_hash_conversions_for` replaces 232 lines of hand-written boilerplate `to_gh`/`from_gh`/`to_ham`/`from_ham`/etc. methods across 7 coordinate classes
|
|
30
|
+
- **Self-registration** — each coordinate class calls `Coordinate.register_class(self)` at load time; `ALL_COORD_CLASSES` is populated from the registry instead of a manual list
|
|
31
|
+
|
|
13
32
|
## [0.1.0] - 2026-03-08
|
|
14
33
|
|
|
15
34
|
### Added
|
|
16
35
|
|
|
17
36
|
- **4 new coordinate systems** bringing the total from 11 to 15:
|
|
18
|
-
- `Geodetic::
|
|
19
|
-
- `Geodetic::
|
|
20
|
-
- `Geodetic::
|
|
21
|
-
- `Geodetic::
|
|
37
|
+
- `Geodetic::Coordinate::GH36` — Geohash-36 (radix-36 spatial hash, URL-friendly)
|
|
38
|
+
- `Geodetic::Coordinate::GH` — Geohash base-32 (standard geohash, supported by Elasticsearch, Redis, PostGIS)
|
|
39
|
+
- `Geodetic::Coordinate::HAM` — Maidenhead Locator System (amateur radio grid squares)
|
|
40
|
+
- `Geodetic::Coordinate::OLC` — Open Location Code / Plus Codes (Google's location encoding)
|
|
22
41
|
- **Full cross-system conversions** — all 15 coordinate systems convert to/from every other system (225 conversion paths)
|
|
23
42
|
- **Spatial hash features** for GH36, GH, HAM, and OLC:
|
|
24
43
|
- `neighbors` — returns all 8 adjacent grid cells
|
data/README.md
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
<td width="50%" valign="top">
|
|
14
14
|
<strong>Key Features</strong><br>
|
|
15
15
|
|
|
16
|
-
- <strong>
|
|
16
|
+
- <strong>17 Coordinate Systems</strong> - LLA, ECEF, UTM, ENU, NED, MGRS, USNG, Web Mercator, UPS, State Plane, BNG, GH36, GH, HAM, OLC, GEOREF, GARS<br>
|
|
17
17
|
- <strong>Full Bidirectional Conversions</strong> - Every system converts to and from every other system<br>
|
|
18
18
|
- <strong>Distance Calculations</strong> - Vincenty great-circle and straight-line with unit tracking<br>
|
|
19
19
|
- <strong>Bearing Calculations</strong> - Forward azimuth, back azimuth, compass directions, elevation angles<br>
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
</tr>
|
|
28
28
|
</table>
|
|
29
29
|
|
|
30
|
-
<p>Geodetic enables precise conversion between geodetic coordinate systems in Ruby. All
|
|
30
|
+
<p>Geodetic enables precise conversion between geodetic coordinate systems in Ruby. All 17 coordinate systems support complete bidirectional conversions with high precision. Review the <a href="https://madbomber.github.io/geodetic/">full documentation website</a> and explore the <a href="examples/">runnable examples</a>.</p>
|
|
31
31
|
|
|
32
32
|
## Installation
|
|
33
33
|
|
|
@@ -63,12 +63,12 @@ ned = Coordinates::NED.new(n: 200.0, e: 100.0, d: -50.0)
|
|
|
63
63
|
|
|
64
64
|
### GCS Shorthand
|
|
65
65
|
|
|
66
|
-
`GCS` is a top-level alias for `Geodetic::
|
|
66
|
+
`GCS` is a top-level alias for `Geodetic::Coordinate`, providing a concise way to create and work with coordinates:
|
|
67
67
|
|
|
68
68
|
```ruby
|
|
69
69
|
require "geodetic"
|
|
70
70
|
|
|
71
|
-
# Use GCS as a shorthand for Geodetic::
|
|
71
|
+
# Use GCS as a shorthand for Geodetic::Coordinate
|
|
72
72
|
seattle = GCS::LLA.new(lat: 47.6205, lng: -122.3493, alt: 184.0)
|
|
73
73
|
ecef = GCS::ECEF.new(x: -2304643.57, y: -3638650.07, z: 4688674.43)
|
|
74
74
|
```
|
|
@@ -542,7 +542,7 @@ The [`examples/`](examples/) directory contains runnable demo scripts showing pr
|
|
|
542
542
|
| Script | Description |
|
|
543
543
|
|--------|-------------|
|
|
544
544
|
| [`01_basic_conversions.rb`](examples/01_basic_conversions.rb) | LLA, ECEF, UTM, ENU, NED conversions and roundtrips |
|
|
545
|
-
| [`02_all_coordinate_systems.rb`](examples/02_all_coordinate_systems.rb) | All
|
|
545
|
+
| [`02_all_coordinate_systems.rb`](examples/02_all_coordinate_systems.rb) | All 17 coordinate systems, cross-system chains, and areas |
|
|
546
546
|
| [`03_distance_calculations.rb`](examples/03_distance_calculations.rb) | Distance class features, unit conversions, and arithmetic |
|
|
547
547
|
| [`04_bearing_calculations.rb`](examples/04_bearing_calculations.rb) | Bearing class, compass directions, elevation angles, and chain bearings |
|
|
548
548
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Geodetic::
|
|
1
|
+
# Geodetic::Coordinate::BNG
|
|
2
2
|
|
|
3
3
|
## British National Grid
|
|
4
4
|
|
|
@@ -9,13 +9,13 @@ The British National Grid (BNG) is the official coordinate system for Great Brit
|
|
|
9
9
|
Create a BNG coordinate from numeric easting/northing values:
|
|
10
10
|
|
|
11
11
|
```ruby
|
|
12
|
-
point = Geodetic::
|
|
12
|
+
point = Geodetic::Coordinate::BNG.new(easting: 530000.0, northing: 180000.0)
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
Alternatively, create from an alphanumeric grid reference string:
|
|
16
16
|
|
|
17
17
|
```ruby
|
|
18
|
-
point = Geodetic::
|
|
18
|
+
point = Geodetic::Coordinate::BNG.new(grid_ref: "TQ 300000 800000")
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
## Grid References
|
|
@@ -52,8 +52,8 @@ point.valid? # => true if within Great Britain bounds
|
|
|
52
52
|
The universal `distance_to` method computes the Vincenty great-circle distance to any other coordinate type, returning a `Distance` object. The `straight_line_distance_to` method computes the Euclidean distance in ECEF space. The universal `bearing_to` method computes the great-circle forward azimuth, returning a `Bearing` object. All accept single or multiple targets.
|
|
53
53
|
|
|
54
54
|
```ruby
|
|
55
|
-
bng_a = Geodetic::
|
|
56
|
-
bng_b = Geodetic::
|
|
55
|
+
bng_a = Geodetic::Coordinate::BNG.new(easting: 530000.0, northing: 180000.0)
|
|
56
|
+
bng_b = Geodetic::Coordinate::BNG.new(easting: 540000.0, northing: 190000.0)
|
|
57
57
|
bng_a.distance_to(bng_b) # => Distance (great-circle)
|
|
58
58
|
bng_a.straight_line_distance_to(bng_b) # => Distance (Euclidean)
|
|
59
59
|
bng_a.bearing_to(bng_b) # => Bearing (great-circle forward azimuth)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Geodetic::
|
|
1
|
+
# Geodetic::Coordinate::ECEF
|
|
2
2
|
|
|
3
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
4
|
|
|
@@ -7,7 +7,7 @@ ECEF is useful for satellite positioning, radar tracking, and any application re
|
|
|
7
7
|
## Constructor
|
|
8
8
|
|
|
9
9
|
```ruby
|
|
10
|
-
Geodetic::
|
|
10
|
+
Geodetic::Coordinate::ECEF.new(x: 0.0, y: 0.0, z: 0.0)
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
| Parameter | Type | Default | Description |
|
|
@@ -35,9 +35,9 @@ All conversion methods accept an optional `datum` parameter (defaults to `Geodet
|
|
|
35
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
36
|
|
|
37
37
|
```ruby
|
|
38
|
-
ecef = Geodetic::
|
|
38
|
+
ecef = Geodetic::Coordinate::ECEF.new(x: 1130730.0, y: -4828583.0, z: 3991570.0)
|
|
39
39
|
lla = ecef.to_lla
|
|
40
|
-
# => Geodetic::
|
|
40
|
+
# => Geodetic::Coordinate::LLA
|
|
41
41
|
```
|
|
42
42
|
|
|
43
43
|
### ECEF.from_lla(lla, datum = WGS84)
|
|
@@ -45,7 +45,7 @@ lla = ecef.to_lla
|
|
|
45
45
|
Creates an ECEF from an LLA instance. Raises `ArgumentError` if the argument is not an `LLA`.
|
|
46
46
|
|
|
47
47
|
```ruby
|
|
48
|
-
ecef = Geodetic::
|
|
48
|
+
ecef = Geodetic::Coordinate::ECEF.from_lla(lla)
|
|
49
49
|
```
|
|
50
50
|
|
|
51
51
|
### to_utm(datum = WGS84)
|
|
@@ -54,7 +54,7 @@ Converts to Universal Transverse Mercator coordinates. Internally converts to LL
|
|
|
54
54
|
|
|
55
55
|
```ruby
|
|
56
56
|
utm = ecef.to_utm
|
|
57
|
-
# => Geodetic::
|
|
57
|
+
# => Geodetic::Coordinate::UTM
|
|
58
58
|
```
|
|
59
59
|
|
|
60
60
|
### ECEF.from_utm(utm, datum = WGS84)
|
|
@@ -66,10 +66,10 @@ Creates an ECEF from a UTM instance. Raises `ArgumentError` if the argument is n
|
|
|
66
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
67
|
|
|
68
68
|
```ruby
|
|
69
|
-
ref_ecef = Geodetic::
|
|
70
|
-
point_ecef = Geodetic::
|
|
69
|
+
ref_ecef = Geodetic::Coordinate::ECEF.new(x: 1130730.0, y: -4828583.0, z: 3991570.0)
|
|
70
|
+
point_ecef = Geodetic::Coordinate::ECEF.new(x: 1130740.0, y: -4828573.0, z: 3991580.0)
|
|
71
71
|
enu = point_ecef.to_enu(ref_ecef)
|
|
72
|
-
# => Geodetic::
|
|
72
|
+
# => Geodetic::Coordinate::ENU
|
|
73
73
|
```
|
|
74
74
|
|
|
75
75
|
Raises `ArgumentError` if `reference_ecef` is not an `ECEF`.
|
|
@@ -84,7 +84,7 @@ Converts to North-East-Down local tangent plane coordinates. Internally converts
|
|
|
84
84
|
|
|
85
85
|
```ruby
|
|
86
86
|
ned = point_ecef.to_ned(ref_ecef)
|
|
87
|
-
# => Geodetic::
|
|
87
|
+
# => Geodetic::Coordinate::NED
|
|
88
88
|
```
|
|
89
89
|
|
|
90
90
|
### ECEF.from_ned(ned, reference_ecef, reference_lla = nil)
|
|
@@ -98,7 +98,7 @@ Creates an ECEF from a NED instance and a reference ECEF origin. Raises `Argumen
|
|
|
98
98
|
Returns a comma-separated string of `x, y, z`.
|
|
99
99
|
|
|
100
100
|
```ruby
|
|
101
|
-
ecef = Geodetic::
|
|
101
|
+
ecef = Geodetic::Coordinate::ECEF.new(x: 1130730.0, y: -4828583.0, z: 3991570.0)
|
|
102
102
|
ecef.to_s
|
|
103
103
|
# => "1130730.0, -4828583.0, 3991570.0"
|
|
104
104
|
```
|
|
@@ -117,7 +117,7 @@ ecef.to_a
|
|
|
117
117
|
Parses a comma-separated string into an ECEF.
|
|
118
118
|
|
|
119
119
|
```ruby
|
|
120
|
-
ecef = Geodetic::
|
|
120
|
+
ecef = Geodetic::Coordinate::ECEF.from_string("1130730.0, -4828583.0, 3991570.0")
|
|
121
121
|
```
|
|
122
122
|
|
|
123
123
|
### ECEF.from_array(array)
|
|
@@ -125,7 +125,7 @@ ecef = Geodetic::Coordinates::ECEF.from_string("1130730.0, -4828583.0, 3991570.0
|
|
|
125
125
|
Creates an ECEF from a three-element array `[x, y, z]`.
|
|
126
126
|
|
|
127
127
|
```ruby
|
|
128
|
-
ecef = Geodetic::
|
|
128
|
+
ecef = Geodetic::Coordinate::ECEF.from_array([1130730.0, -4828583.0, 3991570.0])
|
|
129
129
|
```
|
|
130
130
|
|
|
131
131
|
## Additional Methods
|
|
@@ -135,8 +135,8 @@ ecef = Geodetic::Coordinates::ECEF.from_array([1130730.0, -4828583.0, 3991570.0]
|
|
|
135
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
136
|
|
|
137
137
|
```ruby
|
|
138
|
-
a = Geodetic::
|
|
139
|
-
b = Geodetic::
|
|
138
|
+
a = Geodetic::Coordinate::ECEF.new(x: 1130730.0, y: -4828583.0, z: 3991570.0)
|
|
139
|
+
b = Geodetic::Coordinate::ECEF.new(x: 1130730.0, y: -4828583.0, z: 3991570.0)
|
|
140
140
|
a == b
|
|
141
141
|
# => true
|
|
142
142
|
```
|
|
@@ -146,8 +146,8 @@ a == b
|
|
|
146
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
147
|
|
|
148
148
|
```ruby
|
|
149
|
-
a = Geodetic::
|
|
150
|
-
b = Geodetic::
|
|
149
|
+
a = Geodetic::Coordinate::ECEF.new(x: 1130730.0, y: -4828583.0, z: 3991570.0)
|
|
150
|
+
b = Geodetic::Coordinate::ECEF.new(x: 1130740.0, y: -4828573.0, z: 3991580.0)
|
|
151
151
|
a.distance_to(b)
|
|
152
152
|
# => Distance (meters, great-circle distance)
|
|
153
153
|
```
|
|
@@ -169,7 +169,7 @@ a.straight_line_distance_to(b)
|
|
|
169
169
|
require 'geodetic'
|
|
170
170
|
|
|
171
171
|
# Create an ECEF coordinate
|
|
172
|
-
ecef = Geodetic::
|
|
172
|
+
ecef = Geodetic::Coordinate::ECEF.new(x: 1130730.0, y: -4828583.0, z: 3991570.0)
|
|
173
173
|
|
|
174
174
|
# Convert to LLA and back
|
|
175
175
|
lla = ecef.to_lla
|
|
@@ -181,8 +181,8 @@ ecef == ecef_roundtrip
|
|
|
181
181
|
### Distance between two points
|
|
182
182
|
|
|
183
183
|
```ruby
|
|
184
|
-
station_a = Geodetic::
|
|
185
|
-
station_b = Geodetic::
|
|
184
|
+
station_a = Geodetic::Coordinate::ECEF.new(x: 1130730.0, y: -4828583.0, z: 3991570.0)
|
|
185
|
+
station_b = Geodetic::Coordinate::ECEF.new(x: 1131000.0, y: -4828300.0, z: 3991800.0)
|
|
186
186
|
|
|
187
187
|
# Great-circle distance (Vincenty)
|
|
188
188
|
distance = station_a.distance_to(station_b)
|
|
@@ -196,8 +196,8 @@ puts "Straight-line distance: #{straight.meters} meters"
|
|
|
196
196
|
### Local tangent plane from ECEF
|
|
197
197
|
|
|
198
198
|
```ruby
|
|
199
|
-
origin = Geodetic::
|
|
200
|
-
target = Geodetic::
|
|
199
|
+
origin = Geodetic::Coordinate::ECEF.new(x: 1130730.0, y: -4828583.0, z: 3991570.0)
|
|
200
|
+
target = Geodetic::Coordinate::ECEF.new(x: 1130740.0, y: -4828573.0, z: 3991580.0)
|
|
201
201
|
|
|
202
202
|
# Provide reference LLA to avoid recomputing it
|
|
203
203
|
ref_lla = origin.to_lla
|
|
@@ -210,6 +210,6 @@ ned = target.to_ned(origin, ref_lla)
|
|
|
210
210
|
|
|
211
211
|
```ruby
|
|
212
212
|
clarke66 = Geodetic::Datum.new(name: 'CLARKE_1866')
|
|
213
|
-
ecef = Geodetic::
|
|
213
|
+
ecef = Geodetic::Coordinate::ECEF.new(x: 1130730.0, y: -4828583.0, z: 3991570.0)
|
|
214
214
|
lla = ecef.to_lla(clarke66)
|
|
215
215
|
```
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Geodetic::
|
|
1
|
+
# Geodetic::Coordinate::ENU - East, North, Up
|
|
2
2
|
|
|
3
3
|
## Overview
|
|
4
4
|
|
|
@@ -52,7 +52,7 @@ The one exception is the conversion between ENU and NED, which does not require
|
|
|
52
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
53
|
|
|
54
54
|
```ruby
|
|
55
|
-
ref = Geodetic::
|
|
55
|
+
ref = Geodetic::Coordinate::LLA.new(lat: 47.62, lng: -122.35, alt: 0.0)
|
|
56
56
|
lla = enu.to_lla(ref)
|
|
57
57
|
lla.distance_to(other_lla) # Vincenty great-circle distance
|
|
58
58
|
lla.bearing_to(other_lla) # Great-circle forward azimuth (Bearing object)
|
|
@@ -65,7 +65,7 @@ Bearing is measured in **degrees from north**, clockwise, in the range **0-360**
|
|
|
65
65
|
## Example
|
|
66
66
|
|
|
67
67
|
```ruby
|
|
68
|
-
point = Geodetic::
|
|
68
|
+
point = Geodetic::Coordinate::ENU.new(e: 100.0, n: 200.0, u: 50.0)
|
|
69
69
|
|
|
70
70
|
point.east # => 100.0
|
|
71
71
|
point.north # => 200.0
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# Geodetic::Coordinate::GARS
|
|
2
|
+
|
|
3
|
+
## Global Area Reference System
|
|
4
|
+
|
|
5
|
+
GARS is a standardized geospatial reference system developed by the National Geospatial-Intelligence Agency (NGA). It divides the Earth into hierarchical grid cells at three precision levels, designed for military targeting, air defense, and joint operations.
|
|
6
|
+
|
|
7
|
+
The encoding uses a false coordinate origin that shifts longitude by +180 and latitude by +90. Cells are identified by a combination of numeric longitude bands, alphabetic latitude bands, and optional subdivision digits:
|
|
8
|
+
|
|
9
|
+
| Level | Characters | Size | Description |
|
|
10
|
+
|-------|-----------|------|-------------|
|
|
11
|
+
| **30-minute cell** | 5 (NNNll) | 30' x 30' | 3-digit lon band + 2-letter lat band |
|
|
12
|
+
| **Quadrant** | 6 (NNNllq) | 15' x 15' | + quadrant digit 1-4 |
|
|
13
|
+
| **Keypad** | 7 (NNNllqk) | 5' x 5' | + keypad digit 1-9 |
|
|
14
|
+
|
|
15
|
+
### Quadrant Layout
|
|
16
|
+
|
|
17
|
+
Within each 30-minute cell, quadrants are numbered:
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
+---+---+
|
|
21
|
+
| 1 | 2 | (north)
|
|
22
|
+
+---+---+
|
|
23
|
+
| 3 | 4 | (south)
|
|
24
|
+
+---+---+
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Keypad Layout
|
|
28
|
+
|
|
29
|
+
Within each quadrant, keypads follow telephone-style numbering:
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
+---+---+---+
|
|
33
|
+
| 1 | 2 | 3 | (north)
|
|
34
|
+
+---+---+---+
|
|
35
|
+
| 4 | 5 | 6 |
|
|
36
|
+
+---+---+---+
|
|
37
|
+
| 7 | 8 | 9 | (south)
|
|
38
|
+
+---+---+---+
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
GARS is a **2D coordinate system** (no altitude). Conversions to/from other systems go through LLA as the intermediary. Each code represents a rectangular cell; the coordinate's point value is the cell's midpoint.
|
|
42
|
+
|
|
43
|
+
## Constructor
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
# From a GARS string
|
|
47
|
+
coord = Geodetic::Coordinate::GARS.new("006AG39")
|
|
48
|
+
|
|
49
|
+
# From any coordinate (converts via LLA)
|
|
50
|
+
coord = Geodetic::Coordinate::GARS.new(lla_coord)
|
|
51
|
+
coord = Geodetic::Coordinate::GARS.new(utm_coord, precision: 6)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
| Parameter | Type | Default | Description |
|
|
55
|
+
|-------------|-------------------|---------|--------------------------------------|
|
|
56
|
+
| `source` | String or Coord | -- | A GARS string or any coordinate object |
|
|
57
|
+
| `precision` | Integer | 7 | Code length: 5, 6, or 7 |
|
|
58
|
+
|
|
59
|
+
Raises `ArgumentError` if the source string is empty, has an invalid length, contains an out-of-range longitude band (must be 001-720), invalid latitude letters (I and O excluded), invalid quadrant digit (must be 1-4), or invalid keypad digit (must be 1-9). Letter input is case-insensitive (normalized to uppercase).
|
|
60
|
+
|
|
61
|
+
## Attributes
|
|
62
|
+
|
|
63
|
+
| Attribute | Type | Access | Description |
|
|
64
|
+
|-----------|--------|-----------|------------------------|
|
|
65
|
+
| `code` | String | read-only | The GARS code string |
|
|
66
|
+
|
|
67
|
+
GARS is **immutable** -- there are no setter methods.
|
|
68
|
+
|
|
69
|
+
## Precision
|
|
70
|
+
|
|
71
|
+
The precision (code length) determines the size of the encoded cell:
|
|
72
|
+
|
|
73
|
+
| Length | Level | Approximate Cell Size |
|
|
74
|
+
|--------|-------|----------------------|
|
|
75
|
+
| 5 | 30-minute cell | ~55.6 km x 55.6 km |
|
|
76
|
+
| 6 | 15-minute quadrant | ~27.8 km x 27.8 km |
|
|
77
|
+
| 7 | 5-minute keypad | ~9.3 km x 9.3 km |
|
|
78
|
+
|
|
79
|
+
```ruby
|
|
80
|
+
coord.precision # => 7 (code length)
|
|
81
|
+
coord.precision_in_meters # => { lat: ~9260, lng: ~... }
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Conversions
|
|
85
|
+
|
|
86
|
+
All conversions chain through LLA. The datum parameter defaults to `Geodetic::WGS84`.
|
|
87
|
+
|
|
88
|
+
### Instance Methods
|
|
89
|
+
|
|
90
|
+
```ruby
|
|
91
|
+
coord.to_lla # => LLA (midpoint of the cell)
|
|
92
|
+
coord.to_ecef
|
|
93
|
+
coord.to_utm
|
|
94
|
+
coord.to_enu(reference_lla)
|
|
95
|
+
coord.to_ned(reference_lla)
|
|
96
|
+
coord.to_mgrs
|
|
97
|
+
coord.to_usng
|
|
98
|
+
coord.to_web_mercator
|
|
99
|
+
coord.to_ups
|
|
100
|
+
coord.to_state_plane(zone_code)
|
|
101
|
+
coord.to_bng
|
|
102
|
+
coord.to_gh36
|
|
103
|
+
coord.to_gh
|
|
104
|
+
coord.to_ham
|
|
105
|
+
coord.to_olc
|
|
106
|
+
coord.to_georef
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Class Methods
|
|
110
|
+
|
|
111
|
+
```ruby
|
|
112
|
+
GARS.from_lla(lla_coord)
|
|
113
|
+
GARS.from_ecef(ecef_coord)
|
|
114
|
+
GARS.from_utm(utm_coord)
|
|
115
|
+
GARS.from_web_mercator(wm_coord)
|
|
116
|
+
GARS.from_gh(gh_coord)
|
|
117
|
+
GARS.from_georef(georef_coord)
|
|
118
|
+
# ... and all other coordinate systems
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### LLA Convenience Methods
|
|
122
|
+
|
|
123
|
+
```ruby
|
|
124
|
+
lla = Geodetic::Coordinate::LLA.new(lat: 40.7128, lng: -74.0060)
|
|
125
|
+
gars = lla.to_gars # default precision 7
|
|
126
|
+
gars = lla.to_gars(precision: 5) # 30-minute cell only
|
|
127
|
+
|
|
128
|
+
lla = Geodetic::Coordinate::LLA.from_gars(gars)
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Serialization
|
|
132
|
+
|
|
133
|
+
### `to_s(truncate_to = nil)`
|
|
134
|
+
|
|
135
|
+
Returns the GARS string. An optional integer truncates to that precision (re-encodes from decoded coordinates).
|
|
136
|
+
|
|
137
|
+
```ruby
|
|
138
|
+
coord = GARS.new("006AG39")
|
|
139
|
+
coord.to_s # => "006AG39"
|
|
140
|
+
coord.to_s(6) # => "006AG3"
|
|
141
|
+
coord.to_s(5) # => "006AG"
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### `to_slug`
|
|
145
|
+
|
|
146
|
+
Alias for `to_s`. GARS codes are already URL-safe.
|
|
147
|
+
|
|
148
|
+
### `to_a`
|
|
149
|
+
|
|
150
|
+
Returns `[lat, lng]` of the cell midpoint.
|
|
151
|
+
|
|
152
|
+
```ruby
|
|
153
|
+
coord.to_a # => [lat, lng]
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### `from_string` / `from_array`
|
|
157
|
+
|
|
158
|
+
```ruby
|
|
159
|
+
GARS.from_string("006AG39") # from GARS string
|
|
160
|
+
GARS.from_array([40.0, -74.0]) # from [lat, lng]
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Neighbors
|
|
164
|
+
|
|
165
|
+
Returns all 8 adjacent cells as GARS instances.
|
|
166
|
+
|
|
167
|
+
```ruby
|
|
168
|
+
coord = GARS.new("361HN35")
|
|
169
|
+
neighbors = coord.neighbors
|
|
170
|
+
# => { N: GARS, S: GARS, E: GARS, W: GARS, NE: GARS, NW: GARS, SE: GARS, SW: GARS }
|
|
171
|
+
|
|
172
|
+
neighbors[:N].to_lla.lat > coord.to_lla.lat # => true
|
|
173
|
+
neighbors[:E].to_lla.lng > coord.to_lla.lng # => true
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Neighbors preserve the same precision as the original code. Latitude is clamped to valid range near the poles; longitude wraps at the antimeridian.
|
|
177
|
+
|
|
178
|
+
## Area
|
|
179
|
+
|
|
180
|
+
The `to_area` method returns the GARS cell as a `Geodetic::Areas::Rectangle`.
|
|
181
|
+
|
|
182
|
+
```ruby
|
|
183
|
+
area = coord.to_area
|
|
184
|
+
# => Geodetic::Areas::Rectangle
|
|
185
|
+
|
|
186
|
+
area.includes?(coord.to_lla) # => true (midpoint is inside the cell)
|
|
187
|
+
area.nw # => LLA (northwest corner)
|
|
188
|
+
area.se # => LLA (southeast corner)
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Equality
|
|
192
|
+
|
|
193
|
+
Two GARS instances are equal if their code strings match exactly.
|
|
194
|
+
|
|
195
|
+
```ruby
|
|
196
|
+
GARS.new("006AG39") == GARS.new("006AG39") # => true
|
|
197
|
+
GARS.new("006AG39") == GARS.new("006AG38") # => false
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## `valid?`
|
|
201
|
+
|
|
202
|
+
Returns `true` if the code has a valid length (5, 6, or 7), a longitude band between 001-720, valid latitude letters (A-Z excluding I and O), a valid quadrant digit (1-4), and a valid keypad digit (1-9).
|
|
203
|
+
|
|
204
|
+
```ruby
|
|
205
|
+
coord.valid? # => true
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## Universal Distance and Bearing Methods
|
|
209
|
+
|
|
210
|
+
GARS supports all universal distance and bearing methods via the `DistanceMethods` and `BearingMethods` mixins:
|
|
211
|
+
|
|
212
|
+
```ruby
|
|
213
|
+
a = GARS.new("361HN35")
|
|
214
|
+
b = GARS.new("212LX43")
|
|
215
|
+
|
|
216
|
+
a.distance_to(b) # => Distance
|
|
217
|
+
a.straight_line_distance_to(b) # => Distance
|
|
218
|
+
a.bearing_to(b) # => Bearing
|
|
219
|
+
a.elevation_to(b) # => Float (degrees)
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## Encoding Details
|
|
223
|
+
|
|
224
|
+
### Longitude Bands
|
|
225
|
+
|
|
226
|
+
720 bands of 0.5 degrees each, numbered 001-720 from west to east:
|
|
227
|
+
- Band 001: -180.0 to -179.5
|
|
228
|
+
- Band 361: 0.0 to 0.5
|
|
229
|
+
- Band 720: 179.5 to 180.0
|
|
230
|
+
|
|
231
|
+
### Latitude Bands
|
|
232
|
+
|
|
233
|
+
360 bands of 0.5 degrees each, encoded as 2-letter pairs (AA-QZ) from south to north:
|
|
234
|
+
- AA: -90.0 to -89.5
|
|
235
|
+
- HN: 0.0 to 0.5
|
|
236
|
+
- QZ: 89.5 to 90.0
|
|
237
|
+
|
|
238
|
+
The 24-letter alphabet (A-Z, excluding I and O) gives 24 x 15 = 360 valid combinations.
|
|
239
|
+
|
|
240
|
+
## Well-Known GARS Codes
|
|
241
|
+
|
|
242
|
+
| Location | GARS Code | Description |
|
|
243
|
+
|----------|-----------|-------------|
|
|
244
|
+
| Null Island (0, 0) | 361HN | 30-minute cell at equator/prime meridian |
|
|
245
|
+
| New York City | 212LX43 | 5-minute cell |
|
|
246
|
+
| Western boundary | 001xx | Longitude -180.0 |
|