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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f295df82e85611a9a7a7dd56593ae029d6ad096cc5c96021d5b59b4f4b001cc2
|
|
4
|
+
data.tar.gz: 6da1bc3461fd4f372b53f5458d322f8c14d1feb8a2afa93a2375cb5641674674
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1a0b72935abf28bbcb41182f800d1bdb52466530565f13d75fec5f3eec14c0dac7680f8220f09c51cf4d60ed3b87fc53a3675ceb972f1cec17ca06cbd096d717
|
|
7
|
+
data.tar.gz: 754e5b64d8e1d333dafd270a6e21434d46102ea57e43fbba16b83bcecd9eb69eb4e834a51554c10eb59433f6d434930bf2664fca0d9c22b37cf7378e90cc106e
|
data/CHANGELOG.md
CHANGED
|
@@ -11,21 +11,73 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
11
11
|
## [Unreleased]
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
## [0.2
|
|
14
|
+
## [0.3.2] - 2026-03-09
|
|
15
15
|
|
|
16
16
|
### Added
|
|
17
17
|
|
|
18
|
-
- **
|
|
19
|
-
- `
|
|
20
|
-
- `
|
|
21
|
-
- `
|
|
22
|
-
-
|
|
23
|
-
-
|
|
18
|
+
- **`Geodetic::Path` class** — directed, ordered sequence of unique coordinates for modeling routes, trails, and boundaries
|
|
19
|
+
- **Navigation**: `first`, `last`, `next`, `prev`, `segments`, `size`, `empty?`
|
|
20
|
+
- **Membership**: `include?`/`includes?` (waypoint check), `contains?`/`inside?` (on-segment check with configurable tolerance)
|
|
21
|
+
- **Spatial**: `nearest_waypoint`, `closest_coordinate_to`, `distance_to`, `bearing_to` using geometric projection onto segments
|
|
22
|
+
- **Closest points**: `closest_points_to` for Path-to-Path, Path-to-Polygon, Path-to-Rectangle, and Path-to-Circle
|
|
23
|
+
- **Computed**: `total_distance`, `segment_distances`, `segment_bearings`, `reverse`
|
|
24
|
+
- **Subpath/split**: `between(from, to)` extracts a subpath; `split_at(coord)` divides into two paths sharing the split point
|
|
25
|
+
- **Interpolation**: `at_distance(distance)` finds the coordinate at a given distance along the path
|
|
26
|
+
- **Bounding box**: `bounds` returns an `Areas::Rectangle`
|
|
27
|
+
- **Polygon conversion**: `to_polygon` closes the path (validates no self-intersection)
|
|
28
|
+
- **Intersection**: `intersects?(other_path)` detects crossing segments
|
|
29
|
+
- **Equality**: `==` compares coordinates in order
|
|
30
|
+
- **Enumerable**: includes `Enumerable` via `each` — supports `map`, `select`, `any?`, `to_a`, etc.
|
|
31
|
+
- **Non-mutating operators**: `+` and `-` accept both coordinates and paths
|
|
32
|
+
- **Mutating operators**: `<<`, `>>`, `prepend`, `insert(after:/before:)`, `delete`/`remove` — all accept paths as well as coordinates
|
|
33
|
+
- **Path operations example** (`examples/06_path_operations.rb`) — 19-section demo covering all Path capabilities with a Manhattan walking route
|
|
34
|
+
- Documentation: `docs/reference/path.md` (Path reference)
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
|
|
38
|
+
- Updated `Geodetic::Feature` to support Path as a geometry type — delegates `distance_to` and `bearing_to` using geometric projection
|
|
39
|
+
- Updated README, `docs/index.md`, `docs/reference/feature.md`, `examples/README.md`, and mkdocs nav to include Path class
|
|
40
|
+
|
|
41
|
+
## [0.3.1] - 2026-03-09
|
|
42
|
+
|
|
43
|
+
### Added
|
|
44
|
+
|
|
45
|
+
- **`Geodetic::Feature` class** — wraps any coordinate or area geometry with a `label` and `metadata` hash; delegates `distance_to` and `bearing_to` to the underlying geometry, using the centroid for area geometries
|
|
46
|
+
- **Map rendering example** (`examples/05_map_rendering/`) — renders NYC landmarks on a raster map using [libgd-gis](https://rubygems.org/gems/libgd-gis), demonstrating Feature objects, polygon overlays, bearing arrows, icon compositing, and light/dark theme support
|
|
47
|
+
- `examples/README.md` describing all five example scripts
|
|
48
|
+
- Documentation: `docs/reference/feature.md` (Feature reference) and `docs/reference/map-rendering.md` (libgd-gis integration guide)
|
|
49
|
+
|
|
50
|
+
### Changed
|
|
51
|
+
|
|
52
|
+
- Updated README, `docs/index.md`, and mkdocs nav to include Feature class and map rendering example
|
|
53
|
+
|
|
54
|
+
## [0.3.0] - 2026-03-08
|
|
55
|
+
|
|
56
|
+
### Added
|
|
57
|
+
|
|
58
|
+
- **H3 Hexagonal Hierarchical Index** (`Geodetic::Coordinate::H3`) — Uber's spatial indexing system, bringing total to 18 coordinate systems (324 conversion paths)
|
|
59
|
+
- H3 uses Ruby's `fiddle` to call the H3 v4 C API directly — no gem dependency beyond `fiddle`
|
|
24
60
|
- H3-specific features: `grid_disk(k)`, `parent(res)`, `children(res)`, `pentagon?`, `cell_area`, `h3_index`, `resolution` (0-15)
|
|
25
61
|
- H3 `to_area` returns `Areas::Polygon` (6 vertices for hexagons, 5 for pentagons) instead of `Areas::Rectangle`
|
|
26
62
|
- H3 `neighbors` returns Array of 6 cells instead of directional Hash with 8 cardinal keys
|
|
27
63
|
- Graceful degradation: H3 raises clear error with installation instructions if `libh3` is not found; all other coordinate systems work normally
|
|
28
|
-
-
|
|
64
|
+
- `H3.available?` class method to check for libh3 at runtime
|
|
65
|
+
- Documentation page: `docs/coordinate-systems/h3.md`
|
|
66
|
+
|
|
67
|
+
### Changed
|
|
68
|
+
|
|
69
|
+
- Updated all documentation to reflect 18 coordinate systems (README, docs, gemspec, CLAUDE.md)
|
|
70
|
+
|
|
71
|
+
## [0.2.0] - 2026-03-08
|
|
72
|
+
|
|
73
|
+
### Added
|
|
74
|
+
|
|
75
|
+
- **2 new coordinate systems** bringing the total from 15 to 17:
|
|
76
|
+
- `Geodetic::Coordinate::GEOREF` — World Geographic Reference System (aviation/military geocode with variable precision from 15-degree tiles to 0.001-minute resolution)
|
|
77
|
+
- `Geodetic::Coordinate::GARS` — Global Area Reference System (NGA standard with 30-minute cells, 15-minute quadrants, and 5-minute keypads)
|
|
78
|
+
- Full cross-system conversions for GEOREF and GARS — all 17 coordinate systems convert to/from every other system (289 conversion paths)
|
|
79
|
+
- Spatial hash features for GEOREF and GARS: `neighbors`, `to_area`, `precision_in_meters`, `to_slug`, configurable precision
|
|
80
|
+
- Documentation pages: `docs/coordinate-systems/georef.md` and `docs/coordinate-systems/gars.md`
|
|
29
81
|
|
|
30
82
|
### Changed
|
|
31
83
|
|
data/README.md
CHANGED
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
- <strong>Bearing Calculations</strong> - Forward azimuth, back azimuth, compass directions, elevation angles<br>
|
|
20
20
|
- <strong>Geoid Height Support</strong> - EGM96, EGM2008, GEOID18, GEOID12B models<br>
|
|
21
21
|
- <strong>Geographic Areas</strong> - Circle, Polygon, and Rectangle with point-in-area tests<br>
|
|
22
|
+
- <strong>Paths</strong> - Directed coordinate sequences with navigation, interpolation, closest approach, intersection, and area conversion<br>
|
|
23
|
+
- <strong>Features</strong> - Named geometry wrapper with metadata and delegated distance/bearing<br>
|
|
22
24
|
- <strong>Validated Setters</strong> - Type coercion and range validation on all coordinate attributes<br>
|
|
23
25
|
- <strong>Serialization</strong> - to_s(precision), to_a, from_string, from_array, DMS format<br>
|
|
24
26
|
- <strong>Multiple Datums</strong> - WGS84, Clarke 1866, GRS 1980, Airy 1830, and more<br>
|
|
@@ -77,14 +79,29 @@ ned = Coordinates::NED.new(n: 200.0, e: 100.0, d: -50.0)
|
|
|
77
79
|
|
|
78
80
|
### GCS Shorthand
|
|
79
81
|
|
|
80
|
-
|
|
82
|
+
For convenience, you can define a short alias in your application:
|
|
81
83
|
|
|
82
84
|
```ruby
|
|
83
85
|
require "geodetic"
|
|
84
86
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
87
|
+
GCS = Geodetic::Coordinate
|
|
88
|
+
|
|
89
|
+
seattle = Geodetic::Coordinate::LLA.new(lat: 47.6205, lng: -122.3493, alt: 184.0)
|
|
90
|
+
ecef = Geodetic::Coordinate::ECEF.new(x: -2304643.57, y: -3638650.07, z: 4688674.43)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Discovering Coordinate Systems
|
|
94
|
+
|
|
95
|
+
List all available coordinate systems at runtime:
|
|
96
|
+
|
|
97
|
+
```ruby
|
|
98
|
+
Geodetic::Coordinate.systems
|
|
99
|
+
# => [Geodetic::Coordinate::LLA, Geodetic::Coordinate::ECEF, Geodetic::Coordinate::UTM, ...]
|
|
100
|
+
|
|
101
|
+
# Get short names
|
|
102
|
+
Geodetic::Coordinate.systems.map { |c| c.name.split('::').last }
|
|
103
|
+
# => ["LLA", "ECEF", "UTM", "ENU", "NED", "MGRS", "USNG", "WebMercator",
|
|
104
|
+
# "UPS", "StatePlane", "BNG", "GH36", "GH", "HAM", "OLC", "GEOREF", "GARS", "H3"]
|
|
88
105
|
```
|
|
89
106
|
|
|
90
107
|
### Coordinate Conversions
|
|
@@ -185,9 +202,9 @@ Universal distance methods work across all coordinate types and return `Distance
|
|
|
185
202
|
**Instance method `distance_to`** — Vincenty great-circle distance:
|
|
186
203
|
|
|
187
204
|
```ruby
|
|
188
|
-
seattle =
|
|
189
|
-
portland =
|
|
190
|
-
sf =
|
|
205
|
+
seattle = Geodetic::Coordinate::LLA.new(lat: 47.6205, lng: -122.3493, alt: 0.0)
|
|
206
|
+
portland = Geodetic::Coordinate::LLA.new(lat: 45.5152, lng: -122.6784, alt: 0.0)
|
|
207
|
+
sf = Geodetic::Coordinate::LLA.new(lat: 37.7749, lng: -122.4194, alt: 0.0)
|
|
191
208
|
|
|
192
209
|
d = seattle.distance_to(portland) # => Distance (meters)
|
|
193
210
|
d.meters # => 235393.17
|
|
@@ -201,23 +218,23 @@ seattle.distance_to([portland, sf]) # => [Distance, Distance] (radial)
|
|
|
201
218
|
**Class method `distance_between`** — consecutive chain distances:
|
|
202
219
|
|
|
203
220
|
```ruby
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
221
|
+
Geodetic::Coordinate.distance_between(seattle, portland) # => Distance
|
|
222
|
+
Geodetic::Coordinate.distance_between(seattle, portland, sf) # => [Distance, Distance] (chain)
|
|
223
|
+
Geodetic::Coordinate.distance_between([seattle, portland, sf]) # => [Distance, Distance] (chain)
|
|
207
224
|
```
|
|
208
225
|
|
|
209
226
|
**Straight-line (ECEF Euclidean) versions:**
|
|
210
227
|
|
|
211
228
|
```ruby
|
|
212
229
|
seattle.straight_line_distance_to(portland) # => Distance
|
|
213
|
-
|
|
230
|
+
Geodetic::Coordinate.straight_line_distance_between(seattle, portland) # => Distance
|
|
214
231
|
```
|
|
215
232
|
|
|
216
233
|
**Cross-system distances** — works between any coordinate types:
|
|
217
234
|
|
|
218
235
|
```ruby
|
|
219
236
|
utm = seattle.to_utm
|
|
220
|
-
mgrs =
|
|
237
|
+
mgrs = Geodetic::Coordinate::MGRS.from_lla(portland)
|
|
221
238
|
utm.distance_to(mgrs) # => Distance
|
|
222
239
|
```
|
|
223
240
|
|
|
@@ -230,8 +247,8 @@ Universal bearing methods work across all coordinate types and return `Bearing`
|
|
|
230
247
|
**Instance method `bearing_to`** — great-circle forward azimuth:
|
|
231
248
|
|
|
232
249
|
```ruby
|
|
233
|
-
seattle =
|
|
234
|
-
portland =
|
|
250
|
+
seattle = Geodetic::Coordinate::LLA.new(lat: 47.6205, lng: -122.3493, alt: 0.0)
|
|
251
|
+
portland = Geodetic::Coordinate::LLA.new(lat: 45.5152, lng: -122.6784, alt: 0.0)
|
|
235
252
|
|
|
236
253
|
b = seattle.bearing_to(portland) # => Bearing
|
|
237
254
|
b.degrees # => 188.2
|
|
@@ -245,8 +262,8 @@ b.to_s # => "188.2036°"
|
|
|
245
262
|
**Instance method `elevation_to`** — vertical look angle:
|
|
246
263
|
|
|
247
264
|
```ruby
|
|
248
|
-
a =
|
|
249
|
-
b =
|
|
265
|
+
a = Geodetic::Coordinate::LLA.new(lat: 47.62, lng: -122.35, alt: 0.0)
|
|
266
|
+
b = Geodetic::Coordinate::LLA.new(lat: 47.62, lng: -122.35, alt: 5000.0)
|
|
250
267
|
|
|
251
268
|
a.elevation_to(b) # => 89.9... (degrees, nearly straight up)
|
|
252
269
|
```
|
|
@@ -254,15 +271,15 @@ a.elevation_to(b) # => 89.9... (degrees, nearly straight up)
|
|
|
254
271
|
**Class method `bearing_between`** — consecutive chain bearings:
|
|
255
272
|
|
|
256
273
|
```ruby
|
|
257
|
-
|
|
258
|
-
|
|
274
|
+
Geodetic::Coordinate.bearing_between(seattle, portland) # => Bearing
|
|
275
|
+
Geodetic::Coordinate.bearing_between(seattle, portland, sf) # => [Bearing, Bearing] (chain)
|
|
259
276
|
```
|
|
260
277
|
|
|
261
278
|
**Cross-system bearings** — works between any coordinate types:
|
|
262
279
|
|
|
263
280
|
```ruby
|
|
264
281
|
utm = seattle.to_utm
|
|
265
|
-
mgrs =
|
|
282
|
+
mgrs = Geodetic::Coordinate::MGRS.from_lla(portland)
|
|
266
283
|
utm.bearing_to(mgrs) # => Bearing
|
|
267
284
|
```
|
|
268
285
|
|
|
@@ -405,9 +422,6 @@ gh36 = lla.to_gh36(precision: 8)
|
|
|
405
422
|
# Decode back to LLA
|
|
406
423
|
lla = gh36.to_lla
|
|
407
424
|
|
|
408
|
-
# URL slug (the hash itself is URL-safe)
|
|
409
|
-
gh36.to_slug # => "bdrdC26BqH"
|
|
410
|
-
|
|
411
425
|
# Neighbor cells
|
|
412
426
|
gh36.neighbors # => { N: GH36, S: GH36, E: GH36, W: GH36, NE: ..., NW: ..., SE: ..., SW: ... }
|
|
413
427
|
|
|
@@ -435,9 +449,6 @@ gh = lla.to_gh(precision: 8)
|
|
|
435
449
|
# Decode back to LLA
|
|
436
450
|
lla = gh.to_lla
|
|
437
451
|
|
|
438
|
-
# URL slug (the hash itself is URL-safe)
|
|
439
|
-
gh.to_slug # => "dr5ru7"
|
|
440
|
-
|
|
441
452
|
# Neighbor cells
|
|
442
453
|
gh.neighbors # => { N: GH, S: GH, E: GH, W: GH, NE: ..., NW: ..., SE: ..., SW: ... }
|
|
443
454
|
|
|
@@ -531,6 +542,76 @@ rect.sw # => computed SW corner
|
|
|
531
542
|
rect.includes?(point) # => true/false
|
|
532
543
|
```
|
|
533
544
|
|
|
545
|
+
### Paths
|
|
546
|
+
|
|
547
|
+
`Path` is a directed, ordered sequence of unique coordinates representing routes, trails, or boundaries.
|
|
548
|
+
|
|
549
|
+
```ruby
|
|
550
|
+
route = Path.new(coordinates: [battery_park, wall_street, brooklyn_bridge, city_hall])
|
|
551
|
+
|
|
552
|
+
# Navigation
|
|
553
|
+
route.first # => starting waypoint
|
|
554
|
+
route.next(wall_street) # => brooklyn_bridge
|
|
555
|
+
route.total_distance.to_km # => "3.42 km"
|
|
556
|
+
|
|
557
|
+
# Build incrementally
|
|
558
|
+
trail = Path.new
|
|
559
|
+
trail << start << middle << finish
|
|
560
|
+
trail >> new_start # prepend
|
|
561
|
+
|
|
562
|
+
# Combine paths
|
|
563
|
+
combined = downtown + uptown # concatenate
|
|
564
|
+
trimmed = combined - detour # remove coordinates
|
|
565
|
+
|
|
566
|
+
# Closest approach (geometric projection, not just waypoints)
|
|
567
|
+
route.closest_coordinate_to(off_path_point)
|
|
568
|
+
route.distance_to(target)
|
|
569
|
+
route.closest_points_to(other_path) # path-to-path
|
|
570
|
+
|
|
571
|
+
# Spatial operations
|
|
572
|
+
sub = route.between(a, b) # extract subpath
|
|
573
|
+
left, right = route.split_at(c) # split at waypoint
|
|
574
|
+
route.at_distance(Distance.km(2)) # interpolate along path
|
|
575
|
+
route.bounds # => Areas::Rectangle
|
|
576
|
+
route.to_polygon # close into polygon
|
|
577
|
+
route.intersects?(other_path) # crossing detection
|
|
578
|
+
route.contains?(point) # on-segment check
|
|
579
|
+
|
|
580
|
+
# Enumerable
|
|
581
|
+
route.map { |c| c.lat }
|
|
582
|
+
route.select { |c| c.lat > 40.72 }
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
### Features
|
|
586
|
+
|
|
587
|
+
`Feature` wraps a geometry (any coordinate, area, or path) with a label and a metadata hash. It delegates `distance_to` and `bearing_to` to its geometry, using the centroid for area geometries.
|
|
588
|
+
|
|
589
|
+
```ruby
|
|
590
|
+
liberty = Feature.new(
|
|
591
|
+
label: "Statue of Liberty",
|
|
592
|
+
geometry: Coordinates::LLA.new(lat: 40.6892, lng: -74.0445, alt: 0),
|
|
593
|
+
metadata: { category: "monument", year: 1886 }
|
|
594
|
+
)
|
|
595
|
+
|
|
596
|
+
empire = Feature.new(
|
|
597
|
+
label: "Empire State Building",
|
|
598
|
+
geometry: Coordinates::LLA.new(lat: 40.7484, lng: -73.9857, alt: 0),
|
|
599
|
+
metadata: { category: "building", floors: 102 }
|
|
600
|
+
)
|
|
601
|
+
|
|
602
|
+
liberty.distance_to(empire).to_km # => "8.24 km"
|
|
603
|
+
liberty.bearing_to(empire).degrees # => 36.99
|
|
604
|
+
|
|
605
|
+
# Area geometries use the centroid for distance/bearing
|
|
606
|
+
park = Feature.new(
|
|
607
|
+
label: "Central Park",
|
|
608
|
+
geometry: Areas::Polygon.new(boundary: [...])
|
|
609
|
+
)
|
|
610
|
+
park.distance_to(liberty).to_km # => "12.47 km"
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
All three attributes (`label`, `geometry`, `metadata`) are mutable.
|
|
614
|
+
|
|
534
615
|
### Web Mercator Tile Coordinates
|
|
535
616
|
|
|
536
617
|
```ruby
|
|
@@ -559,6 +640,8 @@ The [`examples/`](examples/) directory contains runnable demo scripts showing pr
|
|
|
559
640
|
| [`02_all_coordinate_systems.rb`](examples/02_all_coordinate_systems.rb) | All 18 coordinate systems, cross-system chains, and areas |
|
|
560
641
|
| [`03_distance_calculations.rb`](examples/03_distance_calculations.rb) | Distance class features, unit conversions, and arithmetic |
|
|
561
642
|
| [`04_bearing_calculations.rb`](examples/04_bearing_calculations.rb) | Bearing class, compass directions, elevation angles, and chain bearings |
|
|
643
|
+
| [`05_map_rendering/`](examples/05_map_rendering/) | Render landmarks on a raster map with Feature objects, polygon areas, bearing arrows, and icons using [libgd-gis](https://rubygems.org/gems/libgd-gis) |
|
|
644
|
+
| [`06_path_operations.rb`](examples/06_path_operations.rb) | Path class: construction, navigation, mutation, path arithmetic, closest approach, containment, Enumerable, equality, subpaths, split, interpolation, bounding boxes, polygon conversion, intersection, path-to-path/area closest points, and Feature integration |
|
|
562
645
|
|
|
563
646
|
Run any example with:
|
|
564
647
|
|
|
@@ -174,10 +174,6 @@ coord.to_s(:integer) # => 608693941536498687
|
|
|
174
174
|
coord.h3_index # => 608693941536498687
|
|
175
175
|
```
|
|
176
176
|
|
|
177
|
-
### `to_slug`
|
|
178
|
-
|
|
179
|
-
Alias for `to_s`. H3 hex strings are already URL-safe.
|
|
180
|
-
|
|
181
177
|
### `to_a`
|
|
182
178
|
|
|
183
179
|
Returns `[lat, lng]` of the cell centroid.
|
|
@@ -84,9 +84,9 @@ Every coordinate system can convert to every other coordinate system. The table
|
|
|
84
84
|
|
|
85
85
|
## Universal Distance and Bearing Calculations
|
|
86
86
|
|
|
87
|
-
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 `
|
|
87
|
+
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 `Geodetic::Coordinate.distance_between` and `Geodetic::Coordinate.straight_line_distance_between` compute consecutive chain distances across a sequence of coordinates.
|
|
88
88
|
|
|
89
|
-
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 `
|
|
89
|
+
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 `Geodetic::Coordinate.bearing_between` computes consecutive chain bearings.
|
|
90
90
|
|
|
91
91
|
See the [Conversions Reference](../reference/conversions.md#distance-calculations) for details on distances and [Bearing Calculations](../reference/conversions.md#bearing-calculations) for bearings.
|
|
92
92
|
|
data/docs/index.md
CHANGED
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
<li><strong>Bearing Calculations</strong> - Forward azimuth, back azimuth, compass directions, elevation angles<br>
|
|
16
16
|
<li><strong>Geoid Height Support</strong> - EGM96, EGM2008, GEOID18, GEOID12B models<br>
|
|
17
17
|
<li><strong>Geographic Areas</strong> - Circle, Polygon, and Rectangle with point-in-area tests<br>
|
|
18
|
+
<li><strong>Paths</strong> - Directed coordinate sequences with navigation, interpolation, closest approach, intersection, and area conversion<br>
|
|
19
|
+
<li><strong>Features</strong> - Named geometry wrapper with metadata and delegated distance/bearing<br>
|
|
18
20
|
<li><strong>Validated Setters</strong> - Type coercion and range validation on all coordinate attributes<br>
|
|
19
21
|
<li><strong>Serialization</strong> - to_s(precision), to_a, from_string, from_array, DMS format<br>
|
|
20
22
|
<li><strong>Multiple Datums</strong> - WGS84, Clarke 1866, GRS 1980, Airy 1830, and more<br>
|
|
@@ -56,12 +58,15 @@ Geodetic supports full bidirectional conversion between all 18 coordinate system
|
|
|
56
58
|
- **16 geodetic datums** -- WGS84, GRS 1980, Clarke 1866, Airy 1830, Bessel 1841, and more. All conversion methods accept an optional datum parameter, defaulting to WGS84.
|
|
57
59
|
- **Geoid height calculations** -- Convert between ellipsoidal and orthometric heights using models such as EGM96, EGM2008, GEOID18, and GEOID12B.
|
|
58
60
|
- **Geographic areas** -- `Geodetic::Areas::Circle`, `Geodetic::Areas::Polygon`, and `Geodetic::Areas::Rectangle` for point-in-area testing.
|
|
61
|
+
- **Paths** -- `Geodetic::Path` is a directed, ordered sequence of unique coordinates supporting navigation, segment analysis, interpolation, closest approach (geometric projection), containment testing, bounding boxes, polygon conversion, and path intersection detection.
|
|
62
|
+
- **Features** -- `Geodetic::Feature` wraps any coordinate, area, or path with a label and metadata hash, delegating `distance_to` and `bearing_to` to the underlying geometry.
|
|
59
63
|
|
|
60
64
|
## Design Principles
|
|
61
65
|
|
|
62
66
|
- All constructors use **keyword arguments** for clarity.
|
|
63
67
|
- Every coordinate system supports **serialization** via `to_s` and `to_a`, and **deserialization** via `from_string` and `from_array`.
|
|
64
68
|
- Conversions are available as instance methods (`to_ecef`, `to_utm`, etc.) and class-level factory methods (`from_ecef`, `from_utm`, etc.).
|
|
69
|
+
- All registered coordinate systems are discoverable at runtime via `Geodetic::Coordinate.systems`.
|
|
65
70
|
|
|
66
71
|
## Quick Example
|
|
67
72
|
|
|
@@ -240,31 +240,31 @@ Universal distance methods are available on all coordinate types and work across
|
|
|
240
240
|
### Great-Circle Distance (Vincenty)
|
|
241
241
|
|
|
242
242
|
- **`distance_to(other, *others)`** — Instance method. Computes the Vincenty great-circle distance from the receiver to one or more target coordinates. Returns a `Distance` for a single target, or an Array of `Distance` objects for multiple targets (radial distances from the receiver).
|
|
243
|
-
- **`
|
|
243
|
+
- **`Geodetic::Coordinate.distance_between(*coords)`** — Class method on `Geodetic::Coordinate`. Computes consecutive chain distances between an ordered sequence of coordinates. Returns a `Distance` for two coordinates, or an Array of `Distance` objects for three or more.
|
|
244
244
|
|
|
245
245
|
> **`Distance` objects** wrap a distance value and provide unit-aware access. Call `.meters` to get the raw Float value in meters, or `.to_f` to get the value in the current display unit.
|
|
246
246
|
|
|
247
247
|
```ruby
|
|
248
|
-
seattle =
|
|
249
|
-
portland =
|
|
250
|
-
sf =
|
|
248
|
+
seattle = Geodetic::Coordinate::LLA.new(lat: 47.6205, lng: -122.3493, alt: 0.0)
|
|
249
|
+
portland = Geodetic::Coordinate::LLA.new(lat: 45.5152, lng: -122.6784, alt: 0.0)
|
|
250
|
+
sf = Geodetic::Coordinate::LLA.new(lat: 37.7749, lng: -122.4194, alt: 0.0)
|
|
251
251
|
|
|
252
252
|
# Radial distances from receiver
|
|
253
253
|
seattle.distance_to(portland) # => Distance (235393.17 m)
|
|
254
254
|
seattle.distance_to(portland, sf) # => [Distance, Distance] (Array)
|
|
255
255
|
|
|
256
256
|
# Consecutive chain distances
|
|
257
|
-
|
|
257
|
+
Geodetic::Coordinate.distance_between(seattle, portland, sf) # => [Distance, Distance] (Array)
|
|
258
258
|
```
|
|
259
259
|
|
|
260
260
|
### Straight-Line Distance (ECEF Euclidean)
|
|
261
261
|
|
|
262
262
|
- **`straight_line_distance_to(other, *others)`** — Instance method. Computes the Euclidean distance in ECEF (3D Cartesian) space. Returns a `Distance` for a single target, or an Array of `Distance` objects for multiple targets.
|
|
263
|
-
- **`
|
|
263
|
+
- **`Geodetic::Coordinate.straight_line_distance_between(*coords)`** — Class method. Computes consecutive chain Euclidean distances.
|
|
264
264
|
|
|
265
265
|
```ruby
|
|
266
266
|
seattle.straight_line_distance_to(portland) # => Distance
|
|
267
|
-
|
|
267
|
+
Geodetic::Coordinate.straight_line_distance_between(seattle, portland) # => Distance
|
|
268
268
|
```
|
|
269
269
|
|
|
270
270
|
### Cross-System Distances
|
|
@@ -273,7 +273,7 @@ Both `distance_to` and `straight_line_distance_to` accept any coordinate type. C
|
|
|
273
273
|
|
|
274
274
|
```ruby
|
|
275
275
|
utm = seattle.to_utm
|
|
276
|
-
mgrs =
|
|
276
|
+
mgrs = Geodetic::Coordinate::MGRS.from_lla(portland)
|
|
277
277
|
utm.distance_to(mgrs) # => Distance (235393.17 m)
|
|
278
278
|
```
|
|
279
279
|
|
|
@@ -282,7 +282,7 @@ utm.distance_to(mgrs) # => Distance (235393.17 m)
|
|
|
282
282
|
ENU and NED are relative coordinate systems and do not support `distance_to` or `straight_line_distance_to` directly. Convert to an absolute system first:
|
|
283
283
|
|
|
284
284
|
```ruby
|
|
285
|
-
ref =
|
|
285
|
+
ref = Geodetic::Coordinate::LLA.new(lat: 47.62, lng: -122.35, alt: 0.0)
|
|
286
286
|
lla = enu.to_lla(ref)
|
|
287
287
|
lla.distance_to(other_lla)
|
|
288
288
|
```
|
|
@@ -299,12 +299,12 @@ Universal bearing methods are available on all coordinate types and work across
|
|
|
299
299
|
|
|
300
300
|
- **`bearing_to(other)`** — Instance method. Computes the great-circle forward azimuth from the receiver to the target coordinate. Returns a `Bearing` object.
|
|
301
301
|
- **`elevation_to(other)`** — Instance method. Computes the vertical look angle (elevation) from the receiver to the target. Returns a Float in degrees (-90 to +90).
|
|
302
|
-
- **`
|
|
302
|
+
- **`Geodetic::Coordinate.bearing_between(*coords)`** — Class method on `Geodetic::Coordinate`. Computes consecutive chain bearings between an ordered sequence of coordinates. Returns a `Bearing` for two coordinates, or an Array of `Bearing` objects for three or more.
|
|
303
303
|
|
|
304
304
|
```ruby
|
|
305
|
-
seattle =
|
|
306
|
-
portland =
|
|
307
|
-
sf =
|
|
305
|
+
seattle = Geodetic::Coordinate::LLA.new(lat: 47.6205, lng: -122.3493, alt: 0.0)
|
|
306
|
+
portland = Geodetic::Coordinate::LLA.new(lat: 45.5152, lng: -122.6784, alt: 0.0)
|
|
307
|
+
sf = Geodetic::Coordinate::LLA.new(lat: 37.7749, lng: -122.4194, alt: 0.0)
|
|
308
308
|
|
|
309
309
|
# Forward azimuth
|
|
310
310
|
b = seattle.bearing_to(portland) # => Bearing
|
|
@@ -316,7 +316,7 @@ b.reverse # => Bearing (back azimuth)
|
|
|
316
316
|
seattle.elevation_to(portland) # => Float (degrees)
|
|
317
317
|
|
|
318
318
|
# Consecutive chain bearings
|
|
319
|
-
|
|
319
|
+
Geodetic::Coordinate.bearing_between(seattle, portland, sf) # => [Bearing, Bearing]
|
|
320
320
|
```
|
|
321
321
|
|
|
322
322
|
### Cross-System Bearings
|
|
@@ -325,7 +325,7 @@ GCS.bearing_between(seattle, portland, sf) # => [Bearing, Bearing]
|
|
|
325
325
|
|
|
326
326
|
```ruby
|
|
327
327
|
utm = seattle.to_utm
|
|
328
|
-
mgrs =
|
|
328
|
+
mgrs = Geodetic::Coordinate::MGRS.from_lla(portland)
|
|
329
329
|
utm.bearing_to(mgrs) # => Bearing
|
|
330
330
|
```
|
|
331
331
|
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Feature Reference
|
|
2
|
+
|
|
3
|
+
`Geodetic::Feature` wraps a geometry with a human-readable label and an arbitrary metadata hash. It provides a single object that ties spatial data to application-level information like names, categories, and display properties.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Constructor
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
Feature.new(
|
|
11
|
+
label: "Statue of Liberty",
|
|
12
|
+
geometry: Geodetic::Coordinate::LLA.new(lat: 40.6892, lng: -74.0445, alt: 0),
|
|
13
|
+
metadata: { category: "monument", year: 1886 }
|
|
14
|
+
)
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The `geometry` parameter accepts any coordinate class, any area class (`Circle`, `Polygon`, `Rectangle`), or a `Path`. The `metadata` hash is optional and defaults to `{}`.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Attributes
|
|
22
|
+
|
|
23
|
+
| Attribute | Type | Mutable | Description |
|
|
24
|
+
|------------|--------|---------|-------------|
|
|
25
|
+
| `label` | String | Yes | A display name for the feature |
|
|
26
|
+
| `geometry` | Object | Yes | A coordinate or area object |
|
|
27
|
+
| `metadata` | Hash | Yes | Arbitrary key-value pairs |
|
|
28
|
+
|
|
29
|
+
All three attributes have both reader and writer methods.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Geometry Types
|
|
34
|
+
|
|
35
|
+
A Feature's geometry can be any of:
|
|
36
|
+
|
|
37
|
+
- **Coordinate** — any of the 18 coordinate classes (`LLA`, `ECEF`, `UTM`, etc.)
|
|
38
|
+
- **Area** — `Areas::Circle`, `Areas::Polygon`, or `Areas::Rectangle`
|
|
39
|
+
- **Path** — a `Geodetic::Path` representing a route or trail
|
|
40
|
+
|
|
41
|
+
When the geometry is an area, `distance_to` and `bearing_to` use the area's `centroid` as the reference point. When the geometry is a Path, `distance_to` and `bearing_to` use geometric projection to find the closest approach point on the path.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Methods
|
|
46
|
+
|
|
47
|
+
### `distance_to(other)`
|
|
48
|
+
|
|
49
|
+
Returns a `Geodetic::Distance` between this feature and another feature, coordinate, or area. When either side is an area geometry, its centroid is used.
|
|
50
|
+
|
|
51
|
+
The `other` parameter can be a `Feature`, a coordinate, or an area.
|
|
52
|
+
|
|
53
|
+
```ruby
|
|
54
|
+
liberty = Feature.new(label: "Liberty", geometry: LLA.new(lat: 40.6892, lng: -74.0445, alt: 0))
|
|
55
|
+
empire = Feature.new(label: "Empire", geometry: LLA.new(lat: 40.7484, lng: -73.9857, alt: 0))
|
|
56
|
+
|
|
57
|
+
liberty.distance_to(empire).to_km # => "8.24 km"
|
|
58
|
+
|
|
59
|
+
# Also works with a raw coordinate
|
|
60
|
+
liberty.distance_to(LLA.new(lat: 40.7484, lng: -73.9857, alt: 0))
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### `bearing_to(other)`
|
|
64
|
+
|
|
65
|
+
Returns a `Geodetic::Bearing` from this feature to another feature, coordinate, or area. Uses the same centroid resolution as `distance_to`.
|
|
66
|
+
|
|
67
|
+
```ruby
|
|
68
|
+
liberty.bearing_to(empire).degrees # => 36.99
|
|
69
|
+
liberty.bearing_to(empire).to_compass # => "NE"
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### `to_s`
|
|
73
|
+
|
|
74
|
+
Returns `"label (geometry.to_s)"`.
|
|
75
|
+
|
|
76
|
+
```ruby
|
|
77
|
+
liberty.to_s # => "Liberty (40.689200, -74.044500, 0.00)"
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### `inspect`
|
|
81
|
+
|
|
82
|
+
Returns a detailed string with label, geometry, and metadata.
|
|
83
|
+
|
|
84
|
+
```ruby
|
|
85
|
+
liberty.inspect
|
|
86
|
+
# => "#<Geodetic::Feature name=\"Liberty\" geometry=#<Geodetic::Coordinate::LLA ...> metadata={}>"
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Area Geometry Example
|
|
92
|
+
|
|
93
|
+
```ruby
|
|
94
|
+
park_boundary = Areas::Polygon.new(boundary: [
|
|
95
|
+
LLA.new(lat: 40.7679, lng: -73.9818, alt: 0),
|
|
96
|
+
LLA.new(lat: 40.7649, lng: -73.9727, alt: 0),
|
|
97
|
+
LLA.new(lat: 40.8003, lng: -73.9494, alt: 0),
|
|
98
|
+
LLA.new(lat: 40.8008, lng: -73.9585, alt: 0),
|
|
99
|
+
])
|
|
100
|
+
|
|
101
|
+
park = Feature.new(label: "Central Park", geometry: park_boundary)
|
|
102
|
+
|
|
103
|
+
# distance_to uses the polygon's centroid
|
|
104
|
+
park.distance_to(liberty) # => Distance
|
|
105
|
+
park.bearing_to(liberty) # => Bearing
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Centroid Resolution
|
|
111
|
+
|
|
112
|
+
When computing distances and bearings, Feature resolves the underlying point as follows:
|
|
113
|
+
|
|
114
|
+
- If the geometry responds to `centroid` (all area classes do), the centroid is used.
|
|
115
|
+
- Otherwise, the geometry itself is used directly (all coordinate classes).
|
|
116
|
+
|
|
117
|
+
This applies to both the source feature and the target. When the target is a Feature, its geometry is resolved the same way. When the target is a raw coordinate or area, the same logic applies.
|