geodetic 0.3.2 → 0.4.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 +41 -2
- data/README.md +45 -8
- data/docs/coordinate-systems/gars.md +2 -2
- data/docs/coordinate-systems/georef.md +2 -2
- data/docs/coordinate-systems/gh.md +2 -2
- data/docs/coordinate-systems/gh36.md +2 -2
- data/docs/coordinate-systems/h3.md +2 -2
- data/docs/coordinate-systems/ham.md +2 -2
- data/docs/coordinate-systems/olc.md +2 -2
- data/docs/index.md +4 -2
- data/docs/reference/areas.md +140 -14
- data/docs/reference/feature.md +2 -2
- data/docs/reference/path.md +3 -3
- data/docs/reference/segment.md +181 -0
- data/examples/02_all_coordinate_systems.rb +6 -6
- data/examples/06_path_operations.rb +2 -4
- data/examples/07_segments_and_shapes.rb +258 -0
- data/examples/README.md +19 -1
- data/lib/geodetic/areas/bounding_box.rb +56 -0
- data/lib/geodetic/areas/hexagon.rb +11 -0
- data/lib/geodetic/areas/octagon.rb +11 -0
- data/lib/geodetic/areas/pentagon.rb +11 -0
- data/lib/geodetic/areas/polygon.rb +54 -14
- data/lib/geodetic/areas/rectangle.rb +85 -35
- data/lib/geodetic/areas/regular_polygon.rb +59 -0
- data/lib/geodetic/areas/triangle.rb +180 -0
- data/lib/geodetic/areas.rb +6 -0
- data/lib/geodetic/coordinate/gh36.rb +1 -1
- data/lib/geodetic/coordinate/h3.rb +1 -1
- data/lib/geodetic/coordinate/spatial_hash.rb +2 -2
- data/lib/geodetic/path.rb +26 -153
- data/lib/geodetic/segment.rb +172 -0
- data/lib/geodetic/version.rb +1 -1
- data/lib/geodetic.rb +1 -0
- data/mkdocs.yml +1 -0
- metadata +10 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f85da37953a9e974422502d62fbf82279fcf4f0ff71594ada2bb150e07cc75d1
|
|
4
|
+
data.tar.gz: a986282c09df583d9de453034f36e1a15c0d29cc016f586f09239f4f642d6fae
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2d72a9b0e9e1a0688f25659dbff3d35e8b1c001c72468d2d2bd5c64ffe9fd2f16315dbbe4ec429c545115268e7486db81b26c4f6c9d625de3a8089b82cbcf8df
|
|
7
|
+
data.tar.gz: 031eb9604c27d9683c74bf3483b913a5ddb065ed78bd5375f23297f39b923f5a6212498c236982a310afe25fa5ca300df0dfaaa60d8632ad02950c75c2662e15
|
data/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,45 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
11
11
|
## [Unreleased]
|
|
12
12
|
|
|
13
13
|
|
|
14
|
+
## [0.4.0] - 2026-03-10
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- **`Geodetic::Segment` class** — directed two-point line segment, the fundamental geometric primitive underlying Path and Polygon
|
|
19
|
+
- **Properties**: `length`/`distance` (returns Distance), `length_meters`, `bearing` (returns Bearing), `midpoint`/`centroid` (returns LLA) — all lazily computed and cached
|
|
20
|
+
- **Projection**: `project(point)` returns the closest point on the segment and perpendicular distance
|
|
21
|
+
- **Interpolation**: `interpolate(fraction)` returns the LLA at any fraction along the segment
|
|
22
|
+
- **Membership**: `includes?(point)` (vertex-only check), `contains?(point, tolerance:)` (on-segment check), `excludes?`
|
|
23
|
+
- **Intersection**: `intersects?(other_segment)` using cross-product orientation tests
|
|
24
|
+
- **Conversion**: `reverse`, `to_path`, `to_a`, `==`, `to_s`, `inspect`
|
|
25
|
+
- **`Geodetic::Areas::Triangle`** — polygon subclass with four construction modes
|
|
26
|
+
- Isosceles: `Triangle.new(center:, width:, height:, bearing:)`
|
|
27
|
+
- Equilateral by circumradius: `Triangle.new(center:, radius:, bearing:)`
|
|
28
|
+
- Equilateral by side length: `Triangle.new(center:, side:, bearing:)`
|
|
29
|
+
- Arbitrary vertices: `Triangle.new(vertices: [p1, p2, p3])`
|
|
30
|
+
- Predicates: `equilateral?`, `isosceles?`, `scalene?` based on actual side lengths (5m tolerance)
|
|
31
|
+
- Methods: `vertices`, `side_lengths`, `base`, `to_bounding_box`
|
|
32
|
+
- **`Geodetic::Areas::Rectangle`** — polygon subclass defined by a centerline Segment and perpendicular width
|
|
33
|
+
- `Rectangle.new(segment:, width:)` — accepts a Segment object or a two-element array of coordinates
|
|
34
|
+
- `width:` accepts numeric (meters) or a Distance instance
|
|
35
|
+
- Derived properties: `center`, `height`, `bearing` from the centerline; `corners`, `square?`, `to_bounding_box`
|
|
36
|
+
- **`Geodetic::Areas::Pentagon`**, **`Hexagon`**, **`Octagon`** — regular polygon subclasses from center + radius + bearing
|
|
37
|
+
- **Polygon self-intersection validation** — `Polygon.new` validates that no edge crosses another; pass `validate: false` to skip (used by subclasses with generated geometry)
|
|
38
|
+
- **Polygon `segments` method** — returns `Array<Segment>` for each edge; `edges` and `border` are aliases
|
|
39
|
+
- **Segments and shapes example** (`examples/07_segments_and_shapes.rb`) — 10-section demo covering Segment operations, Triangle/Rectangle construction and predicates, regular polygons, containment, bounding boxes, and Feature integration
|
|
40
|
+
- Documentation: `docs/reference/segment.md` (Segment reference with Great Circle Arcs section), updated `docs/reference/areas.md` with all polygon subclasses
|
|
41
|
+
|
|
42
|
+
### Changed
|
|
43
|
+
|
|
44
|
+
- **Refactored `Path`** to use Segment objects — removed ~140 lines of private segment methods (`project_onto_segment`, `on_segment?`, `segments_intersect?`, `cross_sign`, `on_collinear?`, `to_flat`); all segment operations now delegate to Segment
|
|
45
|
+
- **Refactored `Polygon`** — `segments` is now the primary method (was `edges`); `edges` and `border` are aliases
|
|
46
|
+
- **Updated `Feature`** to support Segment as a geometry type via `centroid` (alias for `midpoint`)
|
|
47
|
+
- Updated README, `docs/index.md`, `docs/reference/areas.md`, `docs/reference/segment.md`, `examples/README.md`, and mkdocs nav
|
|
48
|
+
|
|
49
|
+
### Removed
|
|
50
|
+
|
|
51
|
+
- `Areas::Rectangle = Areas::BoundingBox` alias — Rectangle is now its own class (Polygon subclass)
|
|
52
|
+
|
|
14
53
|
## [0.3.2] - 2026-03-09
|
|
15
54
|
|
|
16
55
|
### Added
|
|
@@ -19,11 +58,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
19
58
|
- **Navigation**: `first`, `last`, `next`, `prev`, `segments`, `size`, `empty?`
|
|
20
59
|
- **Membership**: `include?`/`includes?` (waypoint check), `contains?`/`inside?` (on-segment check with configurable tolerance)
|
|
21
60
|
- **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-
|
|
61
|
+
- **Closest points**: `closest_points_to` for Path-to-Path, Path-to-Polygon, Path-to-BoundingBox, and Path-to-Circle
|
|
23
62
|
- **Computed**: `total_distance`, `segment_distances`, `segment_bearings`, `reverse`
|
|
24
63
|
- **Subpath/split**: `between(from, to)` extracts a subpath; `split_at(coord)` divides into two paths sharing the split point
|
|
25
64
|
- **Interpolation**: `at_distance(distance)` finds the coordinate at a given distance along the path
|
|
26
|
-
- **Bounding box**: `bounds` returns an `Areas::
|
|
65
|
+
- **Bounding box**: `bounds` returns an `Areas::BoundingBox`
|
|
27
66
|
- **Polygon conversion**: `to_polygon` closes the path (validates no self-intersection)
|
|
28
67
|
- **Intersection**: `intersects?(other_path)` detects crossing segments
|
|
29
68
|
- **Equality**: `==` compares coordinates in order
|
data/README.md
CHANGED
|
@@ -18,7 +18,8 @@
|
|
|
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>
|
|
20
20
|
- <strong>Geoid Height Support</strong> - EGM96, EGM2008, GEOID18, GEOID12B models<br>
|
|
21
|
-
- <strong>Geographic Areas</strong> - Circle, Polygon,
|
|
21
|
+
- <strong>Geographic Areas</strong> - Circle, Polygon, BoundingBox, Triangle, Rectangle, Pentagon, Hexagon, Octagon<br>
|
|
22
|
+
- <strong>Segments</strong> - Directed two-point line segments with projection, intersection, and interpolation<br>
|
|
22
23
|
- <strong>Paths</strong> - Directed coordinate sequences with navigation, interpolation, closest approach, intersection, and area conversion<br>
|
|
23
24
|
- <strong>Features</strong> - Named geometry wrapper with metadata and delegated distance/bearing<br>
|
|
24
25
|
- <strong>Validated Setters</strong> - Type coercion and range validation on all coordinate attributes<br>
|
|
@@ -426,7 +427,7 @@ lla = gh36.to_lla
|
|
|
426
427
|
gh36.neighbors # => { N: GH36, S: GH36, E: GH36, W: GH36, NE: ..., NW: ..., SE: ..., SW: ... }
|
|
427
428
|
|
|
428
429
|
# Bounding rectangle of the geohash cell
|
|
429
|
-
area = gh36.to_area # => Areas::
|
|
430
|
+
area = gh36.to_area # => Areas::BoundingBox
|
|
430
431
|
area.includes?(gh36.to_lla) # => true
|
|
431
432
|
|
|
432
433
|
# Precision info
|
|
@@ -453,7 +454,7 @@ lla = gh.to_lla
|
|
|
453
454
|
gh.neighbors # => { N: GH, S: GH, E: GH, W: GH, NE: ..., NW: ..., SE: ..., SW: ... }
|
|
454
455
|
|
|
455
456
|
# Bounding rectangle of the geohash cell
|
|
456
|
-
area = gh.to_area # => Areas::
|
|
457
|
+
area = gh.to_area # => Areas::BoundingBox
|
|
457
458
|
area.includes?(gh.to_lla) # => true
|
|
458
459
|
|
|
459
460
|
# Precision info
|
|
@@ -480,7 +481,7 @@ lla = ham.to_lla
|
|
|
480
481
|
ham.neighbors # => { N: HAM, S: HAM, E: HAM, W: HAM, NE: ..., NW: ..., SE: ..., SW: ... }
|
|
481
482
|
|
|
482
483
|
# Bounding rectangle of the grid square
|
|
483
|
-
area = ham.to_area # => Areas::
|
|
484
|
+
area = ham.to_area # => Areas::BoundingBox
|
|
484
485
|
area.includes?(ham.to_lla) # => true
|
|
485
486
|
|
|
486
487
|
# Precision info
|
|
@@ -507,7 +508,7 @@ lla = olc.to_lla
|
|
|
507
508
|
olc.neighbors # => { N: OLC, S: OLC, E: OLC, W: OLC, NE: ..., NW: ..., SE: ..., SW: ... }
|
|
508
509
|
|
|
509
510
|
# Bounding rectangle of the plus code cell
|
|
510
|
-
area = olc.to_area # => Areas::
|
|
511
|
+
area = olc.to_area # => Areas::BoundingBox
|
|
511
512
|
area.includes?(olc.to_lla) # => true
|
|
512
513
|
|
|
513
514
|
# Precision info
|
|
@@ -532,16 +533,52 @@ points = [
|
|
|
532
533
|
polygon = Areas::Polygon.new(boundary: points)
|
|
533
534
|
polygon.centroid # => computed centroid as LLA
|
|
534
535
|
|
|
535
|
-
#
|
|
536
|
+
# BoundingBox area (accepts any coordinate type)
|
|
536
537
|
nw = Coordinates::LLA.new(lat: 41.0, lng: -75.0)
|
|
537
538
|
se = Coordinates::LLA.new(lat: 40.0, lng: -74.0)
|
|
538
|
-
rect = Areas::
|
|
539
|
+
rect = Areas::BoundingBox.new(nw: nw, se: se)
|
|
539
540
|
rect.centroid # => LLA at center
|
|
540
541
|
rect.ne # => computed NE corner
|
|
541
542
|
rect.sw # => computed SW corner
|
|
542
543
|
rect.includes?(point) # => true/false
|
|
543
544
|
```
|
|
544
545
|
|
|
546
|
+
### Segments
|
|
547
|
+
|
|
548
|
+
`Segment` represents a directed line segment between two points. It provides the geometric primitives that `Path` and `Polygon` build on.
|
|
549
|
+
|
|
550
|
+
```ruby
|
|
551
|
+
a = Coordinate::LLA.new(lat: 40.7484, lng: -73.9857, alt: 0)
|
|
552
|
+
b = Coordinate::LLA.new(lat: 40.7580, lng: -73.9855, alt: 0)
|
|
553
|
+
|
|
554
|
+
seg = Segment.new(a, b)
|
|
555
|
+
|
|
556
|
+
# Properties (lazily computed, cached)
|
|
557
|
+
seg.length # => Distance
|
|
558
|
+
seg.distance # => Distance (alias for length)
|
|
559
|
+
seg.bearing # => Bearing
|
|
560
|
+
seg.midpoint # => LLA at halfway point
|
|
561
|
+
|
|
562
|
+
# Projection — closest point on segment to a target
|
|
563
|
+
foot, dist_m = seg.project(target_point)
|
|
564
|
+
|
|
565
|
+
# Interpolation — point at fraction along segment
|
|
566
|
+
seg.interpolate(0.25) # => LLA at quarter-way
|
|
567
|
+
|
|
568
|
+
# Membership
|
|
569
|
+
seg.includes?(a) # => true (vertex check only)
|
|
570
|
+
seg.includes?(seg.midpoint) # => false
|
|
571
|
+
seg.contains?(seg.midpoint) # => true (on-segment check)
|
|
572
|
+
|
|
573
|
+
# Intersection
|
|
574
|
+
seg.intersects?(other_seg) # => true/false
|
|
575
|
+
|
|
576
|
+
# Conversion
|
|
577
|
+
seg.reverse # => Segment with swapped endpoints
|
|
578
|
+
seg.to_path # => two-point Path
|
|
579
|
+
seg.to_a # => [start_point, end_point]
|
|
580
|
+
```
|
|
581
|
+
|
|
545
582
|
### Paths
|
|
546
583
|
|
|
547
584
|
`Path` is a directed, ordered sequence of unique coordinates representing routes, trails, or boundaries.
|
|
@@ -572,7 +609,7 @@ route.closest_points_to(other_path) # path-to-path
|
|
|
572
609
|
sub = route.between(a, b) # extract subpath
|
|
573
610
|
left, right = route.split_at(c) # split at waypoint
|
|
574
611
|
route.at_distance(Distance.km(2)) # interpolate along path
|
|
575
|
-
route.bounds # => Areas::
|
|
612
|
+
route.bounds # => Areas::BoundingBox
|
|
576
613
|
route.to_polygon # close into polygon
|
|
577
614
|
route.intersects?(other_path) # crossing detection
|
|
578
615
|
route.contains?(point) # on-segment check
|
|
@@ -173,11 +173,11 @@ Neighbors preserve the same precision as the original code. Latitude is clamped
|
|
|
173
173
|
|
|
174
174
|
## Area
|
|
175
175
|
|
|
176
|
-
The `to_area` method returns the GARS cell as a `Geodetic::Areas::
|
|
176
|
+
The `to_area` method returns the GARS cell as a `Geodetic::Areas::BoundingBox`.
|
|
177
177
|
|
|
178
178
|
```ruby
|
|
179
179
|
area = coord.to_area
|
|
180
|
-
# => Geodetic::Areas::
|
|
180
|
+
# => Geodetic::Areas::BoundingBox
|
|
181
181
|
|
|
182
182
|
area.includes?(coord.to_lla) # => true (midpoint is inside the cell)
|
|
183
183
|
area.nw # => LLA (northwest corner)
|
|
@@ -154,11 +154,11 @@ Neighbors preserve the same precision as the original code. Latitude is clamped
|
|
|
154
154
|
|
|
155
155
|
## Area
|
|
156
156
|
|
|
157
|
-
The `to_area` method returns the GEOREF cell as a `Geodetic::Areas::
|
|
157
|
+
The `to_area` method returns the GEOREF cell as a `Geodetic::Areas::BoundingBox`.
|
|
158
158
|
|
|
159
159
|
```ruby
|
|
160
160
|
area = coord.to_area
|
|
161
|
-
# => Geodetic::Areas::
|
|
161
|
+
# => Geodetic::Areas::BoundingBox
|
|
162
162
|
|
|
163
163
|
area.includes?(coord.to_lla) # => true (midpoint is inside the cell)
|
|
164
164
|
area.nw # => LLA (northwest corner)
|
|
@@ -142,11 +142,11 @@ Neighbors preserve the same precision as the original geohash. Longitude wraps c
|
|
|
142
142
|
|
|
143
143
|
## Area
|
|
144
144
|
|
|
145
|
-
The `to_area` method returns the geohash cell as a `Geodetic::Areas::
|
|
145
|
+
The `to_area` method returns the geohash cell as a `Geodetic::Areas::BoundingBox`.
|
|
146
146
|
|
|
147
147
|
```ruby
|
|
148
148
|
area = coord.to_area
|
|
149
|
-
# => Geodetic::Areas::
|
|
149
|
+
# => Geodetic::Areas::BoundingBox
|
|
150
150
|
|
|
151
151
|
area.includes?(coord.to_lla) # => true (midpoint is inside the cell)
|
|
152
152
|
area.nw # => LLA (northwest corner)
|
|
@@ -137,11 +137,11 @@ Neighbor computation propagates carries when the adjustment wraps beyond the mat
|
|
|
137
137
|
|
|
138
138
|
## Area
|
|
139
139
|
|
|
140
|
-
The `to_area` method returns the geohash cell as a `Geodetic::Areas::
|
|
140
|
+
The `to_area` method returns the geohash cell as a `Geodetic::Areas::BoundingBox`.
|
|
141
141
|
|
|
142
142
|
```ruby
|
|
143
143
|
area = coord.to_area
|
|
144
|
-
# => Geodetic::Areas::
|
|
144
|
+
# => Geodetic::Areas::BoundingBox
|
|
145
145
|
|
|
146
146
|
area.includes?(coord.to_lla) # => true (midpoint is inside the cell)
|
|
147
147
|
area.nw # => LLA (northwest corner)
|
|
@@ -36,8 +36,8 @@ Geodetic searches these paths automatically:
|
|
|
36
36
|
|
|
37
37
|
| Feature | GH/OLC/GARS/GEOREF/HAM | H3 |
|
|
38
38
|
|---------|------------------------|-----|
|
|
39
|
-
| Cell shape |
|
|
40
|
-
| `to_area` returns | `Areas::
|
|
39
|
+
| Cell shape | BoundingBox | Hexagon (6 vertices) |
|
|
40
|
+
| `to_area` returns | `Areas::BoundingBox` | `Areas::Polygon` |
|
|
41
41
|
| `neighbors` returns | Hash with 8 cardinal keys | Array of 6 cells |
|
|
42
42
|
| Code format | String | 64-bit integer (hex string) |
|
|
43
43
|
| Dependency | None (pure Ruby) | `libh3` (C library via fiddle) |
|
|
@@ -150,11 +150,11 @@ Neighbors preserve the same precision as the original locator. Latitude is clamp
|
|
|
150
150
|
|
|
151
151
|
## Area
|
|
152
152
|
|
|
153
|
-
The `to_area` method returns the grid square as a `Geodetic::Areas::
|
|
153
|
+
The `to_area` method returns the grid square as a `Geodetic::Areas::BoundingBox`.
|
|
154
154
|
|
|
155
155
|
```ruby
|
|
156
156
|
area = coord.to_area
|
|
157
|
-
# => Geodetic::Areas::
|
|
157
|
+
# => Geodetic::Areas::BoundingBox
|
|
158
158
|
|
|
159
159
|
area.includes?(coord.to_lla) # => true (midpoint is inside the cell)
|
|
160
160
|
area.nw # => LLA (northwest corner)
|
|
@@ -155,11 +155,11 @@ Neighbors preserve the same precision as the original code. Latitude is clamped
|
|
|
155
155
|
|
|
156
156
|
## Area
|
|
157
157
|
|
|
158
|
-
The `to_area` method returns the plus code cell as a `Geodetic::Areas::
|
|
158
|
+
The `to_area` method returns the plus code cell as a `Geodetic::Areas::BoundingBox`.
|
|
159
159
|
|
|
160
160
|
```ruby
|
|
161
161
|
area = coord.to_area
|
|
162
|
-
# => Geodetic::Areas::
|
|
162
|
+
# => Geodetic::Areas::BoundingBox
|
|
163
163
|
|
|
164
164
|
area.includes?(coord.to_lla) # => true (midpoint is inside the cell)
|
|
165
165
|
area.nw # => LLA (northwest corner)
|
data/docs/index.md
CHANGED
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
<li><strong>Distance Calculations</strong> - Vincenty great-circle and straight-line with unit tracking<br>
|
|
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
|
-
<li><strong>Geographic Areas</strong> - Circle, Polygon,
|
|
17
|
+
<li><strong>Geographic Areas</strong> - Circle, Polygon, BoundingBox, Triangle, Rectangle, Pentagon, Hexagon, Octagon<br>
|
|
18
|
+
<li><strong>Segments</strong> - Directed two-point line segments with projection, intersection, and interpolation<br>
|
|
18
19
|
<li><strong>Paths</strong> - Directed coordinate sequences with navigation, interpolation, closest approach, intersection, and area conversion<br>
|
|
19
20
|
<li><strong>Features</strong> - Named geometry wrapper with metadata and delegated distance/bearing<br>
|
|
20
21
|
<li><strong>Validated Setters</strong> - Type coercion and range validation on all coordinate attributes<br>
|
|
@@ -57,7 +58,8 @@ Geodetic supports full bidirectional conversion between all 18 coordinate system
|
|
|
57
58
|
|
|
58
59
|
- **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.
|
|
59
60
|
- **Geoid height calculations** -- Convert between ellipsoidal and orthometric heights using models such as EGM96, EGM2008, GEOID18, and GEOID12B.
|
|
60
|
-
- **Geographic areas** -- `Geodetic::Areas::Circle`, `Geodetic::Areas::Polygon`,
|
|
61
|
+
- **Geographic areas** -- `Geodetic::Areas::Circle`, `Geodetic::Areas::Polygon`, `Geodetic::Areas::BoundingBox`, plus polygon subclasses (`Triangle`, `Rectangle`, `Pentagon`, `Hexagon`, `Octagon`) for point-in-area testing.
|
|
62
|
+
- **Segments** -- `Geodetic::Segment` is a directed two-point line segment with projection, intersection detection, interpolation, and membership testing. It is the geometric primitive underlying Path and Polygon operations.
|
|
61
63
|
- **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
64
|
- **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.
|
|
63
65
|
|
data/docs/reference/areas.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Areas Reference
|
|
2
2
|
|
|
3
|
-
The `Geodetic::Areas` module provides
|
|
3
|
+
The `Geodetic::Areas` module provides geometric area classes for point-in-area testing: `Circle`, `Polygon`, `BoundingBox`, and polygon subclasses (`Triangle`, `Rectangle`, `Pentagon`, `Hexagon`, `Octagon`). All operate on `Geodetic::Coordinate::LLA` points.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -107,18 +107,38 @@ Returns `true` if the given LLA point falls outside the polygon. The logical inv
|
|
|
107
107
|
polygon.excludes?(point) # => true or false
|
|
108
108
|
```
|
|
109
109
|
|
|
110
|
+
#### `segments` / `edges` / `border`
|
|
111
|
+
|
|
112
|
+
Returns an array of `Segment` objects for each edge of the polygon.
|
|
113
|
+
|
|
114
|
+
```ruby
|
|
115
|
+
polygon.segments # => [Segment(p1→p2), Segment(p2→p3), Segment(p3→p1)]
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Self-Intersection Validation
|
|
119
|
+
|
|
120
|
+
By default the constructor validates that no edge crosses another edge. Pass `validate: false` to skip this check (used by subclasses that generate known-good geometry).
|
|
121
|
+
|
|
122
|
+
```ruby
|
|
123
|
+
Geodetic::Areas::Polygon.new(boundary: points) # validates
|
|
124
|
+
Geodetic::Areas::Polygon.new(boundary: points, validate: false) # skips
|
|
125
|
+
```
|
|
126
|
+
|
|
110
127
|
### Alias Summary
|
|
111
128
|
|
|
112
129
|
| Primary Method | Aliases |
|
|
113
130
|
|---------------|---------|
|
|
114
131
|
| `includes?` | `include?`, `inside?` |
|
|
115
132
|
| `excludes?` | `exclude?`, `outside?` |
|
|
133
|
+
| `segments` | `edges`, `border` |
|
|
116
134
|
|
|
117
135
|
---
|
|
118
136
|
|
|
119
|
-
## Geodetic::Areas::
|
|
137
|
+
## Geodetic::Areas::BoundingBox
|
|
120
138
|
|
|
121
|
-
Defines an axis-aligned
|
|
139
|
+
Defines an axis-aligned bounding box by its northwest and southeast corners. Edges are always oriented East-West and North-South.
|
|
140
|
+
|
|
141
|
+
> **Note:** `Rectangle` is a separate class (`Polygon` subclass) constructed from a `Segment` centerline. It is not related to `BoundingBox`.
|
|
122
142
|
|
|
123
143
|
### Constructor
|
|
124
144
|
|
|
@@ -126,7 +146,7 @@ Defines an axis-aligned rectangle by its northwest and southeast corners.
|
|
|
126
146
|
nw = Geodetic::Coordinate::LLA.new(lat: 41.0, lng: -75.0)
|
|
127
147
|
se = Geodetic::Coordinate::LLA.new(lat: 40.0, lng: -74.0)
|
|
128
148
|
|
|
129
|
-
|
|
149
|
+
bbox = Geodetic::Areas::BoundingBox.new(nw: nw, se: se)
|
|
130
150
|
```
|
|
131
151
|
|
|
132
152
|
The constructor accepts any coordinate type that responds to `to_lla` -- coordinates are automatically converted to LLA.
|
|
@@ -134,7 +154,7 @@ The constructor accepts any coordinate type that responds to `to_lla` -- coordin
|
|
|
134
154
|
```ruby
|
|
135
155
|
nw_wm = Geodetic::Coordinate::WebMercator.from_lla(nw)
|
|
136
156
|
se_wm = Geodetic::Coordinate::WebMercator.from_lla(se)
|
|
137
|
-
|
|
157
|
+
bbox = Geodetic::Areas::BoundingBox.new(nw: nw_wm, se: se_wm)
|
|
138
158
|
```
|
|
139
159
|
|
|
140
160
|
Raises `ArgumentError` if the NW corner has a lower latitude than the SE corner, or if the NW corner has a higher longitude than the SE corner.
|
|
@@ -152,27 +172,27 @@ All attributes are read-only.
|
|
|
152
172
|
### Computed Corners
|
|
153
173
|
|
|
154
174
|
```ruby
|
|
155
|
-
|
|
156
|
-
|
|
175
|
+
bbox.ne # => LLA (nw.lat, se.lng)
|
|
176
|
+
bbox.sw # => LLA (se.lat, nw.lng)
|
|
157
177
|
```
|
|
158
178
|
|
|
159
179
|
### Methods
|
|
160
180
|
|
|
161
181
|
#### `includes?(a_point)` / `include?(a_point)` / `inside?(a_point)`
|
|
162
182
|
|
|
163
|
-
Returns `true` if the given point falls within (or on the boundary of) the
|
|
183
|
+
Returns `true` if the given point falls within (or on the boundary of) the bounding box. Accepts any coordinate type that responds to `to_lla`.
|
|
164
184
|
|
|
165
185
|
```ruby
|
|
166
186
|
point = Geodetic::Coordinate::LLA.new(lat: 40.5, lng: -74.5)
|
|
167
|
-
|
|
187
|
+
bbox.includes?(point) # => true
|
|
168
188
|
```
|
|
169
189
|
|
|
170
190
|
#### `excludes?(a_point)` / `exclude?(a_point)` / `outside?(a_point)`
|
|
171
191
|
|
|
172
|
-
Returns `true` if the given point falls outside the
|
|
192
|
+
Returns `true` if the given point falls outside the bounding box.
|
|
173
193
|
|
|
174
194
|
```ruby
|
|
175
|
-
|
|
195
|
+
bbox.excludes?(point) # => true or false
|
|
176
196
|
```
|
|
177
197
|
|
|
178
198
|
### Alias Summary
|
|
@@ -182,14 +202,120 @@ rectangle.excludes?(point) # => true or false
|
|
|
182
202
|
| `includes?` | `include?`, `inside?` |
|
|
183
203
|
| `excludes?` | `exclude?`, `outside?` |
|
|
184
204
|
|
|
185
|
-
### Integration with
|
|
205
|
+
### Integration with Spatial Hashes
|
|
186
206
|
|
|
187
|
-
`
|
|
207
|
+
Spatial hash coordinate systems (`GH`, `GH36`, `HAM`, `OLC`, `GEOREF`, `GARS`) return a `BoundingBox` from `to_area`:
|
|
188
208
|
|
|
189
209
|
```ruby
|
|
190
210
|
gh36 = Geodetic::Coordinate::GH36.new("bdrdC26BqH")
|
|
191
211
|
area = gh36.to_area
|
|
192
|
-
# => Geodetic::Areas::
|
|
212
|
+
# => Geodetic::Areas::BoundingBox
|
|
193
213
|
|
|
194
214
|
area.includes?(gh36.to_lla) # => true (midpoint is inside the cell)
|
|
195
215
|
```
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## Polygon Subclasses
|
|
220
|
+
|
|
221
|
+
All polygon subclasses inherit from `Polygon` and share its `includes?`/`excludes?`, `segments`, `centroid`, and `boundary` methods. Each adds shape-specific constructors and attributes.
|
|
222
|
+
|
|
223
|
+
### Geodetic::Areas::Triangle
|
|
224
|
+
|
|
225
|
+
A three-sided polygon with four construction modes.
|
|
226
|
+
|
|
227
|
+
#### Constructors
|
|
228
|
+
|
|
229
|
+
```ruby
|
|
230
|
+
center = Geodetic::Coordinate::LLA.new(lat: 40.7484, lng: -73.9857, alt: 0)
|
|
231
|
+
|
|
232
|
+
# Isosceles — width and height from a center point
|
|
233
|
+
tri = Geodetic::Areas::Triangle.new(center: center, width: 400, height: 600, bearing: 0)
|
|
234
|
+
|
|
235
|
+
# Equilateral by circumradius
|
|
236
|
+
tri = Geodetic::Areas::Triangle.new(center: center, radius: 500, bearing: 45)
|
|
237
|
+
|
|
238
|
+
# Equilateral by side length
|
|
239
|
+
tri = Geodetic::Areas::Triangle.new(center: center, side: 600)
|
|
240
|
+
|
|
241
|
+
# Arbitrary 3 vertices
|
|
242
|
+
tri = Geodetic::Areas::Triangle.new(vertices: [p1, p2, p3])
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
#### Attributes and Methods
|
|
246
|
+
|
|
247
|
+
| Method | Returns | Description |
|
|
248
|
+
|--------|---------|-------------|
|
|
249
|
+
| `sides` | Integer | Always 3 |
|
|
250
|
+
| `vertices` | Array<LLA> | The three vertices |
|
|
251
|
+
| `center` | LLA | Center point (or centroid for arbitrary vertices) |
|
|
252
|
+
| `width` | Float | Base width in meters (0 for arbitrary vertices) |
|
|
253
|
+
| `height` | Float | Height in meters (0 for arbitrary vertices) |
|
|
254
|
+
| `bearing` | Float | Bearing in degrees (0 for arbitrary vertices) |
|
|
255
|
+
| `base` | Float/nil | Same as width; nil for arbitrary vertices |
|
|
256
|
+
| `side_lengths` | Array<Float> | Three side lengths in meters |
|
|
257
|
+
| `equilateral?` | Boolean | All sides equal (within 5m tolerance) |
|
|
258
|
+
| `isosceles?` | Boolean | Exactly two sides equal |
|
|
259
|
+
| `scalene?` | Boolean | No two sides equal |
|
|
260
|
+
| `to_bounding_box` | BoundingBox | Axis-aligned bounding box enclosing the triangle |
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
### Geodetic::Areas::Rectangle
|
|
265
|
+
|
|
266
|
+
A four-sided polygon defined by a centerline `Segment` and a perpendicular width. The centerline is the fundamental representation — center, height, and bearing are all derived from it.
|
|
267
|
+
|
|
268
|
+
#### Constructor
|
|
269
|
+
|
|
270
|
+
```ruby
|
|
271
|
+
a = Geodetic::Coordinate::LLA.new(lat: 40.7400, lng: -73.9900, alt: 0)
|
|
272
|
+
b = Geodetic::Coordinate::LLA.new(lat: 40.7500, lng: -73.9900, alt: 0)
|
|
273
|
+
|
|
274
|
+
# From a Segment object
|
|
275
|
+
seg = Geodetic::Segment.new(a, b)
|
|
276
|
+
rect = Geodetic::Areas::Rectangle.new(segment: seg, width: 200)
|
|
277
|
+
|
|
278
|
+
# From an array of two coordinates
|
|
279
|
+
rect = Geodetic::Areas::Rectangle.new(segment: [a, b], width: 200)
|
|
280
|
+
|
|
281
|
+
# Width accepts a Distance instance (converted to meters)
|
|
282
|
+
rect = Geodetic::Areas::Rectangle.new(segment: seg, width: seg.distance)
|
|
283
|
+
rect.square? #=> true
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
The `segment:` parameter defines the centerline of the rectangle. Height equals the segment length, bearing equals the segment direction, and center equals the segment midpoint. Width is the perpendicular extent, specified in meters or as a `Distance` instance.
|
|
287
|
+
|
|
288
|
+
#### Attributes and Methods
|
|
289
|
+
|
|
290
|
+
| Method | Returns | Description |
|
|
291
|
+
|--------|---------|-------------|
|
|
292
|
+
| `sides` | Integer | Always 4 |
|
|
293
|
+
| `centerline` | Segment | The centerline segment |
|
|
294
|
+
| `width` | Float | Perpendicular width in meters |
|
|
295
|
+
| `center` | LLA | Midpoint of the centerline |
|
|
296
|
+
| `height` | Float | Length of the centerline in meters |
|
|
297
|
+
| `bearing` | Float | Direction of the centerline in degrees |
|
|
298
|
+
| `corners` | Array<LLA> | Four corners: front-left, front-right, back-right, back-left |
|
|
299
|
+
| `square?` | Boolean | True when width equals height |
|
|
300
|
+
| `to_bounding_box` | BoundingBox | Axis-aligned bounding box enclosing the rectangle |
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
304
|
+
### Geodetic::Areas::Pentagon / Hexagon / Octagon
|
|
305
|
+
|
|
306
|
+
Regular polygons constructed from a center point and circumradius.
|
|
307
|
+
|
|
308
|
+
```ruby
|
|
309
|
+
center = Geodetic::Coordinate::LLA.new(lat: 40.7484, lng: -73.9857, alt: 0)
|
|
310
|
+
|
|
311
|
+
pent = Geodetic::Areas::Pentagon.new(center: center, radius: 500, bearing: 0)
|
|
312
|
+
hex = Geodetic::Areas::Hexagon.new(center: center, radius: 500, bearing: 0)
|
|
313
|
+
oct = Geodetic::Areas::Octagon.new(center: center, radius: 500, bearing: 0)
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
| Method | Returns | Description |
|
|
317
|
+
|--------|---------|-------------|
|
|
318
|
+
| `sides` | Integer | 5, 6, or 8 respectively |
|
|
319
|
+
| `center` | LLA | Center point |
|
|
320
|
+
| `radius` | Float | Circumradius in meters |
|
|
321
|
+
| `bearing` | Float | Rotation bearing in degrees |
|
data/docs/reference/feature.md
CHANGED
|
@@ -14,7 +14,7 @@ Feature.new(
|
|
|
14
14
|
)
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
The `geometry` parameter accepts any coordinate class, any area class (`Circle`, `Polygon`, `
|
|
17
|
+
The `geometry` parameter accepts any coordinate class, any area class (`Circle`, `Polygon`, `BoundingBox`), or a `Path`. The `metadata` hash is optional and defaults to `{}`.
|
|
18
18
|
|
|
19
19
|
---
|
|
20
20
|
|
|
@@ -35,7 +35,7 @@ All three attributes have both reader and writer methods.
|
|
|
35
35
|
A Feature's geometry can be any of:
|
|
36
36
|
|
|
37
37
|
- **Coordinate** — any of the 18 coordinate classes (`LLA`, `ECEF`, `UTM`, etc.)
|
|
38
|
-
- **Area** — `Areas::Circle`, `Areas::Polygon`, or `Areas::
|
|
38
|
+
- **Area** — `Areas::Circle`, `Areas::Polygon`, or `Areas::BoundingBox`
|
|
39
39
|
- **Path** — a `Geodetic::Path` representing a route or trail
|
|
40
40
|
|
|
41
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.
|
data/docs/reference/path.md
CHANGED
|
@@ -40,7 +40,7 @@ Raises `ArgumentError` if any coordinate appears more than once.
|
|
|
40
40
|
| `prev(coordinate)` | Waypoint before the given one, or `nil` at start |
|
|
41
41
|
| `size` | Integer | Number of waypoints |
|
|
42
42
|
| `empty?` | Boolean | True if the path has no waypoints |
|
|
43
|
-
| `segments` | Array |
|
|
43
|
+
| `segments` | Array | Array of `Segment` objects for each consecutive pair |
|
|
44
44
|
|
|
45
45
|
---
|
|
46
46
|
|
|
@@ -94,7 +94,7 @@ The `other` parameter for `distance_to`, `bearing_to`, and `closest_coordinate_t
|
|
|
94
94
|
}
|
|
95
95
|
```
|
|
96
96
|
|
|
97
|
-
Accepts `Areas::Circle`, `Areas::Polygon`, `Areas::
|
|
97
|
+
Accepts `Areas::Circle`, `Areas::Polygon`, `Areas::BoundingBox`, or another `Path`.
|
|
98
98
|
|
|
99
99
|
---
|
|
100
100
|
|
|
@@ -149,7 +149,7 @@ Returns the last coordinate if the distance exceeds the total path length.
|
|
|
149
149
|
|
|
150
150
|
### `bounds`
|
|
151
151
|
|
|
152
|
-
Returns an `Areas::
|
|
152
|
+
Returns an `Areas::BoundingBox` representing the axis-aligned bounding box of all waypoints.
|
|
153
153
|
|
|
154
154
|
```ruby
|
|
155
155
|
bbox = route.bounds
|