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.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/.envrc +1 -0
  3. data/.github/workflows/deploy-github-pages.yml +52 -0
  4. data/CHANGELOG.md +15 -0
  5. data/COMMITS.md +196 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +471 -0
  8. data/Rakefile +8 -0
  9. data/docs/coordinate-systems/bng.md +60 -0
  10. data/docs/coordinate-systems/ecef.md +215 -0
  11. data/docs/coordinate-systems/enu.md +77 -0
  12. data/docs/coordinate-systems/gh36.md +192 -0
  13. data/docs/coordinate-systems/index.md +93 -0
  14. data/docs/coordinate-systems/lla.md +304 -0
  15. data/docs/coordinate-systems/mgrs.md +81 -0
  16. data/docs/coordinate-systems/ned.md +83 -0
  17. data/docs/coordinate-systems/state-plane.md +60 -0
  18. data/docs/coordinate-systems/ups.md +53 -0
  19. data/docs/coordinate-systems/usng.md +74 -0
  20. data/docs/coordinate-systems/utm.md +257 -0
  21. data/docs/coordinate-systems/web-mercator.md +67 -0
  22. data/docs/getting-started/installation.md +65 -0
  23. data/docs/getting-started/quick-start.md +175 -0
  24. data/docs/index.md +58 -0
  25. data/docs/reference/areas.md +195 -0
  26. data/docs/reference/conversions.md +351 -0
  27. data/docs/reference/datums.md +134 -0
  28. data/docs/reference/geoid-height.md +182 -0
  29. data/docs/reference/serialization.md +252 -0
  30. data/examples/01_basic_conversions.rb +187 -0
  31. data/examples/02_all_coordinate_systems.rb +310 -0
  32. data/examples/03_distance_calculations.rb +224 -0
  33. data/examples/04_bearing_calculations.rb +236 -0
  34. data/lib/geodetic/areas/circle.rb +29 -0
  35. data/lib/geodetic/areas/polygon.rb +57 -0
  36. data/lib/geodetic/areas/rectangle.rb +55 -0
  37. data/lib/geodetic/areas.rb +5 -0
  38. data/lib/geodetic/bearing.rb +94 -0
  39. data/lib/geodetic/coordinates/bng.rb +366 -0
  40. data/lib/geodetic/coordinates/ecef.rb +229 -0
  41. data/lib/geodetic/coordinates/enu.rb +244 -0
  42. data/lib/geodetic/coordinates/gh36.rb +384 -0
  43. data/lib/geodetic/coordinates/lla.rb +268 -0
  44. data/lib/geodetic/coordinates/mgrs.rb +317 -0
  45. data/lib/geodetic/coordinates/ned.rb +246 -0
  46. data/lib/geodetic/coordinates/state_plane.rb +451 -0
  47. data/lib/geodetic/coordinates/ups.rb +325 -0
  48. data/lib/geodetic/coordinates/usng.rb +274 -0
  49. data/lib/geodetic/coordinates/utm.rb +261 -0
  50. data/lib/geodetic/coordinates/web_mercator.rb +242 -0
  51. data/lib/geodetic/coordinates.rb +260 -0
  52. data/lib/geodetic/datum.rb +62 -0
  53. data/lib/geodetic/distance.rb +146 -0
  54. data/lib/geodetic/geoid_height.rb +299 -0
  55. data/lib/geodetic/version.rb +5 -0
  56. data/lib/geodetic.rb +13 -0
  57. data/mkdocs.yml +140 -0
  58. data/sig/geodetic.rbs +4 -0
  59. metadata +104 -0
