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
data/README.md
ADDED
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
# Geodetic
|
|
2
|
+
|
|
3
|
+
A Ruby gem for converting between geodetic coordinate systems. Supports 12 coordinate systems with full bidirectional conversions, plus geoid height calculations and geographic area operations.
|
|
4
|
+
|
|
5
|
+
## Coordinate Systems
|
|
6
|
+
|
|
7
|
+
| Class | Description |
|
|
8
|
+
|-------|-------------|
|
|
9
|
+
| `Coordinates::LLA` | Latitude, Longitude, Altitude (degrees/meters) |
|
|
10
|
+
| `Coordinates::ECEF` | Earth-Centered, Earth-Fixed (meters) |
|
|
11
|
+
| `Coordinates::UTM` | Universal Transverse Mercator |
|
|
12
|
+
| `Coordinates::ENU` | East, North, Up (local tangent plane) |
|
|
13
|
+
| `Coordinates::NED` | North, East, Down (local tangent plane) |
|
|
14
|
+
| `Coordinates::MGRS` | Military Grid Reference System |
|
|
15
|
+
| `Coordinates::USNG` | US National Grid |
|
|
16
|
+
| `Coordinates::WebMercator` | Web Mercator / EPSG:3857 |
|
|
17
|
+
| `Coordinates::UPS` | Universal Polar Stereographic |
|
|
18
|
+
| `Coordinates::StatePlane` | US State Plane Coordinate System |
|
|
19
|
+
| `Coordinates::BNG` | British National Grid |
|
|
20
|
+
| `Coordinates::GH36` | Geohash-36 (spatial hash, URL-friendly) |
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
Add to your Gemfile:
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
gem "geodetic"
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Or install directly:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
gem install geodetic
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Usage
|
|
37
|
+
|
|
38
|
+
### Basic Coordinate Creation
|
|
39
|
+
|
|
40
|
+
All constructors use keyword arguments:
|
|
41
|
+
|
|
42
|
+
```ruby
|
|
43
|
+
require "geodetic"
|
|
44
|
+
|
|
45
|
+
include Geodetic
|
|
46
|
+
|
|
47
|
+
lla = Coordinates::LLA.new(lat: 47.6205, lng: -122.3493, alt: 184.0)
|
|
48
|
+
ecef = Coordinates::ECEF.new(x: -2304643.57, y: -3638650.07, z: 4688674.43)
|
|
49
|
+
utm = Coordinates::UTM.new(easting: 548894.0, northing: 5272748.0, altitude: 184.0, zone: 10, hemisphere: "N")
|
|
50
|
+
enu = Coordinates::ENU.new(e: 100.0, n: 200.0, u: 50.0)
|
|
51
|
+
ned = Coordinates::NED.new(n: 200.0, e: 100.0, d: -50.0)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### GCS Shorthand
|
|
55
|
+
|
|
56
|
+
`GCS` is a top-level alias for `Geodetic::Coordinates`, providing a concise way to create and work with coordinates:
|
|
57
|
+
|
|
58
|
+
```ruby
|
|
59
|
+
require "geodetic"
|
|
60
|
+
|
|
61
|
+
# Use GCS as a shorthand for Geodetic::Coordinates
|
|
62
|
+
seattle = GCS::LLA.new(lat: 47.6205, lng: -122.3493, alt: 184.0)
|
|
63
|
+
ecef = GCS::ECEF.new(x: -2304643.57, y: -3638650.07, z: 4688674.43)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Coordinate Conversions
|
|
67
|
+
|
|
68
|
+
Every coordinate system can convert to and from every other system:
|
|
69
|
+
|
|
70
|
+
```ruby
|
|
71
|
+
lla = Coordinates::LLA.new(lat: 47.6205, lng: -122.3493, alt: 184.0)
|
|
72
|
+
|
|
73
|
+
# LLA to other systems
|
|
74
|
+
ecef = lla.to_ecef
|
|
75
|
+
utm = lla.to_utm
|
|
76
|
+
wm = Coordinates::WebMercator.from_lla(lla)
|
|
77
|
+
mgrs = Coordinates::MGRS.from_lla(lla)
|
|
78
|
+
|
|
79
|
+
# Convert back
|
|
80
|
+
lla_roundtrip = ecef.to_lla
|
|
81
|
+
|
|
82
|
+
# Local coordinate systems require a reference point
|
|
83
|
+
reference = Coordinates::LLA.new(lat: 47.62, lng: -122.35, alt: 0.0)
|
|
84
|
+
enu = lla.to_enu(reference)
|
|
85
|
+
ned = lla.to_ned(reference)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Serialization
|
|
89
|
+
|
|
90
|
+
All coordinate classes support `to_s`, `to_a`, `from_string`, and `from_array`. The `to_s` method accepts an optional precision parameter controlling the number of decimal places:
|
|
91
|
+
|
|
92
|
+
```ruby
|
|
93
|
+
lla = Coordinates::LLA.new(lat: 47.6205, lng: -122.3493, alt: 184.0)
|
|
94
|
+
|
|
95
|
+
lla.to_s # => "47.620500, -122.349300, 184.00"
|
|
96
|
+
lla.to_s(3) # => "47.620, -122.349, 184.00"
|
|
97
|
+
lla.to_s(0) # => "48, -122, 184"
|
|
98
|
+
lla.to_a # => [47.6205, -122.3493, 184.0]
|
|
99
|
+
|
|
100
|
+
Coordinates::LLA.from_string("47.6205, -122.3493, 184.0")
|
|
101
|
+
Coordinates::LLA.from_array([47.6205, -122.3493, 184.0])
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Default precisions by class: LLA=6, Bearing=4, all others=2. Passing `0` returns integers.
|
|
105
|
+
|
|
106
|
+
### Validated Setters
|
|
107
|
+
|
|
108
|
+
All coordinate classes provide setter methods with type coercion and validation:
|
|
109
|
+
|
|
110
|
+
```ruby
|
|
111
|
+
lla = Coordinates::LLA.new(lat: 47.0, lng: -122.0, alt: 100.0)
|
|
112
|
+
lla.lat = 48.0 # validates -90..90
|
|
113
|
+
lla.lng = -121.0 # validates -180..180
|
|
114
|
+
lla.alt = 200.0 # no range constraint
|
|
115
|
+
lla.lat = 91.0 # => ArgumentError
|
|
116
|
+
|
|
117
|
+
utm = Coordinates::UTM.new(easting: 500000.0, northing: 5000000.0, zone: 10, hemisphere: 'N')
|
|
118
|
+
utm.zone = 15 # validates 1..60
|
|
119
|
+
utm.hemisphere = 'S' # validates 'N' or 'S'
|
|
120
|
+
utm.easting = -1.0 # => ArgumentError
|
|
121
|
+
|
|
122
|
+
# UPS cross-validates hemisphere/zone combinations
|
|
123
|
+
ups = Coordinates::UPS.new(hemisphere: 'N', zone: 'Y')
|
|
124
|
+
ups.zone = 'Z' # valid for hemisphere 'N'
|
|
125
|
+
ups.zone = 'A' # => ArgumentError (rolls back)
|
|
126
|
+
|
|
127
|
+
# BNG auto-updates grid_ref when easting/northing change
|
|
128
|
+
bng = Coordinates::BNG.new(easting: 530000, northing: 180000)
|
|
129
|
+
bng.easting = 430000 # grid_ref automatically recalculated
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
ECEF, ENU, NED, and WebMercator setters coerce to float with no range constraints. MGRS, USNG, GH36, Distance, and Bearing are immutable.
|
|
133
|
+
|
|
134
|
+
### DMS (Degrees, Minutes, Seconds)
|
|
135
|
+
|
|
136
|
+
```ruby
|
|
137
|
+
lla = Coordinates::LLA.new(lat: 37.7749, lng: -122.4192, alt: 15.0)
|
|
138
|
+
lla.to_dms # => "37° 46' 29.64\" N, 122° 25' 9.12\" W, 15.00 m"
|
|
139
|
+
|
|
140
|
+
Coordinates::LLA.from_dms("37° 46' 29.64\" N, 122° 25' 9.12\" W, 15.00 m")
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### String-Based Coordinate Systems
|
|
144
|
+
|
|
145
|
+
MGRS and USNG use string representations:
|
|
146
|
+
|
|
147
|
+
```ruby
|
|
148
|
+
mgrs = Coordinates::MGRS.new(mgrs_string: "18SUJ2337006519")
|
|
149
|
+
mgrs = Coordinates::MGRS.from_string("18SUJ2337006519")
|
|
150
|
+
mgrs.to_s # => "18SUJ2337006519"
|
|
151
|
+
|
|
152
|
+
usng = Coordinates::USNG.new(usng_string: "18T WL 12345 67890")
|
|
153
|
+
usng = Coordinates::USNG.from_string("18T WL 12345 67890")
|
|
154
|
+
usng.to_s # => "18T WL 12345 67890"
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Distance Calculations
|
|
158
|
+
|
|
159
|
+
Universal distance methods work across all coordinate types and return `Distance` objects with unit tracking and conversion.
|
|
160
|
+
|
|
161
|
+
**Instance method `distance_to`** — Vincenty great-circle distance:
|
|
162
|
+
|
|
163
|
+
```ruby
|
|
164
|
+
seattle = GCS::LLA.new(lat: 47.6205, lng: -122.3493, alt: 0.0)
|
|
165
|
+
portland = GCS::LLA.new(lat: 45.5152, lng: -122.6784, alt: 0.0)
|
|
166
|
+
sf = GCS::LLA.new(lat: 37.7749, lng: -122.4194, alt: 0.0)
|
|
167
|
+
|
|
168
|
+
d = seattle.distance_to(portland) # => Distance (meters)
|
|
169
|
+
d.meters # => 235393.17
|
|
170
|
+
d.to_km.to_f # => 235.39
|
|
171
|
+
d.to_mi.to_f # => 146.28
|
|
172
|
+
|
|
173
|
+
seattle.distance_to(portland, sf) # => [Distance, Distance] (radial)
|
|
174
|
+
seattle.distance_to([portland, sf]) # => [Distance, Distance] (radial)
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
**Class method `distance_between`** — consecutive chain distances:
|
|
178
|
+
|
|
179
|
+
```ruby
|
|
180
|
+
GCS.distance_between(seattle, portland) # => Distance
|
|
181
|
+
GCS.distance_between(seattle, portland, sf) # => [Distance, Distance] (chain)
|
|
182
|
+
GCS.distance_between([seattle, portland, sf]) # => [Distance, Distance] (chain)
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
**Straight-line (ECEF Euclidean) versions:**
|
|
186
|
+
|
|
187
|
+
```ruby
|
|
188
|
+
seattle.straight_line_distance_to(portland) # => Distance
|
|
189
|
+
GCS.straight_line_distance_between(seattle, portland) # => Distance
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
**Cross-system distances** — works between any coordinate types:
|
|
193
|
+
|
|
194
|
+
```ruby
|
|
195
|
+
utm = seattle.to_utm
|
|
196
|
+
mgrs = GCS::MGRS.from_lla(portland)
|
|
197
|
+
utm.distance_to(mgrs) # => Distance
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
> **Note:** ENU and NED are relative coordinate systems and must be converted to an absolute system before distance and bearing calculations. They retain `local_bearing_to`, `horizontal_distance_to`, and other local methods for tangent-plane operations.
|
|
201
|
+
|
|
202
|
+
### Bearing Calculations
|
|
203
|
+
|
|
204
|
+
Universal bearing methods work across all coordinate types and return `Bearing` objects.
|
|
205
|
+
|
|
206
|
+
**Instance method `bearing_to`** — great-circle forward azimuth:
|
|
207
|
+
|
|
208
|
+
```ruby
|
|
209
|
+
seattle = GCS::LLA.new(lat: 47.6205, lng: -122.3493, alt: 0.0)
|
|
210
|
+
portland = GCS::LLA.new(lat: 45.5152, lng: -122.6784, alt: 0.0)
|
|
211
|
+
|
|
212
|
+
b = seattle.bearing_to(portland) # => Bearing
|
|
213
|
+
b.degrees # => 188.2
|
|
214
|
+
b.to_radians # => 3.28...
|
|
215
|
+
b.to_compass # => "S"
|
|
216
|
+
b.to_compass(points: 8) # => "S"
|
|
217
|
+
b.reverse # => Bearing (back azimuth)
|
|
218
|
+
b.to_s # => "188.2036°"
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
**Instance method `elevation_to`** — vertical look angle:
|
|
222
|
+
|
|
223
|
+
```ruby
|
|
224
|
+
a = GCS::LLA.new(lat: 47.62, lng: -122.35, alt: 0.0)
|
|
225
|
+
b = GCS::LLA.new(lat: 47.62, lng: -122.35, alt: 5000.0)
|
|
226
|
+
|
|
227
|
+
a.elevation_to(b) # => 89.9... (degrees, nearly straight up)
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
**Class method `bearing_between`** — consecutive chain bearings:
|
|
231
|
+
|
|
232
|
+
```ruby
|
|
233
|
+
GCS.bearing_between(seattle, portland) # => Bearing
|
|
234
|
+
GCS.bearing_between(seattle, portland, sf) # => [Bearing, Bearing] (chain)
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
**Cross-system bearings** — works between any coordinate types:
|
|
238
|
+
|
|
239
|
+
```ruby
|
|
240
|
+
utm = seattle.to_utm
|
|
241
|
+
mgrs = GCS::MGRS.from_lla(portland)
|
|
242
|
+
utm.bearing_to(mgrs) # => Bearing
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Bearing Class
|
|
246
|
+
|
|
247
|
+
`Bearing` wraps an azimuth angle (0-360°) with compass and radian conversions.
|
|
248
|
+
|
|
249
|
+
```ruby
|
|
250
|
+
b = Geodetic::Bearing.new(225)
|
|
251
|
+
b.degrees # => 225.0
|
|
252
|
+
b.to_radians # => 3.926...
|
|
253
|
+
b.reverse # => Bearing (45°)
|
|
254
|
+
b.to_compass(points: 4) # => "W"
|
|
255
|
+
b.to_compass(points: 8) # => "SW"
|
|
256
|
+
b.to_compass(points: 16) # => "SW"
|
|
257
|
+
b.to_s # => "225.0000°"
|
|
258
|
+
b.to_s(1) # => "225.0°"
|
|
259
|
+
b.to_s(0) # => "225°"
|
|
260
|
+
|
|
261
|
+
# Arithmetic
|
|
262
|
+
b + 10 # => Bearing (235°)
|
|
263
|
+
b - 10 # => Bearing (215°)
|
|
264
|
+
Bearing.new(90) - Bearing.new(45) # => 45.0 (Float, angular difference)
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Distance Class
|
|
268
|
+
|
|
269
|
+
`Distance` tracks values internally in meters with a configurable display unit. All distance methods return `Distance` objects.
|
|
270
|
+
|
|
271
|
+
**Construction:**
|
|
272
|
+
|
|
273
|
+
```ruby
|
|
274
|
+
d = Geodetic::Distance.new(1000) # 1000 meters
|
|
275
|
+
d = Geodetic::Distance.km(5) # 5 kilometers
|
|
276
|
+
d = Geodetic::Distance.mi(3) # 3 miles
|
|
277
|
+
d = Geodetic::Distance.ft(5280) # 5280 feet
|
|
278
|
+
d = Geodetic::Distance.nmi(1) # 1 nautical mile
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
**Unit conversions** — return a new `Distance` with the same meters, different display unit:
|
|
282
|
+
|
|
283
|
+
```ruby
|
|
284
|
+
d = Geodetic::Distance.new(1609.344)
|
|
285
|
+
d.to_km.to_f # => 1.609344
|
|
286
|
+
d.to_mi.to_f # => 1.0
|
|
287
|
+
d.to_ft.to_f # => 5280.0
|
|
288
|
+
d.to_nmi.to_f # => 0.869...
|
|
289
|
+
d.meters # => 1609.344 (always available)
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
**Display and formatting:**
|
|
293
|
+
|
|
294
|
+
```ruby
|
|
295
|
+
d = Geodetic::Distance.new(5000).to_km
|
|
296
|
+
d.to_f # => 5.0 (in display unit)
|
|
297
|
+
d.to_i # => 5
|
|
298
|
+
d.to_s # => "5.00 km"
|
|
299
|
+
d.to_s(1) # => "5.0 km"
|
|
300
|
+
d.to_s(0) # => "5 km"
|
|
301
|
+
d.inspect # => "#<Geodetic::Distance 5.00 km (5000.0 m)>"
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
**Arithmetic** — results always in meters:
|
|
305
|
+
|
|
306
|
+
```ruby
|
|
307
|
+
d1 = Geodetic::Distance.km(5)
|
|
308
|
+
d2 = Geodetic::Distance.mi(3)
|
|
309
|
+
|
|
310
|
+
(d1 + d2).meters # => 9828.032 (5km + 3mi in meters)
|
|
311
|
+
(d1 - d2).meters # => 171.968
|
|
312
|
+
(d1 * 2).meters # => 10000.0
|
|
313
|
+
(d1 / 2).meters # => 2500.0
|
|
314
|
+
d1 / d2 # => 1.034... (Float ratio)
|
|
315
|
+
|
|
316
|
+
# Numeric constants use the display unit
|
|
317
|
+
d = Geodetic::Distance.new(5000).to_km # 5 km
|
|
318
|
+
(d + 3).meters # => 8000.0 (3 km added)
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
**Comparison:**
|
|
322
|
+
|
|
323
|
+
```ruby
|
|
324
|
+
Geodetic::Distance.km(1) == Geodetic::Distance.new(1000) # => true
|
|
325
|
+
Geodetic::Distance.km(5) > Geodetic::Distance.mi(2) # => true
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
**Supported units:** meters (m), kilometers (km), centimeters (cm), millimeters (mm), miles (mi), yards (yd), feet (ft), inches (in), nautical_miles (nmi)
|
|
329
|
+
|
|
330
|
+
### Datums
|
|
331
|
+
|
|
332
|
+
```ruby
|
|
333
|
+
wgs84 = Datum.new(name: "WGS84")
|
|
334
|
+
wgs84.a # => 6378137.0 (semi-major axis)
|
|
335
|
+
wgs84.e2 # => 0.00669437999014132 (eccentricity squared)
|
|
336
|
+
|
|
337
|
+
# Use a different datum for conversions
|
|
338
|
+
nad27 = Datum.new(name: "CLARKE_1866")
|
|
339
|
+
ecef = lla.to_ecef(nad27)
|
|
340
|
+
|
|
341
|
+
# List available datums
|
|
342
|
+
Datum.list
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### Geoid Height
|
|
346
|
+
|
|
347
|
+
```ruby
|
|
348
|
+
geoid = GeoidHeight.new(geoid_model: "EGM2008")
|
|
349
|
+
|
|
350
|
+
# Get geoid height at a location
|
|
351
|
+
geoid.geoid_height_at(47.6205, -122.3493)
|
|
352
|
+
|
|
353
|
+
# Convert between height datums
|
|
354
|
+
geoid.ellipsoidal_to_orthometric(47.6205, -122.3493, 184.0)
|
|
355
|
+
geoid.orthometric_to_ellipsoidal(47.6205, -122.3493, 150.0)
|
|
356
|
+
|
|
357
|
+
# Convert between vertical datums
|
|
358
|
+
geoid.convert_vertical_datum(47.6205, -122.3493, 184.0, "HAE", "NAVD88")
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
The `GeoidHeightSupport` module is mixed into LLA for convenience:
|
|
362
|
+
|
|
363
|
+
```ruby
|
|
364
|
+
lla = Coordinates::LLA.new(lat: 47.6205, lng: -122.3493, alt: 184.0)
|
|
365
|
+
lla.geoid_height # => geoid undulation in meters
|
|
366
|
+
lla.orthometric_height # => height above mean sea level
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
### Geohash-36 (GH36)
|
|
370
|
+
|
|
371
|
+
A spatial hashing coordinate that encodes lat/lng into a compact, URL-friendly string:
|
|
372
|
+
|
|
373
|
+
```ruby
|
|
374
|
+
# From a geohash string
|
|
375
|
+
gh36 = Coordinates::GH36.new("bdrdC26BqH")
|
|
376
|
+
|
|
377
|
+
# From any coordinate
|
|
378
|
+
gh36 = Coordinates::GH36.new(lla)
|
|
379
|
+
gh36 = lla.to_gh36(precision: 8)
|
|
380
|
+
|
|
381
|
+
# Decode back to LLA
|
|
382
|
+
lla = gh36.to_lla
|
|
383
|
+
|
|
384
|
+
# URL slug (the hash itself is URL-safe)
|
|
385
|
+
gh36.to_slug # => "bdrdC26BqH"
|
|
386
|
+
|
|
387
|
+
# Neighbor cells
|
|
388
|
+
gh36.neighbors # => { N: GH36, S: GH36, E: GH36, W: GH36, NE: ..., NW: ..., SE: ..., SW: ... }
|
|
389
|
+
|
|
390
|
+
# Bounding rectangle of the geohash cell
|
|
391
|
+
area = gh36.to_area # => Areas::Rectangle
|
|
392
|
+
area.includes?(gh36.to_lla) # => true
|
|
393
|
+
|
|
394
|
+
# Precision info
|
|
395
|
+
gh36.precision # => 10
|
|
396
|
+
gh36.precision_in_meters # => { lat: 0.31, lng: 0.62 }
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
### Geographic Areas
|
|
400
|
+
|
|
401
|
+
```ruby
|
|
402
|
+
# Circle area
|
|
403
|
+
center = Coordinates::LLA.new(lat: 47.6205, lng: -122.3493, alt: 0.0)
|
|
404
|
+
circle = Areas::Circle.new(centroid: center, radius: 1000.0) # 1km radius
|
|
405
|
+
|
|
406
|
+
# Polygon area
|
|
407
|
+
points = [
|
|
408
|
+
Coordinates::LLA.new(lat: 47.60, lng: -122.35, alt: 0.0),
|
|
409
|
+
Coordinates::LLA.new(lat: 47.63, lng: -122.35, alt: 0.0),
|
|
410
|
+
Coordinates::LLA.new(lat: 47.63, lng: -122.33, alt: 0.0),
|
|
411
|
+
Coordinates::LLA.new(lat: 47.60, lng: -122.33, alt: 0.0),
|
|
412
|
+
]
|
|
413
|
+
polygon = Areas::Polygon.new(boundary: points)
|
|
414
|
+
polygon.centroid # => computed centroid as LLA
|
|
415
|
+
|
|
416
|
+
# Rectangle area (accepts any coordinate type)
|
|
417
|
+
nw = Coordinates::LLA.new(lat: 41.0, lng: -75.0)
|
|
418
|
+
se = Coordinates::LLA.new(lat: 40.0, lng: -74.0)
|
|
419
|
+
rect = Areas::Rectangle.new(nw: nw, se: se)
|
|
420
|
+
rect.centroid # => LLA at center
|
|
421
|
+
rect.ne # => computed NE corner
|
|
422
|
+
rect.sw # => computed SW corner
|
|
423
|
+
rect.includes?(point) # => true/false
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
### Web Mercator Tile Coordinates
|
|
427
|
+
|
|
428
|
+
```ruby
|
|
429
|
+
wm = Coordinates::WebMercator.from_lla(lla)
|
|
430
|
+
wm.to_tile_coordinates(15) # => [x_tile, y_tile, zoom]
|
|
431
|
+
wm.to_pixel_coordinates(15) # => [x_pixel, y_pixel, zoom]
|
|
432
|
+
|
|
433
|
+
Coordinates::WebMercator.from_tile_coordinates(5241, 11438, 15)
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
## Available Datums
|
|
437
|
+
|
|
438
|
+
Airy 1830, Modified Airy, Australian National, Bessel 1841, Clarke 1866, Clarke 1880, Everest (India 1830), Everest (Brunei & E.Malaysia), Everest (W.Malaysia & Singapore), GRS 1980, Helmert 1906, Hough 1960, International 1924, South American 1969, WGS72, WGS84
|
|
439
|
+
|
|
440
|
+
## Documentation
|
|
441
|
+
|
|
442
|
+
Full documentation is available at **[madbomber.github.io/geodetic](https://madbomber.github.io/geodetic/)**.
|
|
443
|
+
|
|
444
|
+
## Examples
|
|
445
|
+
|
|
446
|
+
The [`examples/`](examples/) directory contains runnable demo scripts showing progressive usage:
|
|
447
|
+
|
|
448
|
+
| Script | Description |
|
|
449
|
+
|--------|-------------|
|
|
450
|
+
| [`01_basic_conversions.rb`](examples/01_basic_conversions.rb) | LLA, ECEF, UTM, ENU, NED conversions and roundtrips |
|
|
451
|
+
| [`02_all_coordinate_systems.rb`](examples/02_all_coordinate_systems.rb) | All 12 coordinate systems, cross-system chains, and areas |
|
|
452
|
+
| [`03_distance_calculations.rb`](examples/03_distance_calculations.rb) | Distance class features, unit conversions, and arithmetic |
|
|
453
|
+
| [`04_bearing_calculations.rb`](examples/04_bearing_calculations.rb) | Bearing class, compass directions, elevation angles, and chain bearings |
|
|
454
|
+
|
|
455
|
+
Run any example with:
|
|
456
|
+
|
|
457
|
+
```bash
|
|
458
|
+
ruby -Ilib examples/01_basic_conversions.rb
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
## Development
|
|
462
|
+
|
|
463
|
+
```bash
|
|
464
|
+
bin/setup # Install dependencies
|
|
465
|
+
rake test # Run tests
|
|
466
|
+
bin/console # Interactive console
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
## License
|
|
470
|
+
|
|
471
|
+
Available as open source under the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Geodetic::Coordinates::BNG
|
|
2
|
+
|
|
3
|
+
## British National Grid
|
|
4
|
+
|
|
5
|
+
The British National Grid (BNG) is the official coordinate system for Great Britain, based on the **OSGB36 datum** and the **Airy 1830 ellipsoid**. It uses a Transverse Mercator projection centered on 2°W longitude and 49°N latitude.
|
|
6
|
+
|
|
7
|
+
## Constructors
|
|
8
|
+
|
|
9
|
+
Create a BNG coordinate from numeric easting/northing values:
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
point = Geodetic::Coordinates::BNG.new(easting: 530000.0, northing: 180000.0)
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Alternatively, create from an alphanumeric grid reference string:
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
point = Geodetic::Coordinates::BNG.new(grid_ref: "TQ 300000 800000")
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Grid References
|
|
22
|
+
|
|
23
|
+
Convert a BNG coordinate to an alphanumeric grid reference at a specified precision:
|
|
24
|
+
|
|
25
|
+
```ruby
|
|
26
|
+
grid_ref = point.to_grid_reference(precision)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The `precision` parameter controls the number of digits and therefore the resolution of the resulting grid reference.
|
|
30
|
+
|
|
31
|
+
## Ellipsoid and Datum
|
|
32
|
+
|
|
33
|
+
BNG uses the **Airy 1830 ellipsoid** internally as part of the OSGB36 datum. When converting between BNG and WGS84-based coordinate systems (such as GPS coordinates), an approximate **OSGB36 to WGS84 transformation** is applied. This transformation introduces small positional offsets (typically a few meters) due to the inherent differences between the two datums.
|
|
34
|
+
|
|
35
|
+
## Validation
|
|
36
|
+
|
|
37
|
+
The `valid?` method checks that the coordinates fall within the bounds of Great Britain:
|
|
38
|
+
|
|
39
|
+
| Axis | Valid Range |
|
|
40
|
+
|---|---|
|
|
41
|
+
| Easting | 0 to 700,000 meters |
|
|
42
|
+
| Northing | 0 to 1,300,000 meters |
|
|
43
|
+
|
|
44
|
+
```ruby
|
|
45
|
+
point.valid? # => true if within Great Britain bounds
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Utility Methods
|
|
49
|
+
|
|
50
|
+
### Universal Distance and Bearing Methods
|
|
51
|
+
|
|
52
|
+
The universal `distance_to` method computes the Vincenty great-circle distance to any other coordinate type, returning a `Distance` object. The `straight_line_distance_to` method computes the Euclidean distance in ECEF space. The universal `bearing_to` method computes the great-circle forward azimuth, returning a `Bearing` object. All accept single or multiple targets.
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
bng_a = Geodetic::Coordinates::BNG.new(easting: 530000.0, northing: 180000.0)
|
|
56
|
+
bng_b = Geodetic::Coordinates::BNG.new(easting: 540000.0, northing: 190000.0)
|
|
57
|
+
bng_a.distance_to(bng_b) # => Distance (great-circle)
|
|
58
|
+
bng_a.straight_line_distance_to(bng_b) # => Distance (Euclidean)
|
|
59
|
+
bng_a.bearing_to(bng_b) # => Bearing (great-circle forward azimuth)
|
|
60
|
+
```
|