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,310 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# Comprehensive demonstration of all coordinate systems
|
|
3
|
+
# Shows complete orthogonal conversions between all implemented coordinate systems
|
|
4
|
+
|
|
5
|
+
require_relative '../lib/geodetic'
|
|
6
|
+
require_relative '../lib/geodetic/coordinates/lla'
|
|
7
|
+
require_relative '../lib/geodetic/coordinates/ecef'
|
|
8
|
+
require_relative '../lib/geodetic/coordinates/utm'
|
|
9
|
+
require_relative '../lib/geodetic/coordinates/enu'
|
|
10
|
+
require_relative '../lib/geodetic/coordinates/ned'
|
|
11
|
+
require_relative '../lib/geodetic/coordinates/mgrs'
|
|
12
|
+
require_relative '../lib/geodetic/coordinates/web_mercator'
|
|
13
|
+
require_relative '../lib/geodetic/coordinates/ups'
|
|
14
|
+
require_relative '../lib/geodetic/coordinates/usng'
|
|
15
|
+
require_relative '../lib/geodetic/coordinates/state_plane'
|
|
16
|
+
require_relative '../lib/geodetic/coordinates/bng'
|
|
17
|
+
require_relative '../lib/geodetic/coordinates/gh36'
|
|
18
|
+
require_relative '../lib/geodetic/geoid_height'
|
|
19
|
+
|
|
20
|
+
include Geodetic
|
|
21
|
+
LLA = Coordinates::LLA
|
|
22
|
+
ECEF = Coordinates::ECEF
|
|
23
|
+
UTM_Coord = Coordinates::UTM
|
|
24
|
+
ENU = Coordinates::ENU
|
|
25
|
+
NED = Coordinates::NED
|
|
26
|
+
MGRS = Coordinates::MGRS
|
|
27
|
+
USNG = Coordinates::USNG
|
|
28
|
+
WebMerc = Coordinates::WebMercator
|
|
29
|
+
UPS = Coordinates::UPS
|
|
30
|
+
StatePlane = Coordinates::StatePlane
|
|
31
|
+
BNG = Coordinates::BNG
|
|
32
|
+
GH36 = Coordinates::GH36
|
|
33
|
+
|
|
34
|
+
def demo_coordinate_systems
|
|
35
|
+
puts "=" * 80
|
|
36
|
+
puts "COMPLETE COORDINATE SYSTEM CONVERSION DEMONSTRATION"
|
|
37
|
+
puts "=" * 80
|
|
38
|
+
puts
|
|
39
|
+
|
|
40
|
+
# Test location: Seattle Space Needle
|
|
41
|
+
seattle_lat = 47.6205
|
|
42
|
+
seattle_lng = -122.3493
|
|
43
|
+
seattle_alt = 184.0
|
|
44
|
+
|
|
45
|
+
puts "Reference Location: Seattle Space Needle"
|
|
46
|
+
puts " Latitude: #{seattle_lat} degrees"
|
|
47
|
+
puts " Longitude: #{seattle_lng} degrees"
|
|
48
|
+
puts " Altitude: #{seattle_alt} meters"
|
|
49
|
+
puts
|
|
50
|
+
|
|
51
|
+
# Create reference LLA coordinate
|
|
52
|
+
lla_coord = LLA.new(lat: seattle_lat, lng: seattle_lng, alt: seattle_alt)
|
|
53
|
+
reference_lla = LLA.new(lat: seattle_lat, lng: seattle_lng, alt: 0.0) # For local coordinates
|
|
54
|
+
|
|
55
|
+
puts "ALL COORDINATE SYSTEM CONVERSIONS"
|
|
56
|
+
puts "-" * 50
|
|
57
|
+
|
|
58
|
+
# ========== ECEF Conversion ==========
|
|
59
|
+
puts "Earth-Centered, Earth-Fixed (ECEF)"
|
|
60
|
+
ecef_coord = lla_coord.to_ecef
|
|
61
|
+
puts " ECEF: X=#{ecef_coord.x.round(3)}m, Y=#{ecef_coord.y.round(3)}m, Z=#{ecef_coord.z.round(3)}m"
|
|
62
|
+
|
|
63
|
+
# Round-trip test
|
|
64
|
+
lla_from_ecef = ecef_coord.to_lla
|
|
65
|
+
lat_error = (lla_from_ecef.lat - seattle_lat).abs
|
|
66
|
+
lng_error = (lla_from_ecef.lng - seattle_lng).abs
|
|
67
|
+
puts " Round-trip error: Lat=#{lat_error.round(15)}, Lng=#{lng_error.round(15)}"
|
|
68
|
+
puts
|
|
69
|
+
|
|
70
|
+
# ========== UTM Conversion ==========
|
|
71
|
+
puts "Universal Transverse Mercator (UTM)"
|
|
72
|
+
utm_coord = lla_coord.to_utm
|
|
73
|
+
puts " UTM: #{utm_coord.easting.round(3)}E #{utm_coord.northing.round(3)}N Zone #{utm_coord.zone}#{utm_coord.hemisphere}"
|
|
74
|
+
|
|
75
|
+
# Round-trip test
|
|
76
|
+
lla_from_utm = utm_coord.to_lla
|
|
77
|
+
utm_lat_error = (lla_from_utm.lat - seattle_lat).abs
|
|
78
|
+
utm_lng_error = (lla_from_utm.lng - seattle_lng).abs
|
|
79
|
+
puts " Round-trip error: Lat=#{utm_lat_error.round(8)}, Lng=#{utm_lng_error.round(8)}"
|
|
80
|
+
puts
|
|
81
|
+
|
|
82
|
+
# ========== ENU Conversion ==========
|
|
83
|
+
puts "East, North, Up (ENU) Local Coordinates"
|
|
84
|
+
enu_coord = lla_coord.to_enu(reference_lla)
|
|
85
|
+
puts " ENU: E=#{enu_coord.east.round(3)}m, N=#{enu_coord.north.round(3)}m, U=#{enu_coord.up.round(3)}m"
|
|
86
|
+
puts " Distance from reference: #{enu_coord.distance_to_origin.round(3)}m"
|
|
87
|
+
puts " Bearing from reference: #{enu_coord.bearing_from_origin.round(3)} degrees"
|
|
88
|
+
puts
|
|
89
|
+
|
|
90
|
+
# ========== NED Conversion ==========
|
|
91
|
+
puts "North, East, Down (NED) Local Coordinates"
|
|
92
|
+
ned_coord = lla_coord.to_ned(reference_lla)
|
|
93
|
+
puts " NED: N=#{ned_coord.north.round(3)}m, E=#{ned_coord.east.round(3)}m, D=#{ned_coord.down.round(3)}m"
|
|
94
|
+
puts " Distance from reference: #{ned_coord.distance_to_origin.round(3)}m"
|
|
95
|
+
puts " Elevation angle: #{ned_coord.elevation_angle.round(3)} degrees"
|
|
96
|
+
puts
|
|
97
|
+
|
|
98
|
+
# ========== MGRS Conversion ==========
|
|
99
|
+
puts "Military Grid Reference System (MGRS)"
|
|
100
|
+
mgrs_coord = MGRS.from_lla(lla_coord)
|
|
101
|
+
puts " MGRS: #{mgrs_coord}"
|
|
102
|
+
puts " Zone: #{mgrs_coord.grid_zone_designator}, Square: #{mgrs_coord.square_identifier}"
|
|
103
|
+
puts " Within square: #{mgrs_coord.easting.round(3)}E, #{mgrs_coord.northing.round(3)}N"
|
|
104
|
+
|
|
105
|
+
# Test different precisions
|
|
106
|
+
mgrs_10m = MGRS.from_lla(lla_coord, WGS84, 4)
|
|
107
|
+
mgrs_1m = MGRS.from_lla(lla_coord, WGS84, 5)
|
|
108
|
+
puts " 10m precision: #{mgrs_10m}"
|
|
109
|
+
puts " 1m precision: #{mgrs_1m}"
|
|
110
|
+
puts
|
|
111
|
+
|
|
112
|
+
# ========== USNG Conversion ==========
|
|
113
|
+
puts "US National Grid (USNG)"
|
|
114
|
+
usng_coord = USNG.from_lla(lla_coord)
|
|
115
|
+
puts " USNG: #{usng_coord}"
|
|
116
|
+
puts " Full format: #{usng_coord.to_full_format}"
|
|
117
|
+
puts " Abbreviated: #{usng_coord.to_abbreviated_format}"
|
|
118
|
+
puts
|
|
119
|
+
|
|
120
|
+
# ========== Web Mercator Conversion ==========
|
|
121
|
+
puts "Web Mercator (EPSG:3857) - Used by Google Maps, OSM"
|
|
122
|
+
web_merc_coord = WebMerc.from_lla(lla_coord)
|
|
123
|
+
puts " Web Mercator: X=#{web_merc_coord.x.round(3)}m, Y=#{web_merc_coord.y.round(3)}m"
|
|
124
|
+
|
|
125
|
+
# Test tile coordinates at different zoom levels
|
|
126
|
+
tile_10 = web_merc_coord.to_tile_coordinates(10)
|
|
127
|
+
tile_15 = web_merc_coord.to_tile_coordinates(15)
|
|
128
|
+
puts " Tile (zoom 10): X=#{tile_10[0]}, Y=#{tile_10[1]}"
|
|
129
|
+
puts " Tile (zoom 15): X=#{tile_15[0]}, Y=#{tile_15[1]}"
|
|
130
|
+
puts
|
|
131
|
+
|
|
132
|
+
# ========== UPS Conversion (using North Pole example) ==========
|
|
133
|
+
puts "Universal Polar Stereographic (UPS)"
|
|
134
|
+
north_pole_lla = LLA.new(lat: 89.0, lng: 0.0, alt: 0.0)
|
|
135
|
+
ups_coord = UPS.from_lla(north_pole_lla)
|
|
136
|
+
puts " UPS (North Pole): #{ups_coord.easting.round(3)}E #{ups_coord.northing.round(3)}N Zone #{ups_coord.zone}#{ups_coord.hemisphere}"
|
|
137
|
+
puts " Grid convergence: #{ups_coord.grid_convergence.round(6)} degrees"
|
|
138
|
+
puts " Scale factor: #{ups_coord.point_scale_factor.round(8)}"
|
|
139
|
+
puts
|
|
140
|
+
|
|
141
|
+
# ========== State Plane Conversion ==========
|
|
142
|
+
puts "State Plane Coordinate System (SPC)"
|
|
143
|
+
begin
|
|
144
|
+
ca_spc = StatePlane.from_lla(lla_coord, 'CA_I')
|
|
145
|
+
puts " California Zone I: #{ca_spc.easting.round(3)}ft, #{ca_spc.northing.round(3)}ft"
|
|
146
|
+
puts " State: #{ca_spc.zone_info[:state]}, Projection: #{ca_spc.zone_info[:projection]}"
|
|
147
|
+
puts " Units: #{ca_spc.zone_info[:units]}"
|
|
148
|
+
|
|
149
|
+
# Convert to meters
|
|
150
|
+
ca_spc_meters = ca_spc.to_meters
|
|
151
|
+
puts " In meters: #{ca_spc_meters.easting.round(3)}m, #{ca_spc_meters.northing.round(3)}m"
|
|
152
|
+
rescue => e
|
|
153
|
+
puts " State Plane conversion: #{e.message}"
|
|
154
|
+
end
|
|
155
|
+
puts
|
|
156
|
+
|
|
157
|
+
# ========== Geohash-36 Conversion ==========
|
|
158
|
+
puts "Geohash-36 (GH36)"
|
|
159
|
+
gh36_coord = GH36.new(lla_coord)
|
|
160
|
+
puts " GH36: #{gh36_coord.to_s}"
|
|
161
|
+
puts " Precision: #{gh36_coord.precision} chars"
|
|
162
|
+
puts " Precision in meters: lat=#{gh36_coord.precision_in_meters[:lat].round(3)}m, lng=#{gh36_coord.precision_in_meters[:lng].round(3)}m"
|
|
163
|
+
|
|
164
|
+
# URL slug
|
|
165
|
+
puts " URL slug: #{gh36_coord.to_slug}"
|
|
166
|
+
|
|
167
|
+
# Reduced precision
|
|
168
|
+
gh36_short = GH36.new(lla_coord, precision: 5)
|
|
169
|
+
puts " 5-char precision: #{gh36_short.to_s}"
|
|
170
|
+
|
|
171
|
+
# Round-trip test
|
|
172
|
+
lla_from_gh36 = gh36_coord.to_lla
|
|
173
|
+
gh36_lat_error = (lla_from_gh36.lat - seattle_lat).abs
|
|
174
|
+
gh36_lng_error = (lla_from_gh36.lng - seattle_lng).abs
|
|
175
|
+
puts " Round-trip error: Lat=#{gh36_lat_error.round(8)}, Lng=#{gh36_lng_error.round(8)}"
|
|
176
|
+
|
|
177
|
+
# Neighbors
|
|
178
|
+
neighbors = gh36_coord.neighbors
|
|
179
|
+
puts " Neighbors:"
|
|
180
|
+
neighbors.each do |dir, neighbor|
|
|
181
|
+
puts " #{dir}: #{neighbor.to_s}"
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Area (bounding rectangle)
|
|
185
|
+
area = gh36_coord.to_area
|
|
186
|
+
puts " Cell area: NW=(#{area.nw.lat.round(6)}, #{area.nw.lng.round(6)}) SE=(#{area.se.lat.round(6)}, #{area.se.lng.round(6)})"
|
|
187
|
+
puts " Midpoint inside cell? #{area.includes?(gh36_coord.to_lla)}"
|
|
188
|
+
puts
|
|
189
|
+
|
|
190
|
+
# ========== British National Grid Conversion ==========
|
|
191
|
+
puts "British National Grid (BNG)"
|
|
192
|
+
london_lla = LLA.new(lat: 51.5007, lng: -0.1246, alt: 11.0)
|
|
193
|
+
bng_coord = BNG.from_lla(london_lla)
|
|
194
|
+
puts " BNG (London): #{bng_coord.easting.round(3)}E #{bng_coord.northing.round(3)}N"
|
|
195
|
+
puts " Grid reference: #{bng_coord.to_grid_reference(6)}"
|
|
196
|
+
puts " Grid reference (low precision): #{bng_coord.to_grid_reference(0)}"
|
|
197
|
+
puts
|
|
198
|
+
|
|
199
|
+
# ========== Geoid Height Demonstration ==========
|
|
200
|
+
puts "Geoid Height and Vertical Datum Support"
|
|
201
|
+
geoid = GeoidHeight.new(geoid_model: 'EGM2008')
|
|
202
|
+
|
|
203
|
+
seattle_geoid_height = geoid.geoid_height_at(seattle_lat, seattle_lng)
|
|
204
|
+
orthometric_height = geoid.ellipsoidal_to_orthometric(seattle_lat, seattle_lng, seattle_alt)
|
|
205
|
+
|
|
206
|
+
puts " Geoid height (EGM2008): #{seattle_geoid_height.round(3)}m"
|
|
207
|
+
puts " Ellipsoidal height (HAE): #{seattle_alt}m"
|
|
208
|
+
puts " Orthometric height (MSL): #{orthometric_height.round(3)}m"
|
|
209
|
+
|
|
210
|
+
# Test different geoid models
|
|
211
|
+
egm96_geoid = GeoidHeight.new(geoid_model: 'EGM96')
|
|
212
|
+
egm96_height = egm96_geoid.geoid_height_at(seattle_lat, seattle_lng)
|
|
213
|
+
puts " Geoid height (EGM96): #{egm96_height.round(3)}m"
|
|
214
|
+
puts " Model difference: #{(seattle_geoid_height - egm96_height).abs.round(3)}m"
|
|
215
|
+
|
|
216
|
+
# Test vertical datum conversion
|
|
217
|
+
navd88_height = geoid.convert_vertical_datum(seattle_lat, seattle_lng, seattle_alt, 'HAE', 'NAVD88')
|
|
218
|
+
puts " Height in NAVD88: #{navd88_height.round(3)}m"
|
|
219
|
+
puts
|
|
220
|
+
|
|
221
|
+
# ========== Cross-System Conversions ==========
|
|
222
|
+
puts "CROSS-SYSTEM CONVERSION CHAINS"
|
|
223
|
+
puts "-" * 50
|
|
224
|
+
|
|
225
|
+
# Chain 1: 3D systems that preserve altitude
|
|
226
|
+
puts "Chain 1 (3D systems): LLA -> ECEF -> UTM -> ENU -> NED -> LLA"
|
|
227
|
+
c1_ecef = lla_coord.to_ecef
|
|
228
|
+
c1_utm = c1_ecef.to_utm
|
|
229
|
+
c1_enu = c1_utm.to_enu(reference_lla)
|
|
230
|
+
c1_ned = c1_enu.to_ned
|
|
231
|
+
c1_final = c1_ned.to_lla(reference_lla)
|
|
232
|
+
|
|
233
|
+
c1_lat_err = (c1_final.lat - seattle_lat).abs
|
|
234
|
+
c1_lng_err = (c1_final.lng - seattle_lng).abs
|
|
235
|
+
c1_alt_err = (c1_final.alt - seattle_alt).abs
|
|
236
|
+
|
|
237
|
+
puts " Final: #{c1_final.lat.round(8)}°, #{c1_final.lng.round(8)}°, #{c1_final.alt.round(3)}m"
|
|
238
|
+
puts " Error: Lat=#{c1_lat_err.round(10)}°, Lng=#{c1_lng_err.round(10)}°, Alt=#{c1_alt_err.round(6)}m"
|
|
239
|
+
puts
|
|
240
|
+
|
|
241
|
+
# Chain 2: 2D systems (use altitude 0.0 since these systems don't carry altitude)
|
|
242
|
+
lla_2d = LLA.new(lat: seattle_lat, lng: seattle_lng, alt: 0.0)
|
|
243
|
+
|
|
244
|
+
puts "Chain 2 (2D systems): LLA -> MGRS -> USNG -> Web Mercator -> LLA"
|
|
245
|
+
puts " Starting with altitude 0.0 (2D systems do not carry altitude)"
|
|
246
|
+
c2_mgrs = MGRS.from_lla(lla_2d)
|
|
247
|
+
c2_usng = USNG.from_mgrs(c2_mgrs)
|
|
248
|
+
c2_web_merc = WebMerc.from_lla(c2_usng.to_lla)
|
|
249
|
+
c2_final = c2_web_merc.to_lla
|
|
250
|
+
|
|
251
|
+
c2_lat_err = (c2_final.lat - seattle_lat).abs
|
|
252
|
+
c2_lng_err = (c2_final.lng - seattle_lng).abs
|
|
253
|
+
|
|
254
|
+
puts " Final: #{c2_final.lat.round(8)}°, #{c2_final.lng.round(8)}°, #{c2_final.alt.round(3)}m"
|
|
255
|
+
puts " Error: Lat=#{c2_lat_err.round(8)}° (~#{(c2_lat_err * 111320).round(2)}m), Lng=#{c2_lng_err.round(8)}° (~#{(c2_lng_err * 111320 * Math.cos(seattle_lat * Math::PI / 180)).round(2)}m), Alt=#{c2_final.alt.round(3)}m"
|
|
256
|
+
puts " Lat/Lng error is from MGRS 1-meter grid precision truncation."
|
|
257
|
+
puts
|
|
258
|
+
|
|
259
|
+
# ========== Rectangle Area ==========
|
|
260
|
+
puts "RECTANGLE AREA"
|
|
261
|
+
puts "-" * 50
|
|
262
|
+
|
|
263
|
+
nw = LLA.new(lat: 47.65, lng: -122.40)
|
|
264
|
+
se = LLA.new(lat: 47.60, lng: -122.30)
|
|
265
|
+
rect = Areas::Rectangle.new(nw: nw, se: se)
|
|
266
|
+
puts " NW: (#{rect.nw.lat}, #{rect.nw.lng})"
|
|
267
|
+
puts " SE: (#{rect.se.lat}, #{rect.se.lng})"
|
|
268
|
+
puts " NE: (#{rect.ne.lat}, #{rect.ne.lng})"
|
|
269
|
+
puts " SW: (#{rect.sw.lat}, #{rect.sw.lng})"
|
|
270
|
+
puts " Centroid: (#{rect.centroid.lat}, #{rect.centroid.lng})"
|
|
271
|
+
puts " Space Needle inside? #{rect.includes?(lla_coord)}"
|
|
272
|
+
puts " London inside? #{rect.includes?(london_lla)}"
|
|
273
|
+
|
|
274
|
+
# Rectangle from non-LLA coordinates
|
|
275
|
+
nw_wm = WebMerc.from_lla(nw)
|
|
276
|
+
se_wm = WebMerc.from_lla(se)
|
|
277
|
+
rect_wm = Areas::Rectangle.new(nw: nw_wm, se: se_wm)
|
|
278
|
+
puts " From WebMercator: NW=(#{rect_wm.nw.lat.round(4)}, #{rect_wm.nw.lng.round(4)})"
|
|
279
|
+
puts
|
|
280
|
+
|
|
281
|
+
# ========== Summary ==========
|
|
282
|
+
puts "COORDINATE SYSTEM SUMMARY"
|
|
283
|
+
puts "-" * 50
|
|
284
|
+
puts "LLA (Latitude, Longitude, Altitude)"
|
|
285
|
+
puts "ECEF (Earth-Centered, Earth-Fixed)"
|
|
286
|
+
puts "UTM (Universal Transverse Mercator)"
|
|
287
|
+
puts "ENU (East, North, Up)"
|
|
288
|
+
puts "NED (North, East, Down)"
|
|
289
|
+
puts "MGRS (Military Grid Reference System)"
|
|
290
|
+
puts "USNG (US National Grid)"
|
|
291
|
+
puts "Web Mercator (EPSG:3857)"
|
|
292
|
+
puts "UPS (Universal Polar Stereographic)"
|
|
293
|
+
puts "State Plane Coordinates"
|
|
294
|
+
puts "British National Grid (BNG)"
|
|
295
|
+
puts "Geohash-36 (GH36)"
|
|
296
|
+
puts "Geoid Height Support"
|
|
297
|
+
puts
|
|
298
|
+
puts "Areas: Circle, Polygon, Rectangle"
|
|
299
|
+
puts
|
|
300
|
+
puts "All coordinate systems support complete bidirectional conversions!"
|
|
301
|
+
puts "Total coordinate systems implemented: 13"
|
|
302
|
+
puts "Total conversion paths available: 156 (13 x 12)"
|
|
303
|
+
puts
|
|
304
|
+
puts "=" * 80
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
# Run the demonstration if this file is executed directly
|
|
308
|
+
if __FILE__ == $0
|
|
309
|
+
demo_coordinate_systems
|
|
310
|
+
end
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
# Demonstration of distance calculations and the Distance class
|
|
4
|
+
# Shows great-circle distances, straight-line distances, unit conversions,
|
|
5
|
+
# and arithmetic with the Distance class.
|
|
6
|
+
|
|
7
|
+
require_relative "../lib/geodetic"
|
|
8
|
+
|
|
9
|
+
Distance = Geodetic::Distance
|
|
10
|
+
|
|
11
|
+
# ── Notable locations ────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
seattle = GCS::LLA.new(lat: 47.6205, lng: -122.3493, alt: 0.0)
|
|
14
|
+
portland = GCS::LLA.new(lat: 45.5152, lng: -122.6784, alt: 0.0)
|
|
15
|
+
sf = GCS::LLA.new(lat: 37.7749, lng: -122.4194, alt: 0.0)
|
|
16
|
+
nyc = GCS::LLA.new(lat: 40.7128, lng: -74.0060, alt: 0.0)
|
|
17
|
+
london = GCS::LLA.new(lat: 51.5074, lng: -0.1278, alt: 0.0)
|
|
18
|
+
|
|
19
|
+
puts "=== Distance Calculations Demo ==="
|
|
20
|
+
puts
|
|
21
|
+
|
|
22
|
+
# ── Basic distance_to ────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
puts "--- Great-Circle Distances (Vincenty) ---"
|
|
25
|
+
puts
|
|
26
|
+
|
|
27
|
+
d = seattle.distance_to(portland)
|
|
28
|
+
puts "Seattle -> Portland:"
|
|
29
|
+
puts " #{d.meters.round(2)} meters"
|
|
30
|
+
puts " #{d.to_km.to_f.round(2)} km"
|
|
31
|
+
puts " #{d.to_mi.to_f.round(2)} miles"
|
|
32
|
+
puts " #{d.to_nmi.to_f.round(2)} nautical miles"
|
|
33
|
+
puts
|
|
34
|
+
|
|
35
|
+
d = seattle.distance_to(nyc)
|
|
36
|
+
puts "Seattle -> New York: #{d.to_mi.to_s}"
|
|
37
|
+
puts
|
|
38
|
+
|
|
39
|
+
d = seattle.distance_to(london)
|
|
40
|
+
puts "Seattle -> London: #{d.to_km.to_s}"
|
|
41
|
+
puts
|
|
42
|
+
|
|
43
|
+
# ── Radial distances (one-to-many) ──────────────────────────────
|
|
44
|
+
|
|
45
|
+
puts "--- Radial Distances from Seattle ---"
|
|
46
|
+
puts
|
|
47
|
+
|
|
48
|
+
distances = seattle.distance_to(portland, sf, nyc, london)
|
|
49
|
+
labels = %w[Portland SF NYC London]
|
|
50
|
+
|
|
51
|
+
distances.each_with_index do |dist, i|
|
|
52
|
+
puts " -> %-10s %10.1f km (%7.1f mi)" % [labels[i], dist.to_km.to_f, dist.to_mi.to_f]
|
|
53
|
+
end
|
|
54
|
+
puts
|
|
55
|
+
|
|
56
|
+
# ── Chain distances (consecutive pairs) ─────────────────────────
|
|
57
|
+
|
|
58
|
+
puts "--- Chain Distances (consecutive legs) ---"
|
|
59
|
+
puts "Route: Seattle -> Portland -> SF -> NYC"
|
|
60
|
+
puts
|
|
61
|
+
|
|
62
|
+
legs = GCS.distance_between(seattle, portland, sf, nyc)
|
|
63
|
+
leg_labels = ["Seattle -> Portland", "Portland -> SF", "SF -> NYC"]
|
|
64
|
+
|
|
65
|
+
total = Distance.new(0)
|
|
66
|
+
legs.each_with_index do |leg, i|
|
|
67
|
+
total = total + leg
|
|
68
|
+
puts " %-20s %8.1f km" % [leg_labels[i], leg.to_km.to_f]
|
|
69
|
+
end
|
|
70
|
+
puts " %-20s %8.1f km" % ["Total", total.to_km.to_f]
|
|
71
|
+
puts
|
|
72
|
+
|
|
73
|
+
# ── Straight-line vs great-circle ───────────────────────────────
|
|
74
|
+
|
|
75
|
+
puts "--- Straight-Line vs Great-Circle ---"
|
|
76
|
+
puts
|
|
77
|
+
|
|
78
|
+
gc = seattle.distance_to(london)
|
|
79
|
+
sl = seattle.straight_line_distance_to(london)
|
|
80
|
+
|
|
81
|
+
puts "Seattle -> London:"
|
|
82
|
+
puts " Great-circle: #{gc.to_km.to_f.round(1)} km"
|
|
83
|
+
puts " Straight-line: #{sl.to_km.to_f.round(1)} km (through the Earth)"
|
|
84
|
+
puts " Difference: #{(gc - sl).to_km.to_f.round(1)} km"
|
|
85
|
+
puts
|
|
86
|
+
|
|
87
|
+
# ── Cross-system distances ──────────────────────────────────────
|
|
88
|
+
|
|
89
|
+
puts "--- Cross-System Distances ---"
|
|
90
|
+
puts
|
|
91
|
+
|
|
92
|
+
utm_seattle = seattle.to_utm
|
|
93
|
+
mgrs_portland = GCS::MGRS.from_lla(portland)
|
|
94
|
+
|
|
95
|
+
d = utm_seattle.distance_to(mgrs_portland)
|
|
96
|
+
puts "UTM(Seattle) -> MGRS(Portland): #{d.to_km.to_f.round(2)} km"
|
|
97
|
+
|
|
98
|
+
wm_sf = GCS::WebMercator.from_lla(sf)
|
|
99
|
+
d = wm_sf.distance_to(utm_seattle)
|
|
100
|
+
puts "WebMercator(SF) -> UTM(Seattle): #{d.to_mi.to_f.round(2)} mi"
|
|
101
|
+
|
|
102
|
+
gh36_nyc = GCS::GH36.new(nyc)
|
|
103
|
+
d = gh36_nyc.distance_to(utm_seattle)
|
|
104
|
+
puts "GH36(NYC) -> UTM(Seattle): #{d.to_km.to_f.round(2)} km"
|
|
105
|
+
puts
|
|
106
|
+
|
|
107
|
+
# ── Distance class construction ─────────────────────────────────
|
|
108
|
+
|
|
109
|
+
puts "=== Distance Class Features ==="
|
|
110
|
+
puts
|
|
111
|
+
|
|
112
|
+
puts "--- Construction from different units ---"
|
|
113
|
+
puts
|
|
114
|
+
|
|
115
|
+
examples = [
|
|
116
|
+
Distance.new(1000),
|
|
117
|
+
Distance.km(5),
|
|
118
|
+
Distance.mi(3),
|
|
119
|
+
Distance.ft(5280),
|
|
120
|
+
Distance.nmi(1),
|
|
121
|
+
]
|
|
122
|
+
|
|
123
|
+
examples.each do |d|
|
|
124
|
+
puts " %-40s => %10.3f meters" % [d.inspect, d.meters]
|
|
125
|
+
end
|
|
126
|
+
puts
|
|
127
|
+
|
|
128
|
+
# ── Unit conversions ────────────────────────────────────────────
|
|
129
|
+
|
|
130
|
+
puts "--- Unit Conversions ---"
|
|
131
|
+
puts
|
|
132
|
+
|
|
133
|
+
d = Distance.new(1609.344)
|
|
134
|
+
puts "1609.344 meters is:"
|
|
135
|
+
puts " #{d.to_km.to_f.round(6)} km"
|
|
136
|
+
puts " #{d.to_mi.to_f.round(6)} miles"
|
|
137
|
+
puts " #{d.to_ft.to_f.round(2)} feet"
|
|
138
|
+
puts " #{d.to_yd.to_f.round(2)} yards"
|
|
139
|
+
puts " #{d.to_nmi.to_f.round(6)} nautical miles"
|
|
140
|
+
puts " #{d.to_cm.to_f.round(2)} cm"
|
|
141
|
+
puts " #{d.to_mm.to_f.round(2)} mm"
|
|
142
|
+
puts
|
|
143
|
+
|
|
144
|
+
# ── Display formatting ──────────────────────────────────────────
|
|
145
|
+
|
|
146
|
+
puts "--- Display Formatting ---"
|
|
147
|
+
puts
|
|
148
|
+
|
|
149
|
+
d = Distance.km(42.195) # marathon distance
|
|
150
|
+
puts <<~HEREDOC
|
|
151
|
+
Marathon distance:
|
|
152
|
+
to_s: #{d.to_s}
|
|
153
|
+
to_s(3): #{d.to_s(3)}
|
|
154
|
+
to_s(1): #{d.to_s(1)}
|
|
155
|
+
to_s(0): #{d.to_s(0)}
|
|
156
|
+
to_f: #{d.to_f}
|
|
157
|
+
to_i: #{d.to_i}
|
|
158
|
+
inspect: #{d.inspect}
|
|
159
|
+
in miles: #{d.to_mi.to_s}
|
|
160
|
+
in feet: #{d.to_ft.to_s(0)}
|
|
161
|
+
HEREDOC
|
|
162
|
+
|
|
163
|
+
# ── Arithmetic ──────────────────────────────────────────────────
|
|
164
|
+
|
|
165
|
+
puts "--- Arithmetic ---"
|
|
166
|
+
puts
|
|
167
|
+
|
|
168
|
+
d1 = Distance.km(5)
|
|
169
|
+
d2 = Distance.mi(3)
|
|
170
|
+
|
|
171
|
+
puts "d1 = #{d1.inspect}"
|
|
172
|
+
puts "d2 = #{d2.inspect}"
|
|
173
|
+
puts
|
|
174
|
+
|
|
175
|
+
sum = d1 + d2
|
|
176
|
+
puts "d1 + d2 = #{sum.to_km.to_f.round(3)} km (#{sum.meters.round(3)} m)"
|
|
177
|
+
|
|
178
|
+
diff = d1 - d2
|
|
179
|
+
puts "d1 - d2 = #{diff.to_km.to_f.round(3)} km (#{diff.meters.round(3)} m)"
|
|
180
|
+
|
|
181
|
+
scaled = d1 * 3
|
|
182
|
+
puts "d1 * 3 = #{scaled.to_km.to_f.round(3)} km"
|
|
183
|
+
|
|
184
|
+
halved = d2 / 2
|
|
185
|
+
puts "d2 / 2 = #{halved.to_mi.to_f.round(3)} mi"
|
|
186
|
+
|
|
187
|
+
ratio = d1 / d2
|
|
188
|
+
puts "d1 / d2 = #{ratio.round(6)} (ratio)"
|
|
189
|
+
puts
|
|
190
|
+
|
|
191
|
+
# Numeric constants use the display unit
|
|
192
|
+
puts "--- Numeric Constants in Display Unit ---"
|
|
193
|
+
puts
|
|
194
|
+
|
|
195
|
+
d = Distance.new(5000).to_km # 5 km
|
|
196
|
+
puts "d = #{d.inspect}"
|
|
197
|
+
puts "d + 3 (adds 3 km) = #{(d + 3).to_km.to_f} km"
|
|
198
|
+
puts "d - 2 (subs 2 km) = #{(d - 2).to_km.to_f} km"
|
|
199
|
+
puts "d > 4 (4 km)? #{d > 4}"
|
|
200
|
+
puts "d < 4 (4 km)? #{d < 4}"
|
|
201
|
+
puts
|
|
202
|
+
|
|
203
|
+
# ── Comparison ──────────────────────────────────────────────────
|
|
204
|
+
|
|
205
|
+
puts "--- Comparison (unit-independent) ---"
|
|
206
|
+
puts
|
|
207
|
+
|
|
208
|
+
a = Distance.km(1)
|
|
209
|
+
b = Distance.new(1000)
|
|
210
|
+
c = Distance.mi(1)
|
|
211
|
+
|
|
212
|
+
puts "1 km == 1000 m? #{a == b}"
|
|
213
|
+
puts "1 mi > 1 km? #{c > a}"
|
|
214
|
+
puts "1 km < 1 mi? #{a < c}"
|
|
215
|
+
puts
|
|
216
|
+
|
|
217
|
+
# ── Coerce (Numeric * Distance) ────────────────────────────────
|
|
218
|
+
|
|
219
|
+
d = Distance.km(10)
|
|
220
|
+
result = 3 * d
|
|
221
|
+
puts "3 * #{d.to_s} = #{result} (via coerce)"
|
|
222
|
+
puts
|
|
223
|
+
|
|
224
|
+
puts "=== Done ==="
|