geodetic 0.3.1 → 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 +66 -0
- data/README.md +87 -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 +7 -3
- data/docs/reference/areas.md +140 -14
- data/docs/reference/feature.md +4 -3
- data/docs/reference/path.md +269 -0
- data/docs/reference/segment.md +181 -0
- data/examples/02_all_coordinate_systems.rb +6 -6
- data/examples/06_path_operations.rb +366 -0
- data/examples/07_segments_and_shapes.rb +258 -0
- data/examples/README.md +41 -0
- 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/feature.rb +10 -2
- data/lib/geodetic/path.rb +472 -0
- data/lib/geodetic/segment.rb +172 -0
- data/lib/geodetic/version.rb +1 -1
- data/lib/geodetic.rb +2 -0
- data/mkdocs.yml +2 -0
- metadata +13 -1
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
# Path Reference
|
|
2
|
+
|
|
3
|
+
`Geodetic::Path` represents a directed, ordered sequence of unique coordinates. It models routes, trails, boundaries, and any linear geographic feature where the order of waypoints matters.
|
|
4
|
+
|
|
5
|
+
A Path has a start (first coordinate) and an end (last coordinate). No duplicate coordinates are allowed — each waypoint appears exactly once, enabling unambiguous navigation with `next` and `prev`.
|
|
6
|
+
|
|
7
|
+
Path includes Ruby's `Enumerable` module, so all standard iteration methods (`map`, `select`, `any?`, `to_a`, etc.) are available.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Constructor
|
|
12
|
+
|
|
13
|
+
```ruby
|
|
14
|
+
# Empty path
|
|
15
|
+
path = Path.new
|
|
16
|
+
|
|
17
|
+
# From an array of coordinates
|
|
18
|
+
path = Path.new(coordinates: [a, b, c, d])
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Raises `ArgumentError` if any coordinate appears more than once.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Attributes
|
|
26
|
+
|
|
27
|
+
| Attribute | Type | Description |
|
|
28
|
+
|---------------|-------|-------------|
|
|
29
|
+
| `coordinates` | Array | The ordered list of waypoints (read-only) |
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Navigation
|
|
34
|
+
|
|
35
|
+
| Method | Returns | Description |
|
|
36
|
+
|---------------------|-------------|-------------|
|
|
37
|
+
| `first` | Coordinate | Starting waypoint |
|
|
38
|
+
| `last` | Coordinate | Ending waypoint |
|
|
39
|
+
| `next(coordinate)` | Coordinate | Waypoint after the given one, or `nil` at end |
|
|
40
|
+
| `prev(coordinate)` | Waypoint before the given one, or `nil` at start |
|
|
41
|
+
| `size` | Integer | Number of waypoints |
|
|
42
|
+
| `empty?` | Boolean | True if the path has no waypoints |
|
|
43
|
+
| `segments` | Array | Array of `Segment` objects for each consecutive pair |
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Membership
|
|
48
|
+
|
|
49
|
+
| Method | Description |
|
|
50
|
+
|--------|-------------|
|
|
51
|
+
| `include?(coord)` | True if the coordinate is a waypoint in the path |
|
|
52
|
+
| `includes?(coord)` | Alias for `include?` |
|
|
53
|
+
| `contains?(coord, tolerance: 10.0)` | True if the coordinate lies on any segment within tolerance (meters) |
|
|
54
|
+
| `inside?(coord, tolerance: 10.0)` | Alias for `contains?` |
|
|
55
|
+
| `excludes?(coord, tolerance: 10.0)` | Opposite of `contains?` |
|
|
56
|
+
| `exclude?(coord)` | Alias for `excludes?` |
|
|
57
|
+
| `outside?(coord)` | Alias for `excludes?` |
|
|
58
|
+
|
|
59
|
+
`includes?` checks waypoints only. `contains?` checks whether a coordinate lies on the line between any two consecutive waypoints, using a bearing comparison with a tolerance derived from the segment length.
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Equality
|
|
64
|
+
|
|
65
|
+
```ruby
|
|
66
|
+
path1 == path2
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Two paths are equal if they have the same coordinates in the same order.
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Spatial Methods
|
|
74
|
+
|
|
75
|
+
| Method | Returns | Description |
|
|
76
|
+
|--------|---------|-------------|
|
|
77
|
+
| `nearest_waypoint(target)` | Coordinate | The waypoint closest to the target |
|
|
78
|
+
| `closest_coordinate_to(target)` | Coordinate | The closest point on the path (projected onto segments) |
|
|
79
|
+
| `distance_to(other)` | Distance | Distance from the closest point on the path to the target |
|
|
80
|
+
| `bearing_to(other)` | Bearing | Bearing from the closest point on the path to the target |
|
|
81
|
+
| `closest_points_to(other)` | Hash | Closest pair between path and an Area or another Path |
|
|
82
|
+
|
|
83
|
+
The `other` parameter for `distance_to`, `bearing_to`, and `closest_coordinate_to` can be a coordinate, a Feature, an Area, or another Path.
|
|
84
|
+
|
|
85
|
+
### Closest Points
|
|
86
|
+
|
|
87
|
+
`closest_points_to` returns a hash with:
|
|
88
|
+
|
|
89
|
+
```ruby
|
|
90
|
+
{
|
|
91
|
+
path_point: Coordinate, # closest point on this path
|
|
92
|
+
area_point: Coordinate, # closest point on the other geometry
|
|
93
|
+
distance: Distance # distance between the two points
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Accepts `Areas::Circle`, `Areas::Polygon`, `Areas::BoundingBox`, or another `Path`.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Computed Properties
|
|
102
|
+
|
|
103
|
+
| Method | Returns | Description |
|
|
104
|
+
|--------|---------|-------------|
|
|
105
|
+
| `total_distance` | Distance | Sum of all segment distances |
|
|
106
|
+
| `segment_distances` | Array | Distance for each segment |
|
|
107
|
+
| `segment_bearings` | Array | Bearing for each segment |
|
|
108
|
+
| `reverse` | Path | New path with coordinates in reverse order |
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Subpath and Split
|
|
113
|
+
|
|
114
|
+
### `between(from, to)`
|
|
115
|
+
|
|
116
|
+
Extracts a subpath between two waypoints (inclusive). Both must exist in the path, and `from` must precede `to`.
|
|
117
|
+
|
|
118
|
+
```ruby
|
|
119
|
+
sub = route.between(wall_street, union_square)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### `split_at(coordinate)`
|
|
123
|
+
|
|
124
|
+
Splits the path at a waypoint, returning two paths that share the split point.
|
|
125
|
+
|
|
126
|
+
```ruby
|
|
127
|
+
left, right = route.split_at(city_hall)
|
|
128
|
+
# left ends with city_hall, right starts with city_hall
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Interpolation
|
|
134
|
+
|
|
135
|
+
### `at_distance(distance)`
|
|
136
|
+
|
|
137
|
+
Returns the coordinate at a given distance along the path from the start. Accepts a `Distance` object or a numeric value in meters.
|
|
138
|
+
|
|
139
|
+
```ruby
|
|
140
|
+
halfway = route.at_distance(route.total_distance.meters / 2.0)
|
|
141
|
+
quarter = route.at_distance(Distance.new(route.total_distance.meters * 0.25))
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Returns the last coordinate if the distance exceeds the total path length.
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Bounding Box
|
|
149
|
+
|
|
150
|
+
### `bounds`
|
|
151
|
+
|
|
152
|
+
Returns an `Areas::BoundingBox` representing the axis-aligned bounding box of all waypoints.
|
|
153
|
+
|
|
154
|
+
```ruby
|
|
155
|
+
bbox = route.bounds
|
|
156
|
+
bbox.nw # => northwest corner
|
|
157
|
+
bbox.se # => southeast corner
|
|
158
|
+
bbox.includes?(some_point) # => true/false
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## To Polygon
|
|
164
|
+
|
|
165
|
+
### `to_polygon`
|
|
166
|
+
|
|
167
|
+
Closes the path into an `Areas::Polygon` by connecting the last coordinate to the first. Requires at least 3 coordinates. Raises `ArgumentError` if the closing segment would intersect any interior segment of the path.
|
|
168
|
+
|
|
169
|
+
```ruby
|
|
170
|
+
triangle = Path.new(coordinates: [a, b, c])
|
|
171
|
+
poly = triangle.to_polygon
|
|
172
|
+
poly.includes?(some_point)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Intersection
|
|
178
|
+
|
|
179
|
+
### `intersects?(other_path)`
|
|
180
|
+
|
|
181
|
+
Returns true if any segment of this path crosses any segment of the other path. Uses orientation-based intersection testing.
|
|
182
|
+
|
|
183
|
+
```ruby
|
|
184
|
+
route.intersects?(crosstown) # => true/false
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Non-Mutating Operators
|
|
190
|
+
|
|
191
|
+
These return new Path objects; the original is unchanged.
|
|
192
|
+
|
|
193
|
+
| Operator | Accepts | Description |
|
|
194
|
+
|----------|---------|-------------|
|
|
195
|
+
| `+ coordinate` | Coordinate | New path with coordinate appended |
|
|
196
|
+
| `+ path` | Path | New path with all coordinates of the other path appended |
|
|
197
|
+
| `- coordinate` | Coordinate | New path with coordinate removed |
|
|
198
|
+
| `- path` | Path | New path with all of the other path's coordinates removed |
|
|
199
|
+
|
|
200
|
+
```ruby
|
|
201
|
+
combined = downtown_path + uptown_path
|
|
202
|
+
trimmed = full_route - detour_path
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Raises `ArgumentError` if `+` would create duplicates, or if `-` references coordinates not in the path.
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## Mutating Operators
|
|
210
|
+
|
|
211
|
+
These modify the path in place and return `self` for chaining.
|
|
212
|
+
|
|
213
|
+
| Method | Accepts | Description |
|
|
214
|
+
|--------|---------|-------------|
|
|
215
|
+
| `<< other` | Coordinate or Path | Append to end |
|
|
216
|
+
| `>> other` | Coordinate or Path | Prepend to start |
|
|
217
|
+
| `prepend(other)` | Coordinate or Path | Same as `>>` |
|
|
218
|
+
| `insert(coord, after: ref)` | Coordinate | Insert after a reference waypoint |
|
|
219
|
+
| `insert(coord, before: ref)` | Coordinate | Insert before a reference waypoint |
|
|
220
|
+
| `delete(coord)` | Coordinate | Remove a waypoint |
|
|
221
|
+
| `remove(coord)` | Coordinate | Alias for `delete` |
|
|
222
|
+
|
|
223
|
+
```ruby
|
|
224
|
+
path = Path.new
|
|
225
|
+
path << a << b << c # build incrementally
|
|
226
|
+
path >> start_point # prepend
|
|
227
|
+
path << other_path # append an entire path
|
|
228
|
+
path.insert(detour, after: b) # insert between waypoints
|
|
229
|
+
path.delete(b) # remove a waypoint
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## Enumerable
|
|
235
|
+
|
|
236
|
+
Path includes `Enumerable`. The `each` method iterates over coordinates in order.
|
|
237
|
+
|
|
238
|
+
```ruby
|
|
239
|
+
route.map { |c| c.lat }
|
|
240
|
+
route.select { |c| c.lat > 40.72 }
|
|
241
|
+
route.max_by { |c| c.lat }
|
|
242
|
+
route.to_a
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## Display
|
|
248
|
+
|
|
249
|
+
| Method | Returns |
|
|
250
|
+
|--------|---------|
|
|
251
|
+
| `to_s` | `"Path(7): 40.70... -> 40.71... -> ..."` |
|
|
252
|
+
| `inspect` | `"#<Geodetic::Path size=7 first=... last=...>"` |
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## Feature Integration
|
|
257
|
+
|
|
258
|
+
A Path can be used as the geometry of a `Feature`. When a Feature wraps a Path, `distance_to` and `bearing_to` use the Path's geometric projection to find the closest approach point.
|
|
259
|
+
|
|
260
|
+
```ruby
|
|
261
|
+
hiking_route = Feature.new(
|
|
262
|
+
label: "Manhattan Walking Tour",
|
|
263
|
+
geometry: route,
|
|
264
|
+
metadata: { type: "walking" }
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
hiking_route.distance_to(statue_of_liberty).to_km
|
|
268
|
+
hiking_route.bearing_to(statue_of_liberty).to_compass
|
|
269
|
+
```
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# Segment Reference
|
|
2
|
+
|
|
3
|
+
`Geodetic::Segment` represents a directed line segment between two points on the Earth's surface. It is the fundamental geometric primitive underlying `Path` segments, `Polygon` edges, and closest-approach calculations.
|
|
4
|
+
|
|
5
|
+
A Segment has a `start_point` and an `end_point`, both stored as `Coordinate::LLA`. Properties like `length`, `bearing`, and `midpoint` are computed lazily and cached.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Great Circle Arcs
|
|
10
|
+
|
|
11
|
+
On a sphere, any two points that are not antipodal (diametrically opposite) define a great circle, and that great circle produces two arcs between them: the **minor arc** (the shorter path) and the **major arc** (the longer way around the globe).
|
|
12
|
+
|
|
13
|
+
Segment always uses the **minor arc**. All operations — `length`, `bearing`, `interpolate`, `project`, `contains?` — follow the shortest path between the two endpoints via Vincenty geodesic calculations.
|
|
14
|
+
|
|
15
|
+
For **antipodal points** (exactly opposite sides of the Earth, roughly 20,000 km apart), the great circle is degenerate: there are infinitely many paths of equal length and the bearing is undefined.
|
|
16
|
+
|
|
17
|
+
In practice, real-world segments rarely approach even a quarter of the Earth's circumference (~10,000 km), so the minor arc assumption holds for virtually all use cases.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Constructor
|
|
22
|
+
|
|
23
|
+
```ruby
|
|
24
|
+
a = Geodetic::Coordinate::LLA.new(lat: 40.7484, lng: -73.9857, alt: 0)
|
|
25
|
+
b = Geodetic::Coordinate::LLA.new(lat: 40.7580, lng: -73.9855, alt: 0)
|
|
26
|
+
|
|
27
|
+
seg = Geodetic::Segment.new(a, b)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Accepts any coordinate type that responds to `to_lla`. Endpoints are converted to LLA on construction.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Attributes
|
|
35
|
+
|
|
36
|
+
| Attribute | Type | Description |
|
|
37
|
+
|---------------|------|-------------|
|
|
38
|
+
| `start_point` | LLA | The starting point of the segment |
|
|
39
|
+
| `end_point` | LLA | The ending point of the segment |
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Properties
|
|
44
|
+
|
|
45
|
+
All properties are lazily computed and cached after first access.
|
|
46
|
+
|
|
47
|
+
| Method | Returns | Description |
|
|
48
|
+
|----------------|----------|-------------|
|
|
49
|
+
| `length` | Distance | Great-circle distance between endpoints |
|
|
50
|
+
| `distance` | Distance | Alias for `length` |
|
|
51
|
+
| `length_meters`| Float | Length in meters (convenience accessor) |
|
|
52
|
+
| `bearing` | Bearing | Forward azimuth from start to end |
|
|
53
|
+
| `midpoint` | LLA | Point at the halfway mark |
|
|
54
|
+
|
|
55
|
+
```ruby
|
|
56
|
+
seg.length # => #<Geodetic::Distance 1067.45 m>
|
|
57
|
+
seg.distance # => #<Geodetic::Distance 1067.45 m> (alias)
|
|
58
|
+
seg.length_meters # => 1067.45
|
|
59
|
+
seg.bearing # => #<Geodetic::Bearing 1.0°>
|
|
60
|
+
seg.midpoint # => LLA at the midpoint
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Geometry
|
|
66
|
+
|
|
67
|
+
### `reverse`
|
|
68
|
+
|
|
69
|
+
Returns a new Segment with start and end points swapped.
|
|
70
|
+
|
|
71
|
+
```ruby
|
|
72
|
+
rev = seg.reverse
|
|
73
|
+
rev.start_point == seg.end_point # => true
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### `interpolate(fraction)`
|
|
77
|
+
|
|
78
|
+
Returns the LLA coordinate at a given fraction (0.0 to 1.0) along the segment.
|
|
79
|
+
|
|
80
|
+
```ruby
|
|
81
|
+
seg.interpolate(0.0) # => start_point
|
|
82
|
+
seg.interpolate(0.5) # => midpoint
|
|
83
|
+
seg.interpolate(1.0) # => end_point
|
|
84
|
+
seg.interpolate(0.25) # => quarter-way along
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Projection
|
|
90
|
+
|
|
91
|
+
### `project(point)`
|
|
92
|
+
|
|
93
|
+
Projects a point onto the segment, returning the closest point on the segment and the distance in meters.
|
|
94
|
+
|
|
95
|
+
```ruby
|
|
96
|
+
foot, distance_m = seg.project(target_point)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
- If the perpendicular foot falls within the segment, returns the foot and the perpendicular distance.
|
|
100
|
+
- If the foot falls before the start, returns `start_point`.
|
|
101
|
+
- If the foot falls past the end, returns `end_point`.
|
|
102
|
+
- Handles zero-length segments and target-at-endpoint edge cases.
|
|
103
|
+
|
|
104
|
+
This is the core geometric operation used by `Path#closest_coordinate_to`, `Path#closest_points_to`, and `Path#at_distance`.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Membership
|
|
109
|
+
|
|
110
|
+
| Method | Description |
|
|
111
|
+
|--------|-------------|
|
|
112
|
+
| `includes?(point)` | True if the point is a vertex (start or end point) |
|
|
113
|
+
| `contains?(point, tolerance: 10.0)` | True if the point lies on the segment within tolerance (meters) |
|
|
114
|
+
| `excludes?(point, tolerance: 10.0)` | Opposite of `contains?` |
|
|
115
|
+
|
|
116
|
+
`includes?` checks vertices only. `contains?` checks whether a point lies anywhere along the line between the two endpoints using a bearing comparison with a tolerance derived from the segment length.
|
|
117
|
+
|
|
118
|
+
```ruby
|
|
119
|
+
seg.includes?(seg.start_point) # => true
|
|
120
|
+
seg.includes?(seg.midpoint) # => false
|
|
121
|
+
|
|
122
|
+
seg.contains?(seg.midpoint) # => true
|
|
123
|
+
seg.contains?(far_away_point) # => false
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Intersection
|
|
129
|
+
|
|
130
|
+
### `intersects?(other_segment)`
|
|
131
|
+
|
|
132
|
+
Tests whether two segments cross each other using cross-product orientation tests on a flat lat/lng approximation. Handles both proper intersections and collinear overlap.
|
|
133
|
+
|
|
134
|
+
```ruby
|
|
135
|
+
seg1 = Geodetic::Segment.new(a, b)
|
|
136
|
+
seg2 = Geodetic::Segment.new(c, d)
|
|
137
|
+
|
|
138
|
+
seg1.intersects?(seg2) # => true/false
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Used internally by `Path#intersects?` and `Path#to_polygon` for self-intersection validation.
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Conversion
|
|
146
|
+
|
|
147
|
+
| Method | Returns | Description |
|
|
148
|
+
|----------|---------|-------------|
|
|
149
|
+
| `to_path`| Path | A two-point Path from start to end |
|
|
150
|
+
| `to_a` | Array | `[start_point, end_point]` |
|
|
151
|
+
|
|
152
|
+
```ruby
|
|
153
|
+
seg.to_path # => #<Geodetic::Path size=2 ...>
|
|
154
|
+
seg.to_a # => [start_point, end_point]
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Equality and Display
|
|
160
|
+
|
|
161
|
+
```ruby
|
|
162
|
+
seg1 == seg2 # true if same start and end points
|
|
163
|
+
|
|
164
|
+
seg.to_s # => "Segment(40.748400, ... -> 40.758000, ...)"
|
|
165
|
+
seg.inspect # => "#<Geodetic::Segment start=... end=... length=1067.45 m>"
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Two segments are equal if they have the same start and end points. Direction matters: `Segment.new(a, b) != Segment.new(b, a)`.
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## Relationship to Path and Polygon
|
|
173
|
+
|
|
174
|
+
`Path#segments` returns an array of Segment objects:
|
|
175
|
+
|
|
176
|
+
```ruby
|
|
177
|
+
route = Path.new(coordinates: [a, b, c, d])
|
|
178
|
+
route.segments # => [Segment(a→b), Segment(b→c), Segment(c→d)]
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Polygon edges are implicit segments formed by consecutive boundary points. Segment's `project`, `intersects?`, and `contains?` methods power the geometric operations in both Path and Polygon.
|
|
@@ -253,13 +253,13 @@ def demo_coordinate_systems
|
|
|
253
253
|
puts " Lat/Lng error is from MGRS 1-meter grid precision truncation."
|
|
254
254
|
puts
|
|
255
255
|
|
|
256
|
-
# ==========
|
|
257
|
-
puts "
|
|
256
|
+
# ========== BoundingBox Area ==========
|
|
257
|
+
puts "BOUNDING BOX AREA"
|
|
258
258
|
puts "-" * 50
|
|
259
259
|
|
|
260
260
|
nw = LLA.new(lat: 47.65, lng: -122.40)
|
|
261
261
|
se = LLA.new(lat: 47.60, lng: -122.30)
|
|
262
|
-
rect = Areas::
|
|
262
|
+
rect = Areas::BoundingBox.new(nw: nw, se: se)
|
|
263
263
|
puts " NW: (#{rect.nw.lat}, #{rect.nw.lng})"
|
|
264
264
|
puts " SE: (#{rect.se.lat}, #{rect.se.lng})"
|
|
265
265
|
puts " NE: (#{rect.ne.lat}, #{rect.ne.lng})"
|
|
@@ -268,10 +268,10 @@ def demo_coordinate_systems
|
|
|
268
268
|
puts " Space Needle inside? #{rect.includes?(lla_coord)}"
|
|
269
269
|
puts " London inside? #{rect.includes?(london_lla)}"
|
|
270
270
|
|
|
271
|
-
#
|
|
271
|
+
# BoundingBox from non-LLA coordinates
|
|
272
272
|
nw_wm = WebMerc.from_lla(nw)
|
|
273
273
|
se_wm = WebMerc.from_lla(se)
|
|
274
|
-
rect_wm = Areas::
|
|
274
|
+
rect_wm = Areas::BoundingBox.new(nw: nw_wm, se: se_wm)
|
|
275
275
|
puts " From WebMercator: NW=(#{rect_wm.nw.lat.round(4)}, #{rect_wm.nw.lng.round(4)})"
|
|
276
276
|
puts
|
|
277
277
|
|
|
@@ -292,7 +292,7 @@ def demo_coordinate_systems
|
|
|
292
292
|
puts "Geohash-36 (GH36)"
|
|
293
293
|
puts "Geoid Height Support"
|
|
294
294
|
puts
|
|
295
|
-
puts "Areas: Circle, Polygon,
|
|
295
|
+
puts "Areas: Circle, Polygon, BoundingBox"
|
|
296
296
|
puts
|
|
297
297
|
puts "All coordinate systems support complete bidirectional conversions!"
|
|
298
298
|
puts "Total coordinate systems implemented: 13"
|