geodetic 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.envrc +1 -0
- data/.github/workflows/deploy-github-pages.yml +52 -0
- data/CHANGELOG.md +15 -0
- data/COMMITS.md +196 -0
- data/LICENSE.txt +21 -0
- data/README.md +471 -0
- data/Rakefile +8 -0
- data/docs/coordinate-systems/bng.md +60 -0
- data/docs/coordinate-systems/ecef.md +215 -0
- data/docs/coordinate-systems/enu.md +77 -0
- data/docs/coordinate-systems/gh36.md +192 -0
- data/docs/coordinate-systems/index.md +93 -0
- data/docs/coordinate-systems/lla.md +304 -0
- data/docs/coordinate-systems/mgrs.md +81 -0
- data/docs/coordinate-systems/ned.md +83 -0
- data/docs/coordinate-systems/state-plane.md +60 -0
- data/docs/coordinate-systems/ups.md +53 -0
- data/docs/coordinate-systems/usng.md +74 -0
- data/docs/coordinate-systems/utm.md +257 -0
- data/docs/coordinate-systems/web-mercator.md +67 -0
- data/docs/getting-started/installation.md +65 -0
- data/docs/getting-started/quick-start.md +175 -0
- data/docs/index.md +58 -0
- data/docs/reference/areas.md +195 -0
- data/docs/reference/conversions.md +351 -0
- data/docs/reference/datums.md +134 -0
- data/docs/reference/geoid-height.md +182 -0
- data/docs/reference/serialization.md +252 -0
- data/examples/01_basic_conversions.rb +187 -0
- data/examples/02_all_coordinate_systems.rb +310 -0
- data/examples/03_distance_calculations.rb +224 -0
- data/examples/04_bearing_calculations.rb +236 -0
- data/lib/geodetic/areas/circle.rb +29 -0
- data/lib/geodetic/areas/polygon.rb +57 -0
- data/lib/geodetic/areas/rectangle.rb +55 -0
- data/lib/geodetic/areas.rb +5 -0
- data/lib/geodetic/bearing.rb +94 -0
- data/lib/geodetic/coordinates/bng.rb +366 -0
- data/lib/geodetic/coordinates/ecef.rb +229 -0
- data/lib/geodetic/coordinates/enu.rb +244 -0
- data/lib/geodetic/coordinates/gh36.rb +384 -0
- data/lib/geodetic/coordinates/lla.rb +268 -0
- data/lib/geodetic/coordinates/mgrs.rb +317 -0
- data/lib/geodetic/coordinates/ned.rb +246 -0
- data/lib/geodetic/coordinates/state_plane.rb +451 -0
- data/lib/geodetic/coordinates/ups.rb +325 -0
- data/lib/geodetic/coordinates/usng.rb +274 -0
- data/lib/geodetic/coordinates/utm.rb +261 -0
- data/lib/geodetic/coordinates/web_mercator.rb +242 -0
- data/lib/geodetic/coordinates.rb +260 -0
- data/lib/geodetic/datum.rb +62 -0
- data/lib/geodetic/distance.rb +146 -0
- data/lib/geodetic/geoid_height.rb +299 -0
- data/lib/geodetic/version.rb +5 -0
- data/lib/geodetic.rb +13 -0
- data/mkdocs.yml +140 -0
- data/sig/geodetic.rbs +4 -0
- metadata +104 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# Areas Reference
|
|
2
|
+
|
|
3
|
+
The `Geodetic::Areas` module provides three geometric area classes for point-in-area testing: `Circle`, `Polygon`, and `Rectangle`. All operate on `Geodetic::Coordinates::LLA` points.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Geodetic::Areas::Circle
|
|
8
|
+
|
|
9
|
+
Defines a circular area on the Earth's surface.
|
|
10
|
+
|
|
11
|
+
### Constructor
|
|
12
|
+
|
|
13
|
+
```ruby
|
|
14
|
+
center = Geodetic::Coordinates::LLA.new(lat: 38.8977, lng: -77.0365, alt: 0.0)
|
|
15
|
+
|
|
16
|
+
circle = Geodetic::Areas::Circle.new(
|
|
17
|
+
centroid: center, # LLA point at the center
|
|
18
|
+
radius: 1000.0 # radius in meters
|
|
19
|
+
)
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Attributes
|
|
23
|
+
|
|
24
|
+
| Attribute | Type | Description |
|
|
25
|
+
|-----------|------|-------------|
|
|
26
|
+
| `centroid` | LLA | The center point of the circle |
|
|
27
|
+
| `radius` | Float | The radius in meters |
|
|
28
|
+
|
|
29
|
+
### Methods
|
|
30
|
+
|
|
31
|
+
#### `includes?(a_point)` / `include?(a_point)` / `inside?(a_point)`
|
|
32
|
+
|
|
33
|
+
Returns `true` if the given LLA point falls within (or on the boundary of) the circle. The distance from centroid to the point is compared against the radius.
|
|
34
|
+
|
|
35
|
+
```ruby
|
|
36
|
+
point = Geodetic::Coordinates::LLA.new(lat: 38.898, lng: -77.036, alt: 0.0)
|
|
37
|
+
circle.includes?(point) # => true or false
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
#### `excludes?(a_point)` / `exclude?(a_point)` / `outside?(a_point)`
|
|
41
|
+
|
|
42
|
+
Returns `true` if the given LLA point falls outside the circle. The logical inverse of `includes?`.
|
|
43
|
+
|
|
44
|
+
```ruby
|
|
45
|
+
circle.excludes?(point) # => true or false
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Alias Summary
|
|
49
|
+
|
|
50
|
+
| Primary Method | Aliases |
|
|
51
|
+
|---------------|---------|
|
|
52
|
+
| `includes?` | `include?`, `inside?` |
|
|
53
|
+
| `excludes?` | `exclude?`, `outside?` |
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Geodetic::Areas::Polygon
|
|
58
|
+
|
|
59
|
+
Defines an arbitrary polygon area on the Earth's surface.
|
|
60
|
+
|
|
61
|
+
### Constructor
|
|
62
|
+
|
|
63
|
+
```ruby
|
|
64
|
+
boundary = [
|
|
65
|
+
Geodetic::Coordinates::LLA.new(lat: 38.90, lng: -77.04, alt: 0.0),
|
|
66
|
+
Geodetic::Coordinates::LLA.new(lat: 38.90, lng: -77.03, alt: 0.0),
|
|
67
|
+
Geodetic::Coordinates::LLA.new(lat: 38.89, lng: -77.03, alt: 0.0),
|
|
68
|
+
Geodetic::Coordinates::LLA.new(lat: 38.89, lng: -77.04, alt: 0.0)
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
polygon = Geodetic::Areas::Polygon.new(boundary: boundary)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
A minimum of 3 points is required. The constructor throws an error if fewer points are provided.
|
|
75
|
+
|
|
76
|
+
The polygon is automatically closed: if the first and last points in the boundary array are not equal, the first point is appended to close the polygon.
|
|
77
|
+
|
|
78
|
+
### Attributes
|
|
79
|
+
|
|
80
|
+
| Attribute | Type | Description |
|
|
81
|
+
|-----------|------|-------------|
|
|
82
|
+
| `boundary` | Array<LLA> | The ordered array of LLA points forming the polygon boundary (auto-closed) |
|
|
83
|
+
| `centroid` | LLA | The computed centroid of the polygon, calculated automatically during initialization |
|
|
84
|
+
|
|
85
|
+
### Centroid Calculation
|
|
86
|
+
|
|
87
|
+
The centroid is computed using the standard polygon centroid formula based on the signed area of the polygon in the latitude/longitude coordinate space. This is calculated automatically during initialization and stored in the `centroid` attribute.
|
|
88
|
+
|
|
89
|
+
### Methods
|
|
90
|
+
|
|
91
|
+
#### `includes?(a_point)` / `include?(a_point)` / `inside?(a_point)`
|
|
92
|
+
|
|
93
|
+
Returns `true` if the given LLA point falls within the polygon. Uses the winding angle algorithm: sums the turning angles from the test point to each consecutive pair of boundary vertices. If the absolute accumulated angle exceeds 180 degrees, the point is inside.
|
|
94
|
+
|
|
95
|
+
Also returns `true` if the point is exactly equal to any boundary vertex.
|
|
96
|
+
|
|
97
|
+
```ruby
|
|
98
|
+
point = Geodetic::Coordinates::LLA.new(lat: 38.895, lng: -77.035, alt: 0.0)
|
|
99
|
+
polygon.includes?(point) # => true or false
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
#### `excludes?(a_point)` / `exclude?(a_point)` / `outside?(a_point)`
|
|
103
|
+
|
|
104
|
+
Returns `true` if the given LLA point falls outside the polygon. The logical inverse of `includes?`.
|
|
105
|
+
|
|
106
|
+
```ruby
|
|
107
|
+
polygon.excludes?(point) # => true or false
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Alias Summary
|
|
111
|
+
|
|
112
|
+
| Primary Method | Aliases |
|
|
113
|
+
|---------------|---------|
|
|
114
|
+
| `includes?` | `include?`, `inside?` |
|
|
115
|
+
| `excludes?` | `exclude?`, `outside?` |
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Geodetic::Areas::Rectangle
|
|
120
|
+
|
|
121
|
+
Defines an axis-aligned rectangle by its northwest and southeast corners.
|
|
122
|
+
|
|
123
|
+
### Constructor
|
|
124
|
+
|
|
125
|
+
```ruby
|
|
126
|
+
nw = Geodetic::Coordinates::LLA.new(lat: 41.0, lng: -75.0)
|
|
127
|
+
se = Geodetic::Coordinates::LLA.new(lat: 40.0, lng: -74.0)
|
|
128
|
+
|
|
129
|
+
rectangle = Geodetic::Areas::Rectangle.new(nw: nw, se: se)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
The constructor accepts any coordinate type that responds to `to_lla` -- coordinates are automatically converted to LLA.
|
|
133
|
+
|
|
134
|
+
```ruby
|
|
135
|
+
nw_wm = Geodetic::Coordinates::WebMercator.from_lla(nw)
|
|
136
|
+
se_wm = Geodetic::Coordinates::WebMercator.from_lla(se)
|
|
137
|
+
rectangle = Geodetic::Areas::Rectangle.new(nw: nw_wm, se: se_wm)
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
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.
|
|
141
|
+
|
|
142
|
+
### Attributes
|
|
143
|
+
|
|
144
|
+
| Attribute | Type | Description |
|
|
145
|
+
|------------|------|-------------|
|
|
146
|
+
| `nw` | LLA | The northwest corner (max latitude, min longitude) |
|
|
147
|
+
| `se` | LLA | The southeast corner (min latitude, max longitude) |
|
|
148
|
+
| `centroid` | LLA | The center point, computed automatically |
|
|
149
|
+
|
|
150
|
+
All attributes are read-only.
|
|
151
|
+
|
|
152
|
+
### Computed Corners
|
|
153
|
+
|
|
154
|
+
```ruby
|
|
155
|
+
rectangle.ne # => LLA (nw.lat, se.lng)
|
|
156
|
+
rectangle.sw # => LLA (se.lat, nw.lng)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Methods
|
|
160
|
+
|
|
161
|
+
#### `includes?(a_point)` / `include?(a_point)` / `inside?(a_point)`
|
|
162
|
+
|
|
163
|
+
Returns `true` if the given point falls within (or on the boundary of) the rectangle. Accepts any coordinate type that responds to `to_lla`.
|
|
164
|
+
|
|
165
|
+
```ruby
|
|
166
|
+
point = Geodetic::Coordinates::LLA.new(lat: 40.5, lng: -74.5)
|
|
167
|
+
rectangle.includes?(point) # => true
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
#### `excludes?(a_point)` / `exclude?(a_point)` / `outside?(a_point)`
|
|
171
|
+
|
|
172
|
+
Returns `true` if the given point falls outside the rectangle.
|
|
173
|
+
|
|
174
|
+
```ruby
|
|
175
|
+
rectangle.excludes?(point) # => true or false
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Alias Summary
|
|
179
|
+
|
|
180
|
+
| Primary Method | Aliases |
|
|
181
|
+
|---------------|---------|
|
|
182
|
+
| `includes?` | `include?`, `inside?` |
|
|
183
|
+
| `excludes?` | `exclude?`, `outside?` |
|
|
184
|
+
|
|
185
|
+
### Integration with GH36
|
|
186
|
+
|
|
187
|
+
`Geodetic::Coordinates::GH36#to_area` returns a `Rectangle` representing the geohash cell's bounding box:
|
|
188
|
+
|
|
189
|
+
```ruby
|
|
190
|
+
gh36 = Geodetic::Coordinates::GH36.new("bdrdC26BqH")
|
|
191
|
+
area = gh36.to_area
|
|
192
|
+
# => Geodetic::Areas::Rectangle
|
|
193
|
+
|
|
194
|
+
area.includes?(gh36.to_lla) # => true (midpoint is inside the cell)
|
|
195
|
+
```
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
# Conversions Reference
|
|
2
|
+
|
|
3
|
+
Every coordinate class in the Geodetic gem can convert to every other coordinate class. Conversions are available as both instance methods (on the source object) and class methods (on the target class).
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Conversion Method Patterns
|
|
8
|
+
|
|
9
|
+
### Instance Methods (on the source)
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
source.to_<target>(datum = WGS84)
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Examples:
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
lla.to_ecef # LLA -> ECEF
|
|
19
|
+
lla.to_utm # LLA -> UTM
|
|
20
|
+
ecef.to_lla # ECEF -> LLA
|
|
21
|
+
utm.to_lla # UTM -> LLA
|
|
22
|
+
web_mercator.to_lla # WebMercator -> LLA
|
|
23
|
+
bng.to_utm # BNG -> UTM
|
|
24
|
+
ups.to_mgrs # UPS -> MGRS
|
|
25
|
+
state_plane.to_web_mercator # StatePlane -> WebMercator
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Class Methods (on the target)
|
|
29
|
+
|
|
30
|
+
```ruby
|
|
31
|
+
TargetClass.from_<source>(source_object, datum = WGS84)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Examples:
|
|
35
|
+
|
|
36
|
+
```ruby
|
|
37
|
+
Geodetic::Coordinates::ECEF.from_lla(lla)
|
|
38
|
+
Geodetic::Coordinates::LLA.from_ecef(ecef)
|
|
39
|
+
Geodetic::Coordinates::UTM.from_lla(lla)
|
|
40
|
+
Geodetic::Coordinates::LLA.from_utm(utm)
|
|
41
|
+
Geodetic::Coordinates::WebMercator.from_ecef(ecef)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Local Coordinate Systems (ENU, NED)
|
|
47
|
+
|
|
48
|
+
ENU and NED are local tangent plane systems that require a **reference LLA** point defining the origin of the local frame. All conversions to/from ENU and NED include a reference parameter.
|
|
49
|
+
|
|
50
|
+
### Instance Methods
|
|
51
|
+
|
|
52
|
+
```ruby
|
|
53
|
+
ref = Geodetic::Coordinates::LLA.new(lat: 38.8977, lng: -77.0365, alt: 0.0)
|
|
54
|
+
|
|
55
|
+
# LLA to local
|
|
56
|
+
lla.to_enu(ref)
|
|
57
|
+
lla.to_ned(ref)
|
|
58
|
+
|
|
59
|
+
# Local to LLA
|
|
60
|
+
enu.to_lla(ref)
|
|
61
|
+
ned.to_lla(ref)
|
|
62
|
+
|
|
63
|
+
# Local to UTM (requires reference)
|
|
64
|
+
enu.to_utm(ref)
|
|
65
|
+
ned.to_utm(ref)
|
|
66
|
+
|
|
67
|
+
# ENU <-> NED (direct, no reference needed)
|
|
68
|
+
enu.to_ned
|
|
69
|
+
ned.to_enu
|
|
70
|
+
|
|
71
|
+
# ECEF to local (reference_ecef required, reference_lla optional)
|
|
72
|
+
ecef.to_enu(reference_ecef, reference_lla)
|
|
73
|
+
ecef.to_ned(reference_ecef, reference_lla)
|
|
74
|
+
|
|
75
|
+
# Local to ECEF (reference_ecef required, reference_lla optional)
|
|
76
|
+
enu.to_ecef(reference_ecef, reference_lla)
|
|
77
|
+
ned.to_ecef(reference_ecef, reference_lla)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Class Methods
|
|
81
|
+
|
|
82
|
+
```ruby
|
|
83
|
+
ref = Geodetic::Coordinates::LLA.new(lat: 38.8977, lng: -77.0365, alt: 0.0)
|
|
84
|
+
|
|
85
|
+
Geodetic::Coordinates::ENU.from_lla(lla, ref)
|
|
86
|
+
Geodetic::Coordinates::NED.from_lla(lla, ref)
|
|
87
|
+
Geodetic::Coordinates::LLA.from_enu(enu, ref)
|
|
88
|
+
Geodetic::Coordinates::LLA.from_ned(ned, ref)
|
|
89
|
+
|
|
90
|
+
# From other systems via reference
|
|
91
|
+
Geodetic::Coordinates::ENU.from_utm(utm, ref)
|
|
92
|
+
Geodetic::Coordinates::NED.from_utm(utm, ref)
|
|
93
|
+
Geodetic::Coordinates::UTM.from_enu(enu, ref)
|
|
94
|
+
Geodetic::Coordinates::UTM.from_ned(ned, ref)
|
|
95
|
+
|
|
96
|
+
# ECEF-based
|
|
97
|
+
Geodetic::Coordinates::ENU.from_ecef(ecef, ref_ecef, ref_lla)
|
|
98
|
+
Geodetic::Coordinates::NED.from_ecef(ecef, ref_ecef, ref_lla)
|
|
99
|
+
Geodetic::Coordinates::ECEF.from_enu(enu, ref_ecef, ref_lla)
|
|
100
|
+
Geodetic::Coordinates::ECEF.from_ned(ned, ref_ecef, ref_lla)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
When `reference_lla` is omitted in ECEF-based conversions, it is computed automatically from `reference_ecef` via `reference_ecef.to_lla`.
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## MGRS and USNG Conversions
|
|
108
|
+
|
|
109
|
+
MGRS and USNG accept an optional `precision` parameter (1-5, default 5) controlling the coordinate resolution:
|
|
110
|
+
|
|
111
|
+
| Precision | Resolution |
|
|
112
|
+
|-----------|-----------|
|
|
113
|
+
| 1 | 10 km |
|
|
114
|
+
| 2 | 1 km |
|
|
115
|
+
| 3 | 100 m |
|
|
116
|
+
| 4 | 10 m |
|
|
117
|
+
| 5 | 1 m |
|
|
118
|
+
|
|
119
|
+
```ruby
|
|
120
|
+
# LLA to MGRS with precision
|
|
121
|
+
Geodetic::Coordinates::MGRS.from_lla(lla, datum, precision)
|
|
122
|
+
lla_point.to_mgrs(datum, precision) # available on UPS, WebMercator, BNG, StatePlane
|
|
123
|
+
|
|
124
|
+
# LLA to USNG with precision
|
|
125
|
+
Geodetic::Coordinates::USNG.from_lla(lla, datum, precision)
|
|
126
|
+
|
|
127
|
+
# MGRS <-> USNG (direct conversion)
|
|
128
|
+
mgrs.to_usng # implicit via component transfer
|
|
129
|
+
Geodetic::Coordinates::USNG.from_mgrs(mgrs)
|
|
130
|
+
usng.to_mgrs
|
|
131
|
+
Geodetic::Coordinates::MGRS.from_usng(usng) # implicit via string
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## StatePlane Conversions
|
|
137
|
+
|
|
138
|
+
StatePlane requires a **zone code** when converting into the system:
|
|
139
|
+
|
|
140
|
+
```ruby
|
|
141
|
+
# To StatePlane (zone_code required)
|
|
142
|
+
Geodetic::Coordinates::StatePlane.from_lla(lla, 'CA_I')
|
|
143
|
+
Geodetic::Coordinates::StatePlane.from_ecef(ecef, 'CA_I')
|
|
144
|
+
Geodetic::Coordinates::StatePlane.from_utm(utm, 'CA_I')
|
|
145
|
+
Geodetic::Coordinates::StatePlane.from_enu(enu, ref_lla, 'CA_I')
|
|
146
|
+
Geodetic::Coordinates::StatePlane.from_ned(ned, ref_lla, 'CA_I')
|
|
147
|
+
|
|
148
|
+
# From StatePlane (zone is stored in the object)
|
|
149
|
+
state_plane.to_lla
|
|
150
|
+
state_plane.to_ecef
|
|
151
|
+
state_plane.to_utm
|
|
152
|
+
state_plane.to_enu(ref_lla)
|
|
153
|
+
state_plane.to_ned(ref_lla)
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
BNG also provides conversion to StatePlane with a zone code:
|
|
157
|
+
|
|
158
|
+
```ruby
|
|
159
|
+
bng.to_state_plane('CA_I')
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Datum Parameter
|
|
165
|
+
|
|
166
|
+
Most conversion methods accept an optional datum parameter that defaults to `Geodetic::WGS84`:
|
|
167
|
+
|
|
168
|
+
```ruby
|
|
169
|
+
# Using a different datum
|
|
170
|
+
clarke = Geodetic::Datum.new(name: 'CLARKE_1866')
|
|
171
|
+
|
|
172
|
+
lla.to_ecef(clarke)
|
|
173
|
+
ecef.to_lla(clarke)
|
|
174
|
+
lla.to_utm(clarke)
|
|
175
|
+
utm.to_lla(clarke)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
StatePlane stores its datum internally and uses it when no datum argument is provided:
|
|
179
|
+
|
|
180
|
+
```ruby
|
|
181
|
+
sp = Geodetic::Coordinates::StatePlane.new(
|
|
182
|
+
easting: 2000000.0, northing: 500000.0,
|
|
183
|
+
zone_code: 'CA_I', datum: clarke
|
|
184
|
+
)
|
|
185
|
+
sp.to_lla # uses the stored clarke datum
|
|
186
|
+
sp.to_lla(wgs84) # overrides with wgs84
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Conversion Chains
|
|
192
|
+
|
|
193
|
+
Most conversions are not direct but route through intermediate systems. The gem handles this transparently. Here are the typical chains:
|
|
194
|
+
|
|
195
|
+
| Conversion | Chain |
|
|
196
|
+
|-----------|-------|
|
|
197
|
+
| LLA <-> ECEF | Direct mathematical transformation |
|
|
198
|
+
| LLA <-> UTM | Direct mathematical transformation |
|
|
199
|
+
| LLA <-> ENU | LLA -> ECEF -> ENU (and reverse) |
|
|
200
|
+
| LLA <-> NED | LLA -> ECEF -> ENU -> NED (and reverse) |
|
|
201
|
+
| ENU <-> NED | Direct axis swap: `NED(n, e, d) = ENU(e, n, -u)` |
|
|
202
|
+
| UTM <-> ECEF | UTM -> LLA -> ECEF (and reverse) |
|
|
203
|
+
| UTM <-> ENU | UTM -> LLA -> ENU (and reverse) |
|
|
204
|
+
| MGRS <-> LLA | MGRS -> UTM -> LLA (and reverse) |
|
|
205
|
+
| USNG <-> LLA | USNG -> MGRS -> UTM -> LLA (and reverse) |
|
|
206
|
+
| WebMercator <-> LLA | Direct mathematical transformation |
|
|
207
|
+
| UPS <-> LLA | Direct mathematical transformation |
|
|
208
|
+
| BNG <-> LLA | BNG -> OSGB36 LLA -> WGS84 LLA (with datum shift) |
|
|
209
|
+
| StatePlane <-> LLA | Direct projection (Lambert or Transverse Mercator) |
|
|
210
|
+
| GH36 <-> LLA | Encode/decode via 6x6 matrix subdivision |
|
|
211
|
+
| Any <-> Any | Routes through LLA as the universal hub |
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## Conversion Accuracy Notes
|
|
216
|
+
|
|
217
|
+
- **LLA <-> ECEF**: Full precision. Iterative algorithm converges to sub-millimeter accuracy (tolerance: 1e-12 radians for latitude, 1e-12 meters for altitude, max 100 iterations).
|
|
218
|
+
- **LLA <-> UTM**: Simplified series expansion. Accurate for typical use but may diverge at extreme latitudes or far from the central meridian.
|
|
219
|
+
- **ENU / NED**: Full precision when going through ECEF. The rotation matrices are exact.
|
|
220
|
+
- **MGRS / USNG**: Precision depends on the grid precision level (1-5). A 5-digit precision gives 1-meter resolution.
|
|
221
|
+
- **WebMercator**: Latitude is clamped to +/-85.0511 degrees. Altitude information is lost (always 0.0).
|
|
222
|
+
- **UPS**: Iterative refinement (5 iterations) for the inverse projection. Designed for polar regions.
|
|
223
|
+
- **BNG**: Uses a simplified datum transformation between OSGB36 and WGS84 (approximate offset). A full Helmert 7-parameter transformation would provide higher accuracy.
|
|
224
|
+
- **StatePlane**: Uses simplified projection formulas. Production applications may require the full NOAA/NGS projection equations for survey-grade accuracy.
|
|
225
|
+
- **GH36**: Precision depends on hash length. Default 10 characters gives sub-meter resolution. Altitude information is lost (always 0.0).
|
|
226
|
+
- **Equality comparisons**: All classes use tolerance-based equality. Coordinates (in meters) use 1e-6 m tolerance. LLA uses 1e-10 degrees for lat/lng and 1e-6 m for altitude. GH36 uses exact string comparison.
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## Distance Calculations
|
|
231
|
+
|
|
232
|
+
Universal distance methods are available on all coordinate types and work across different coordinate systems.
|
|
233
|
+
|
|
234
|
+
### Great-Circle Distance (Vincenty)
|
|
235
|
+
|
|
236
|
+
- **`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).
|
|
237
|
+
- **`GCS.distance_between(*coords)`** — Class method on `Geodetic::Coordinates` (aliased as `GCS`). 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.
|
|
238
|
+
|
|
239
|
+
> **`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.
|
|
240
|
+
|
|
241
|
+
```ruby
|
|
242
|
+
seattle = GCS::LLA.new(lat: 47.6205, lng: -122.3493, alt: 0.0)
|
|
243
|
+
portland = GCS::LLA.new(lat: 45.5152, lng: -122.6784, alt: 0.0)
|
|
244
|
+
sf = GCS::LLA.new(lat: 37.7749, lng: -122.4194, alt: 0.0)
|
|
245
|
+
|
|
246
|
+
# Radial distances from receiver
|
|
247
|
+
seattle.distance_to(portland) # => Distance (235393.17 m)
|
|
248
|
+
seattle.distance_to(portland, sf) # => [Distance, Distance] (Array)
|
|
249
|
+
|
|
250
|
+
# Consecutive chain distances
|
|
251
|
+
GCS.distance_between(seattle, portland, sf) # => [Distance, Distance] (Array)
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Straight-Line Distance (ECEF Euclidean)
|
|
255
|
+
|
|
256
|
+
- **`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.
|
|
257
|
+
- **`GCS.straight_line_distance_between(*coords)`** — Class method. Computes consecutive chain Euclidean distances.
|
|
258
|
+
|
|
259
|
+
```ruby
|
|
260
|
+
seattle.straight_line_distance_to(portland) # => Distance
|
|
261
|
+
GCS.straight_line_distance_between(seattle, portland) # => Distance
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### Cross-System Distances
|
|
265
|
+
|
|
266
|
+
Both `distance_to` and `straight_line_distance_to` accept any coordinate type. Coordinates are converted to LLA (for Vincenty) or ECEF (for Euclidean) internally:
|
|
267
|
+
|
|
268
|
+
```ruby
|
|
269
|
+
utm = seattle.to_utm
|
|
270
|
+
mgrs = GCS::MGRS.from_lla(portland)
|
|
271
|
+
utm.distance_to(mgrs) # => Distance (235393.17 m)
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### ENU and NED (Relative Systems)
|
|
275
|
+
|
|
276
|
+
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:
|
|
277
|
+
|
|
278
|
+
```ruby
|
|
279
|
+
ref = GCS::LLA.new(lat: 47.62, lng: -122.35, alt: 0.0)
|
|
280
|
+
lla = enu.to_lla(ref)
|
|
281
|
+
lla.distance_to(other_lla)
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
ENU and NED retain `horizontal_distance_to` and `local_bearing_to` for local Euclidean operations within the tangent plane.
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
## Bearing Calculations
|
|
289
|
+
|
|
290
|
+
Universal bearing methods are available on all coordinate types and work across different coordinate systems. All bearing methods return `Bearing` objects.
|
|
291
|
+
|
|
292
|
+
### Great-Circle Bearing (Forward Azimuth)
|
|
293
|
+
|
|
294
|
+
- **`bearing_to(other)`** — Instance method. Computes the great-circle forward azimuth from the receiver to the target coordinate. Returns a `Bearing` object.
|
|
295
|
+
- **`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).
|
|
296
|
+
- **`GCS.bearing_between(*coords)`** — Class method on `Geodetic::Coordinates` (aliased as `GCS`). 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.
|
|
297
|
+
|
|
298
|
+
```ruby
|
|
299
|
+
seattle = GCS::LLA.new(lat: 47.6205, lng: -122.3493, alt: 0.0)
|
|
300
|
+
portland = GCS::LLA.new(lat: 45.5152, lng: -122.6784, alt: 0.0)
|
|
301
|
+
sf = GCS::LLA.new(lat: 37.7749, lng: -122.4194, alt: 0.0)
|
|
302
|
+
|
|
303
|
+
# Forward azimuth
|
|
304
|
+
b = seattle.bearing_to(portland) # => Bearing
|
|
305
|
+
b.degrees # => 188.2...
|
|
306
|
+
b.to_compass(points: 8) # => "S"
|
|
307
|
+
b.reverse # => Bearing (back azimuth)
|
|
308
|
+
|
|
309
|
+
# Elevation angle
|
|
310
|
+
seattle.elevation_to(portland) # => Float (degrees)
|
|
311
|
+
|
|
312
|
+
# Consecutive chain bearings
|
|
313
|
+
GCS.bearing_between(seattle, portland, sf) # => [Bearing, Bearing]
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### Cross-System Bearings
|
|
317
|
+
|
|
318
|
+
`bearing_to` and `elevation_to` accept any coordinate type. Coordinates are converted to LLA internally:
|
|
319
|
+
|
|
320
|
+
```ruby
|
|
321
|
+
utm = seattle.to_utm
|
|
322
|
+
mgrs = GCS::MGRS.from_lla(portland)
|
|
323
|
+
utm.bearing_to(mgrs) # => Bearing
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### ENU and NED (Relative Systems)
|
|
327
|
+
|
|
328
|
+
ENU and NED are relative coordinate systems and do not support `bearing_to` or `elevation_to` directly (these raise `ArgumentError`). Convert to an absolute system first, or use the local methods:
|
|
329
|
+
|
|
330
|
+
- **`local_bearing_to(other)`** — Local tangent plane bearing (degrees from north, 0-360)
|
|
331
|
+
- **`local_elevation_angle_to(other)`** — Local elevation angle (NED only, degrees)
|
|
332
|
+
|
|
333
|
+
### Bearing Class
|
|
334
|
+
|
|
335
|
+
`Bearing` wraps an azimuth angle (0-360) with compass and radian conversions:
|
|
336
|
+
|
|
337
|
+
```ruby
|
|
338
|
+
b = Geodetic::Bearing.new(225)
|
|
339
|
+
b.degrees # => 225.0
|
|
340
|
+
b.to_radians # => 3.926...
|
|
341
|
+
b.reverse # => Bearing (45)
|
|
342
|
+
b.to_compass(points: 4) # => "W"
|
|
343
|
+
b.to_compass(points: 8) # => "SW"
|
|
344
|
+
b.to_compass(points: 16) # => "SW"
|
|
345
|
+
b.to_s # => "225.0000°"
|
|
346
|
+
|
|
347
|
+
# Arithmetic
|
|
348
|
+
b + 10 # => Bearing (235°)
|
|
349
|
+
b - 10 # => Bearing (215°)
|
|
350
|
+
Bearing.new(90) - Bearing.new(45) # => 45.0 (Float, angular difference)
|
|
351
|
+
```
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# Datums Reference
|
|
2
|
+
|
|
3
|
+
## Geodetic::Datum
|
|
4
|
+
|
|
5
|
+
A datum defines the reference ellipsoid used for geodetic calculations. The `Geodetic::Datum` class provides access to 16 pre-defined geodetic datums.
|
|
6
|
+
|
|
7
|
+
### Constructor
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
datum = Geodetic::Datum.new(name: 'WGS84')
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The `name` parameter is case-insensitive and must match one of the 16 available datum names.
|
|
14
|
+
|
|
15
|
+
Raises `NameError` if the datum name is not recognized.
|
|
16
|
+
|
|
17
|
+
### Instance Attributes
|
|
18
|
+
|
|
19
|
+
| Attribute | Type | Description |
|
|
20
|
+
|-----------|------|-------------|
|
|
21
|
+
| `name` | String | Uppercase datum name |
|
|
22
|
+
| `desc` | String | Human-readable description |
|
|
23
|
+
| `a` | Float | Semi-major axis (equatorial radius) in meters |
|
|
24
|
+
| `b` | Float | Semi-minor axis (polar radius) in meters |
|
|
25
|
+
| `f` | Float | Flattening (computed as `1.0 / f_inv`) |
|
|
26
|
+
| `f_inv` | Float | Inverse flattening |
|
|
27
|
+
| `e` | Float | First eccentricity (computed as `sqrt(e2)`) |
|
|
28
|
+
| `e2` | Float | First eccentricity squared |
|
|
29
|
+
|
|
30
|
+
All attributes have both reader and writer accessors.
|
|
31
|
+
|
|
32
|
+
### WGS84 Constant
|
|
33
|
+
|
|
34
|
+
The most commonly used datum is available as a pre-built constant:
|
|
35
|
+
|
|
36
|
+
```ruby
|
|
37
|
+
Geodetic::WGS84
|
|
38
|
+
# => #<Geodetic::Datum name="WGS84", a=6378137.0, f_inv=298.257223563>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
This constant is the default datum for all conversion methods throughout the gem.
|
|
42
|
+
|
|
43
|
+
### Class Methods
|
|
44
|
+
|
|
45
|
+
#### `Datum.list`
|
|
46
|
+
|
|
47
|
+
Prints all available datums to STDOUT and returns `nil`.
|
|
48
|
+
|
|
49
|
+
```ruby
|
|
50
|
+
Geodetic::Datum.list
|
|
51
|
+
# AIRY: Airy 1830
|
|
52
|
+
# MODIFIED_AIRY: Modified Airy
|
|
53
|
+
# ...
|
|
54
|
+
# WGS84: World Geodetic System 1984
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
#### `Datum.get(name)`
|
|
58
|
+
|
|
59
|
+
Returns a Hash with all datum parameters for the given name. The name is case-insensitive. Raises `NameError` if the datum is not found.
|
|
60
|
+
|
|
61
|
+
```ruby
|
|
62
|
+
Geodetic::Datum.get('WGS84')
|
|
63
|
+
# => {
|
|
64
|
+
# "name" => "WGS84",
|
|
65
|
+
# "desc" => "World Geodetic System 1984",
|
|
66
|
+
# "a" => 6378137.0,
|
|
67
|
+
# "f_inv" => 298.257223563,
|
|
68
|
+
# "f" => 0.0033528106647...,
|
|
69
|
+
# "b" => 6356752.3142451793,
|
|
70
|
+
# "e2" => 0.00669437999014132,
|
|
71
|
+
# "e" => 0.08181919084...
|
|
72
|
+
# }
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Available Datums
|
|
76
|
+
|
|
77
|
+
| Name | Description | Semi-Major Axis (a) |
|
|
78
|
+
|------|-------------|---------------------|
|
|
79
|
+
| `AIRY` | Airy 1830 | 6,377,563.396 m |
|
|
80
|
+
| `MODIFIED_AIRY` | Modified Airy | 6,377,340.189 m |
|
|
81
|
+
| `AUSTRALIAN_NATIONAL` | Australian National | 6,378,160.0 m |
|
|
82
|
+
| `BESSEL_1841` | Bessel 1841 | 6,377,397.155 m |
|
|
83
|
+
| `CLARKE_1866` | Clarke 1866 | 6,378,206.4 m |
|
|
84
|
+
| `CLARKE_1880` | Clarke 1880 | 6,378,249.145 m |
|
|
85
|
+
| `EVEREST_INDIA_1830` | Everest (India 1830) | 6,377,276.345 m |
|
|
86
|
+
| `EVEREST_BRUNEI_E_MALAYSIA` | Everest (Brunei & E.Malaysia) | 6,377,298.556 m |
|
|
87
|
+
| `EVEREST_W_MALAYSIA_SINGAPORE` | Everest (W.Malaysia & Singapore) | 6,377,304.063 m |
|
|
88
|
+
| `GRS_1980` | Geodetic Reference System 1980 | 6,378,137.0 m |
|
|
89
|
+
| `HELMERT_1906` | Helmert 1906 | 6,378,200.0 m |
|
|
90
|
+
| `HOUGH_1960` | Hough 1960 | 6,378,270.0 m |
|
|
91
|
+
| `INTERNATIONAL_1924` | International 1924 | 6,378,388.0 m |
|
|
92
|
+
| `SOUTH_AMERICAN_1969` | South American 1969 | 6,378,160.0 m |
|
|
93
|
+
| `WGS72` | World Geodetic System 1972 | 6,378,135.0 m |
|
|
94
|
+
| `WGS84` | World Geodetic System 1984 | 6,378,137.0 m |
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Module Functions
|
|
99
|
+
|
|
100
|
+
The `Geodetic` module provides two conversion functions available as module methods:
|
|
101
|
+
|
|
102
|
+
```ruby
|
|
103
|
+
Geodetic.deg2rad(180.0) # => 3.14159265...
|
|
104
|
+
Geodetic.rad2deg(Math::PI) # => 180.0
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
| Function | Description |
|
|
108
|
+
|----------|-------------|
|
|
109
|
+
| `deg2rad(deg)` | Converts degrees to radians. Multiplies by `RAD_PER_DEG`. |
|
|
110
|
+
| `rad2deg(rad)` | Converts radians to degrees. Multiplies by `DEG_PER_RAD`. |
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Module Constants
|
|
115
|
+
|
|
116
|
+
| Constant | Value | Description |
|
|
117
|
+
|----------|-------|-------------|
|
|
118
|
+
| `RAD_PER_DEG` | 0.0174532925199433 | Radians per degree |
|
|
119
|
+
| `DEG_PER_RAD` | 57.2957795130823 | Degrees per radian |
|
|
120
|
+
| `QUARTER_PI` | 0.785398163397448 | Pi / 4 |
|
|
121
|
+
| `HALF_PI` | 1.5707963267949 | Pi / 2 |
|
|
122
|
+
| `FEET_PER_METER` | 3.2808399 | International feet per meter |
|
|
123
|
+
| `FEET_PER_MILE` | 5280.0 | Feet per statute mile |
|
|
124
|
+
| `INCH_PER_FOOT` | 12.0 | Inches per foot |
|
|
125
|
+
| `KM_PER_MILE` | 1.609344 | Kilometers per statute mile |
|
|
126
|
+
| `MILE_PER_KM` | 0.621371192237334 | Statute miles per kilometer |
|
|
127
|
+
| `SM_PER_NM` | 0.868976242 | Statute miles per nautical mile |
|
|
128
|
+
| `NM_PER_SM` | 1.15077944789197 | Nautical miles per statute mile |
|
|
129
|
+
| `NM_PER_DEG` | 60.0 | Nautical miles per degree of latitude |
|
|
130
|
+
| `SM_PER_DEG` | 52.13857452 | Statute miles per degree of latitude |
|
|
131
|
+
| `MILES_PER_DEG` | 52.13857452 | Alias for `SM_PER_DEG` |
|
|
132
|
+
| `GRAVITY_MS2` | 9.80665 | Standard gravity in m/s^2 |
|
|
133
|
+
| `GRAVITY_FS2` | 32.174 | Standard gravity in ft/s^2 |
|
|
134
|
+
| `GRAVITY` | 9.80665 | Alias for `GRAVITY_MS2` |
|