@@ -0,0 +1,257 @@
1
+ # Geodetic::Coordinates::UTM
2
+
3
+ Universal Transverse Mercator -- a projected coordinate system that divides the Earth into 60 longitudinal zones (each 6 degrees wide) and two hemispheres (North and South). Positions within each zone are expressed as easting and northing distances in meters from the zone's origin. UTM is widely used in military, engineering, and surveying applications because it provides a flat, metric grid that minimizes distortion within each zone.
4
+
5
+ ## Constructor
6
+
7
+ ```ruby
8
+ Geodetic::Coordinates::UTM.new(easting: 0.0, northing: 0.0, altitude: 0.0, zone: 1, hemisphere: 'N')
9
+ ```
10
+
11
+ | Parameter | Type | Default | Description |
12
+ |--------------|---------|---------|---------------------------------------------|
13
+ | `easting` | Float | `0.0` | Easting in meters (must be >= 0) |
14
+ | `northing` | Float | `0.0` | Northing in meters (must be >= 0) |
15
+ | `altitude` | Float | `0.0` | Altitude in meters above the ellipsoid |
16
+ | `zone` | Integer | `1` | UTM zone number (1..60) |
17
+ | `hemisphere` | String | `'N'` | Hemisphere: `'N'` (North) or `'S'` (South) |
18
+
19
+ Numeric values are coerced via `.to_f` / `.to_i`. The hemisphere string is uppercased automatically.
20
+
21
+ ### Validation
22
+
23
+ The constructor raises `ArgumentError` if:
24
+
25
+ - `zone` is outside the range `1..60`
26
+ - `hemisphere` is not `'N'` or `'S'`
27
+ - `easting` is negative
28
+ - `northing` is negative
29
+
30
+ ## Attributes
31
+
32
+ | Attribute | Alias | Access |
33
+ |--------------|-------|------------|
34
+ | `easting` | `x` | read/write |
35
+ | `northing` | `y` | read/write |
36
+ | `altitude` | `z` | read/write |
37
+ | `zone` | -- | read/write |
38
+ | `hemisphere` | -- | read/write |
39
+
40
+ Setters validate and coerce types. `easting` and `northing` must be non-negative (`ArgumentError`). `zone` must be `1..60`. `hemisphere` must be `'N'` or `'S'` (auto-upcased). `altitude` has no range constraint.
41
+
42
+ ## Conversions
43
+
44
+ All conversion methods accept an optional `datum` parameter (defaults to `Geodetic::WGS84`).
45
+
46
+ ### to_lla(datum = WGS84)
47
+
48
+ Converts to geodetic Latitude, Longitude, Altitude coordinates. Uses a series expansion for the inverse UTM projection.
49
+
50
+ ```ruby
51
+ utm = Geodetic::Coordinates::UTM.new(easting: 580000.0, northing: 4510000.0, zone: 18, hemisphere: 'N')
52
+ lla = utm.to_lla
53
+ # => Geodetic::Coordinates::LLA
54
+ ```
55
+
56
+ ### UTM.from_lla(lla, datum = WGS84)
57
+
58
+ Creates a UTM from an LLA instance. The zone and hemisphere are computed automatically. Raises `ArgumentError` if the argument is not an `LLA`.
59
+
60
+ ```ruby
61
+ utm = Geodetic::Coordinates::UTM.from_lla(lla)
62
+ ```
63
+
64
+ ### to_ecef(datum = WGS84)
65
+
66
+ Converts to Earth-Centered, Earth-Fixed Cartesian coordinates. Internally converts to LLA first, then to ECEF.
67
+
68
+ ```ruby
69
+ ecef = utm.to_ecef
70
+ # => Geodetic::Coordinates::ECEF
71
+ ```
72
+
73
+ ### UTM.from_ecef(ecef, datum = WGS84)
74
+
75
+ Creates a UTM from an ECEF instance. Raises `ArgumentError` if the argument is not an `ECEF`.
76
+
77
+ ### to_enu(reference_lla, datum = WGS84)
78
+
79
+ Converts to East-North-Up local tangent plane coordinates relative to a reference LLA position.
80
+
81
+ ```ruby
82
+ origin = Geodetic::Coordinates::LLA.new(lat: 40.7128, lng: -74.0060, alt: 10.0)
83
+ enu = utm.to_enu(origin)
84
+ # => Geodetic::Coordinates::ENU
85
+ ```
86
+
87
+ Raises `ArgumentError` if `reference_lla` is not an `LLA`.
88
+
89
+ ### UTM.from_enu(enu, reference_lla, datum = WGS84)
90
+
91
+ Creates a UTM from an ENU instance and a reference LLA origin. Raises `ArgumentError` if the arguments are not the expected types.
92
+
93
+ ### to_ned(reference_lla, datum = WGS84)
94
+
95
+ Converts to North-East-Down local tangent plane coordinates relative to a reference LLA position.
96
+
97
+ ```ruby
98
+ ned = utm.to_ned(origin)
99
+ # => Geodetic::Coordinates::NED
100
+ ```
101
+
102
+ Raises `ArgumentError` if `reference_lla` is not an `LLA`.
103
+
104
+ ### UTM.from_ned(ned, reference_lla, datum = WGS84)
105
+
106
+ Creates a UTM from a NED instance and a reference LLA origin. Raises `ArgumentError` if the arguments are not the expected types.
107
+
108
+ ## Serialization
109
+
110
+ ### to_s
111
+
112
+ Returns a comma-separated string of `easting, northing, altitude, zone, hemisphere`.
113
+
114
+ ```ruby
115
+ utm = Geodetic::Coordinates::UTM.new(easting: 580000.0, northing: 4510000.0, altitude: 10.0, zone: 18, hemisphere: 'N')
116
+ utm.to_s
117
+ # => "580000.0, 4510000.0, 10.0, 18, N"
118
+ ```
119
+
120
+ ### to_a
121
+
122
+ Returns a five-element array `[easting, northing, altitude, zone, hemisphere]`.
123
+
124
+ ```ruby
125
+ utm.to_a
126
+ # => [580000.0, 4510000.0, 10.0, 18, "N"]
127
+ ```
128
+
129
+ ### UTM.from_string(string)
130
+
131
+ Parses a comma-separated string into a UTM.
132
+
133
+ ```ruby
134
+ utm = Geodetic::Coordinates::UTM.from_string("580000.0, 4510000.0, 10.0, 18, N")
135
+ ```
136
+
137
+ ### UTM.from_array(array)
138
+
139
+ Creates a UTM from a five-element array `[easting, northing, altitude, zone, hemisphere]`.
140
+
141
+ ```ruby
142
+ utm = Geodetic::Coordinates::UTM.from_array([580000.0, 4510000.0, 10.0, 18, "N"])
143
+ ```
144
+
145
+ ## Additional Methods
146
+
147
+ ### ==(other)
148
+
149
+ Compares two UTM instances for approximate equality. Returns `true` if:
150
+
151
+ - `|easting difference| <= 1e-6`
152
+ - `|northing difference| <= 1e-6`
153
+ - `|altitude difference| <= 1e-6`
154
+ - `zone` values are equal
155
+ - `hemisphere` values are equal
156
+
157
+ Returns `false` if `other` is not a `UTM`.
158
+
159
+ ```ruby
160
+ a = Geodetic::Coordinates::UTM.new(easting: 580000.0, northing: 4510000.0, zone: 18, hemisphere: 'N')
161
+ b = Geodetic::Coordinates::UTM.new(easting: 580000.0, northing: 4510000.0, zone: 18, hemisphere: 'N')
162
+ a == b
163
+ # => true
164
+ ```
165
+
166
+ ### distance_to(other, *others)
167
+
168
+ Computes the Vincenty great-circle distance to one or more other coordinates. Works across UTM zones and across different coordinate types (coordinates are converted to LLA internally). Returns a `Distance` for a single target or an Array of `Distance` objects for multiple targets (radial distances from the receiver).
169
+
170
+ ```ruby
171
+ a = Geodetic::Coordinates::UTM.new(easting: 580000.0, northing: 4510000.0, altitude: 10.0, zone: 18, hemisphere: 'N')
172
+ b = Geodetic::Coordinates::UTM.new(easting: 580100.0, northing: 4510200.0, altitude: 15.0, zone: 18, hemisphere: 'N')
173
+ a.distance_to(b)
174
+ # => Distance (meters, great-circle distance)
175
+ ```
176
+
177
+ ### straight_line_distance_to(other, *others)
178
+
179
+ Computes the Euclidean (straight-line) distance between two points in ECEF space. Accepts any coordinate type. Returns a `Distance` for a single target or an Array of `Distance` objects for multiple targets.
180
+
181
+ ```ruby
182
+ a.straight_line_distance_to(b)
183
+ # => Distance (meters)
184
+ ```
185
+
186
+ ### same_zone?(other)
187
+
188
+ Returns `true` if both UTM instances share the same zone number and hemisphere.
189
+
190
+ ```ruby
191
+ a = Geodetic::Coordinates::UTM.new(easting: 580000.0, northing: 4510000.0, zone: 18, hemisphere: 'N')
192
+ b = Geodetic::Coordinates::UTM.new(easting: 590000.0, northing: 4520000.0, zone: 18, hemisphere: 'N')
193
+ a.same_zone?(b)
194
+ # => true
195
+ ```
196
+
197
+ Raises `ArgumentError` if `other` is not a `UTM`.
198
+
199
+ ### central_meridian
200
+
201
+ Returns the central meridian longitude (in degrees) for the UTM zone.
202
+
203
+ ```ruby
204
+ utm = Geodetic::Coordinates::UTM.new(zone: 18, hemisphere: 'N')
205
+ utm.central_meridian
206
+ # => -75
207
+ ```
208
+
209
+ The formula is: `(zone - 1) * 6 - 180 + 3`.
210
+
211
+ ## Code Examples
212
+
213
+ ### Round-trip conversion
214
+
215
+ ```ruby
216
+ require 'geodetic'
217
+
218
+ # Start with an LLA and convert to UTM
219
+ lla = Geodetic::Coordinates::LLA.new(lat: 40.7128, lng: -74.0060, alt: 10.0)
220
+ utm = lla.to_utm
221
+ puts utm.to_s
222
+ # zone and hemisphere are determined automatically
223
+
224
+ # Convert back to LLA
225
+ lla_roundtrip = utm.to_lla
226
+ ```
227
+
228
+ ### Distance calculations
229
+
230
+ ```ruby
231
+ point_a = Geodetic::Coordinates::UTM.new(
232
+ easting: 583960.0, northing: 4507523.0,
233
+ altitude: 10.0, zone: 18, hemisphere: 'N'
234
+ )
235
+ point_b = Geodetic::Coordinates::UTM.new(
236
+ easting: 584060.0, northing: 4507623.0,
237
+ altitude: 15.0, zone: 18, hemisphere: 'N'
238
+ )
239
+
240
+ # Great-circle distance (works across zones and coordinate types)
241
+ puts "Distance: #{point_a.distance_to(point_b).meters} m"
242
+
243
+ # Straight-line (Euclidean) distance
244
+ puts "Straight: #{point_a.straight_line_distance_to(point_b).meters} m"
245
+
246
+ puts "Same zone? #{point_a.same_zone?(point_b)}"
247
+ ```
248
+
249
+ ### Working with zones
250
+
251
+ ```ruby
252
+ # Check which zone a location falls in
253
+ lla = Geodetic::Coordinates::LLA.new(lat: 48.8566, lng: 2.3522, alt: 35.0) # Paris
254
+ utm = lla.to_utm
255
+ puts "Zone: #{utm.zone}#{utm.hemisphere}" # => "31N"
256
+ puts "Central meridian: #{utm.central_meridian}" # => 3
257
+ ```
@@ -0,0 +1,67 @@
1
+ # Geodetic::Coordinates::WebMercator
2
+
3
+ ## Web Mercator (EPSG:3857)
4
+
5
+ Web Mercator is the de facto standard projection used by major web mapping platforms including Google Maps, OpenStreetMap, and Bing Maps. It projects the Earth onto a square grid using a spherical Mercator projection, making it well-suited for tiled map rendering.
6
+
7
+ ## Constructor
8
+
9
+ ```ruby
10
+ point = Geodetic::Coordinates::WebMercator.new(x: 0.0, y: 0.0)
11
+ ```
12
+
13
+ Parameters `x` and `y` are specified in **meters** from the projection origin (the intersection of the Equator and the Prime Meridian).
14
+
15
+ ## Constants
16
+
17
+ | Constant | Description |
18
+ |---|---|
19
+ | `EARTH_RADIUS` | Radius of the Earth used for projection calculations |
20
+ | `ORIGIN_SHIFT` | Half the circumference of the Earth at the Equator; defines the extent of the projected coordinate space |
21
+ | `MAX_LATITUDE` | Maximum representable latitude (~85.051°); the projection is undefined beyond this limit |
22
+
23
+ ## Tile Coordinate Methods
24
+
25
+ Convert between Web Mercator coordinates and map tile indices at a given zoom level.
26
+
27
+ ```ruby
28
+ tile_x, tile_y = point.to_tile_coordinates(zoom)
29
+
30
+ point = Geodetic::Coordinates::WebMercator.from_tile_coordinates(x, y, zoom)
31
+ ```
32
+
33
+ ## Pixel Coordinate Methods
34
+
35
+ Convert between Web Mercator coordinates and pixel positions at a given zoom level and tile size.
36
+
37
+ ```ruby
38
+ pixel_x, pixel_y = point.to_pixel_coordinates(zoom, tile_size)
39
+
40
+ point = Geodetic::Coordinates::WebMercator.from_pixel_coordinates(x, y, zoom, tile_size)
41
+ ```
42
+
43
+ ## Tile Bounds
44
+
45
+ Retrieve the bounding box of a specific tile.
46
+
47
+ ```ruby
48
+ bounds = Geodetic::Coordinates::WebMercator.tile_bounds(tile_x, tile_y, zoom)
49
+ ```
50
+
51
+ ## Validation and Utility Methods
52
+
53
+ | Method | Description |
54
+ |---|---|
55
+ | `valid?` | Returns `true` if the coordinates fall within the valid Web Mercator extent |
56
+ | `clamp!` | Clamps coordinates to the valid range, modifying the object in place |
57
+
58
+ ### Universal Distance Methods
59
+
60
+ The universal `distance_to` method computes the Vincenty great-circle distance (in meters) to any other coordinate type. The `straight_line_distance_to` method computes the Euclidean distance in ECEF space. Both accept single or multiple targets.
61
+
62
+ ```ruby
63
+ wm_a = Geodetic::Coordinates::WebMercator.new(x: -13627665.0, y: 6044499.0)
64
+ wm_b = Geodetic::Coordinates::WebMercator.new(x: -13631157.0, y: 5694043.0)
65
+ wm_a.distance_to(wm_b) # => Distance (meters, great-circle)
66
+ wm_a.straight_line_distance_to(wm_b) # => Distance (meters, Euclidean)
67
+ ```
@@ -0,0 +1,65 @@
1
+ # Installation
2
+
3
+ ## Requirements
4
+
5
+ Geodetic is tested with **Ruby 4.0.1**. It has no runtime dependencies beyond the Ruby standard library.
6
+
7
+ ## Add to Your Gemfile
8
+
9
+ ```ruby
10
+ gem "geodetic"
11
+ ```
12
+
13
+ Then run:
14
+
15
+ ```sh
16
+ bundle install
17
+ ```
18
+
19
+ ## Or Install Directly
20
+
21
+ ```sh
22
+ gem install geodetic
23
+ ```
24
+
25
+ ## Require the Gem
26
+
27
+ The simplest way to load Geodetic is to require the top-level module:
28
+
29
+ ```ruby
30
+ require "geodetic"
31
+ ```
32
+
33
+ This loads the core module, the `Geodetic::Datum` class, and the `Geodetic::GeoidHeight` support. Coordinate types are loaded on demand when you first perform a conversion.
34
+
35
+ ## Requiring Specific Coordinate Types
36
+
37
+ If you want to use a coordinate class directly without going through a conversion, require it explicitly:
38
+
39
+ ```ruby
40
+ require "geodetic"
41
+ require "geodetic/coordinates/lla"
42
+ require "geodetic/coordinates/ecef"
43
+ require "geodetic/coordinates/utm"
44
+ require "geodetic/coordinates/enu"
45
+ require "geodetic/coordinates/ned"
46
+ require "geodetic/coordinates/mgrs"
47
+ require "geodetic/coordinates/usng"
48
+ require "geodetic/coordinates/web_mercator"
49
+ require "geodetic/coordinates/ups"
50
+ require "geodetic/coordinates/state_plane"
51
+ require "geodetic/coordinates/bng"
52
+ ```
53
+
54
+ You only need to require the types you plan to construct directly. Conversion methods handle their own requires internally.
55
+
56
+ ## Verify the Installation
57
+
58
+ ```ruby
59
+ require "geodetic"
60
+ require "geodetic/coordinates/lla"
61
+
62
+ lla = Geodetic::Coordinates::LLA.new(lat: 47.6205, lng: -122.3493, alt: 184.0)
63
+ puts lla.to_s
64
+ #=> "47.6205, -122.3493, 184.0"
65
+ ```
@@ -0,0 +1,175 @@
1
+ # Quick Start
2
+
3
+ This guide walks through the core features of Geodetic using the Seattle Space Needle as a reference point.
4
+
5
+ ## 1. Create an LLA Coordinate
6
+
7
+ LLA (Latitude, Longitude, Altitude) is the most common starting point. All constructors use keyword arguments.
8
+
9
+ ```ruby
10
+ require "geodetic"
11
+ require "geodetic/coordinates/lla"
12
+
13
+ space_needle = Geodetic::Coordinates::LLA.new(
14
+ lat: 47.6205,
15
+ lng: -122.3493,
16
+ alt: 184.0
17
+ )
18
+
19
+ puts space_needle.lat #=> 47.6205
20
+ puts space_needle.longitude #=> -122.3493 (alias for lng)
21
+ puts space_needle.alt #=> 184.0
22
+ ```
23
+
24
+ ## 2. Convert to ECEF
25
+
26
+ ECEF (Earth-Centered, Earth-Fixed) represents a position as X, Y, Z coordinates in meters relative to the center of the Earth.
27
+
28
+ ```ruby
29
+ ecef = space_needle.to_ecef
30
+
31
+ puts ecef.x # X coordinate in meters
32
+ puts ecef.y # Y coordinate in meters
33
+ puts ecef.z # Z coordinate in meters
34
+
35
+ # Convert back to LLA
36
+ lla = ecef.to_lla
37
+ puts lla.to_s #=> "47.6205, -122.3493, 184.0"
38
+ ```
39
+
40
+ ## 3. Convert to UTM
41
+
42
+ UTM (Universal Transverse Mercator) is widely used in mapping and surveying.
43
+
44
+ ```ruby
45
+ utm = space_needle.to_utm
46
+
47
+ puts utm.easting # easting in meters
48
+ puts utm.northing # northing in meters
49
+ puts utm.altitude # altitude in meters
50
+ puts utm.zone # UTM zone number (e.g., 10)
51
+ puts utm.hemisphere # "N" or "S"
52
+
53
+ # Convert back to LLA
54
+ lla = utm.to_lla
55
+ ```
56
+
57
+ ## 4. Convert to Local Coordinates (ENU and NED)
58
+
59
+ Local tangent plane coordinate systems require a reference point. ENU (East, North, Up) and NED (North, East, Down) are commonly used in navigation and aerospace.
60
+
61
+ ```ruby
62
+ # Define a reference point (e.g., a nearby base station)
63
+ reference = Geodetic::Coordinates::LLA.new(
64
+ lat: 47.6062,
65
+ lng: -122.3321,
66
+ alt: 0.0
67
+ )
68
+
69
+ # Convert to ENU relative to the reference
70
+ enu = space_needle.to_enu(reference)
71
+
72
+ puts enu.east # meters east of reference (alias: e)
73
+ puts enu.north # meters north of reference (alias: n)
74
+ puts enu.up # meters above reference (alias: u)
75
+
76
+ # Convert to NED relative to the reference
77
+ ned = space_needle.to_ned(reference)
78
+
79
+ puts ned.north # meters north of reference (alias: n)
80
+ puts ned.east # meters east of reference (alias: e)
81
+ puts ned.down # meters below reference (alias: d)
82
+
83
+ # ENU and NED convert directly to each other
84
+ ned_from_enu = enu.to_ned
85
+ enu_from_ned = ned.to_enu
86
+
87
+ # Convert back to LLA
88
+ lla = enu.to_lla(reference)
89
+ ```
90
+
91
+ ## 5. Use DMS Format
92
+
93
+ LLA coordinates can be displayed and parsed in Degrees, Minutes, Seconds format.
94
+
95
+ ```ruby
96
+ # Convert to DMS string
97
+ dms = space_needle.to_dms
98
+ puts dms #=> "47 37' 13.80\" N, 122 20' 57.48\" W, 184.00 m"
99
+
100
+ # Parse a DMS string back to LLA
101
+ lla = Geodetic::Coordinates::LLA.from_dms("47 37' 13.80\" N, 122 20' 57.48\" W, 184.00 m")
102
+ puts lla.lat #=> 47.6205
103
+ ```
104
+
105
+ ## 6. Serialize and Deserialize
106
+
107
+ Every coordinate type supports `to_s`, `to_a`, `from_string`, and `from_array` for serialization.
108
+
109
+ ```ruby
110
+ # Serialize to string and array
111
+ str = space_needle.to_s #=> "47.6205, -122.3493, 184.0"
112
+ arr = space_needle.to_a #=> [47.6205, -122.3493, 184.0]
113
+
114
+ # Deserialize
115
+ lla_from_str = Geodetic::Coordinates::LLA.from_string(str)
116
+ lla_from_arr = Geodetic::Coordinates::LLA.from_array(arr)
117
+
118
+ # Works the same for all coordinate types
119
+ ecef = space_needle.to_ecef
120
+ ecef_str = ecef.to_s
121
+ ecef_arr = ecef.to_a
122
+ ecef_restored = Geodetic::Coordinates::ECEF.from_string(ecef_str)
123
+ ecef_restored = Geodetic::Coordinates::ECEF.from_array(ecef_arr)
124
+ ```
125
+
126
+ ## 7. Work with Datums
127
+
128
+ Geodetic ships with 16 geodetic datums. All conversion methods default to WGS84 but accept an optional datum parameter.
129
+
130
+ ```ruby
131
+ # List available datums
132
+ Geodetic::Datum.list
133
+
134
+ # Use a specific datum
135
+ clarke = Geodetic::Datum.new(name: "CLARKE_1866")
136
+
137
+ ecef_clarke = space_needle.to_ecef(clarke)
138
+ utm_clarke = space_needle.to_utm(clarke)
139
+
140
+ # Inspect datum properties
141
+ puts clarke.name #=> "CLARKE_1866"
142
+ puts clarke.desc #=> "Clarke 1866"
143
+ puts clarke.a #=> 6378206.4 (semi-major axis in meters)
144
+ puts clarke.b #=> 6356583.7999989809 (semi-minor axis in meters)
145
+ puts clarke.f #=> flattening
146
+ puts clarke.e2 #=> eccentricity squared
147
+
148
+ # Look up datum info without creating an instance
149
+ info = Geodetic::Datum.get("WGS84")
150
+ puts info["desc"] #=> "World Geodetic System 1984"
151
+ ```
152
+
153
+ Available datums include: WGS84, WGS72, GRS_1980, CLARKE_1866, CLARKE_1880, AIRY, MODIFIED_AIRY, AUSTRALIAN_NATIONAL, BESSEL_1841, EVEREST_INDIA_1830, EVEREST_BRUNEI_E_MALAYSIA, EVEREST_W_MALAYSIA_SINGAPORE, HELMERT_1906, HOUGH_1960, INTERNATIONAL_1924, and SOUTH_AMERICAN_1969.
154
+
155
+ ## 8. Geoid Height
156
+
157
+ The `GeoidHeight` class converts between ellipsoidal heights (HAE) and orthometric heights (e.g., NAVD88, MSL). LLA coordinates include geoid height support directly.
158
+
159
+ ```ruby
160
+ # Get the geoid undulation at a location
161
+ undulation = space_needle.geoid_height
162
+ puts undulation # geoid height in meters (EGM2008 model)
163
+
164
+ # Get orthometric height (height above mean sea level)
165
+ ortho = space_needle.orthometric_height
166
+ puts ortho # orthometric height in meters
167
+
168
+ # Convert between vertical datums
169
+ lla_navd88 = space_needle.convert_height_datum("HAE", "NAVD88")
170
+
171
+ # Use a different geoid model
172
+ undulation_egm96 = space_needle.geoid_height("EGM96")
173
+ ```
174
+
175
+ Available geoid models: EGM96, EGM2008, GEOID18, GEOID12B.
data/docs/index.md ADDED
@@ -0,0 +1,58 @@
1
+ # Geodetic
2
+
3
+ Geodetic is a Ruby gem for converting between geodetic coordinate systems. It provides a clean, consistent API for working with 12 coordinate systems, 16 geodetic datums, geoid height calculations, and geographic area computations.
4
+
5
+ ## Coordinate Systems
6
+
7
+ Geodetic supports full bidirectional conversion between all 12 coordinate systems:
8
+
9
+ | System | Class | Description |
10
+ |--------|-------|-------------|
11
+ | **LLA** | `Geodetic::Coordinates::LLA` | Latitude, Longitude, Altitude |
12
+ | **ECEF** | `Geodetic::Coordinates::ECEF` | Earth-Centered, Earth-Fixed (X, Y, Z) |
13
+ | **UTM** | `Geodetic::Coordinates::UTM` | Universal Transverse Mercator |
14
+ | **ENU** | `Geodetic::Coordinates::ENU` | East, North, Up (local tangent plane) |
15
+ | **NED** | `Geodetic::Coordinates::NED` | North, East, Down (local tangent plane) |
16
+ | **MGRS** | `Geodetic::Coordinates::MGRS` | Military Grid Reference System |
17
+ | **USNG** | `Geodetic::Coordinates::USNG` | United States National Grid |
18
+ | **WebMercator** | `Geodetic::Coordinates::WebMercator` | Web Mercator projection (EPSG:3857) |
19
+ | **UPS** | `Geodetic::Coordinates::UPS` | Universal Polar Stereographic |
20
+ | **StatePlane** | `Geodetic::Coordinates::StatePlane` | State Plane Coordinate System |
21
+ | **BNG** | `Geodetic::Coordinates::BNG` | British National Grid |
22
+ | **GH36** | `Geodetic::Coordinates::GH36` | Geohash-36 (spatial hash, URL-friendly) |
23
+
24
+ ## Additional Features
25
+
26
+ - **16 geodetic datums** -- WGS84, GRS 1980, Clarke 1866, Airy 1830, Bessel 1841, and more. All conversion methods accept an optional datum parameter, defaulting to WGS84.
27
+ - **Geoid height calculations** -- Convert between ellipsoidal and orthometric heights using models such as EGM96, EGM2008, GEOID18, and GEOID12B.
28
+ - **Geographic areas** -- `Geodetic::Areas::Circle`, `Geodetic::Areas::Polygon`, and `Geodetic::Areas::Rectangle` for point-in-area testing.
29
+
30
+ ## Design Principles
31
+
32
+ - All constructors use **keyword arguments** for clarity.
33
+ - Every coordinate system supports **serialization** via `to_s` and `to_a`, and **deserialization** via `from_string` and `from_array`.
34
+ - Conversions are available as instance methods (`to_ecef`, `to_utm`, etc.) and class-level factory methods (`from_ecef`, `from_utm`, etc.).
35
+
36
+ ## Quick Example
37
+
38
+ ```ruby
39
+ require "geodetic"
40
+
41
+ # Create an LLA coordinate (Seattle Space Needle)
42
+ lla = Geodetic::Coordinates::LLA.new(lat: 47.6205, lng: -122.3493, alt: 184.0)
43
+
44
+ # Convert to ECEF
45
+ ecef = lla.to_ecef
46
+ puts ecef.to_s
47
+ #=> "-2689653.22..., -4251180.82..., 4696587.81..."
48
+
49
+ # Convert back to LLA
50
+ lla_again = ecef.to_lla
51
+ puts lla_again.to_s
52
+ #=> "47.6205, -122.3493, 184.0"
53
+ ```
54
+
55
+ ## Documentation
56
+
57
+ - [Getting Started: Installation](getting-started/installation.md)
58
+ - [Getting Started: Quick Start](getting-started/quick-start.md)