geodetic 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: aff287312f66448c5c97edde02c099b6bb6fbfa3e465afe604b744e6c50676dc
4
- data.tar.gz: 5800e7ac55fe3d6bd74a1383b21d9b7aae777d2999b42050e12a0b18633aca07
3
+ metadata.gz: 4b413145dfecf16a20a9c7ac563811ed5cc2ae5784ae07a246a88d589704173b
4
+ data.tar.gz: 64b5d90e49b653a42ba469248dbcd4723e4b561f59475862a20ba4c7ce28366e
5
5
  SHA512:
6
- metadata.gz: a9f38b3a2587b77592d51549649042f25267929f09c515a5526fb53f5ca3a87a2ec8d665672ed0f90da2642f9ae00f1aa9b31db9bc076374b19ac6ec1f722fcf
7
- data.tar.gz: d3ed9287c2fe9d942e387df0d53754f0acdbc497235ac7a72c415be367bd766bb5b814efaefa6568809e68b3f90b3e4f3d059456f203b0bd03b341c94df16a65
6
+ metadata.gz: e5902317105ba68039818520b7a0b0ca6938e0f349b3123efe2e0d100a16f670ed0bc7571292f5691f1fcba20e7e0644baea192bf91306da972725a5984d3e0f
7
+ data.tar.gz: '08dd192001b74b25a46303fc61572bd1b5da7899d0b995a8538bece0f3ef616db9a340c9e8c542a5577d09f2e4c8cade454dada0a275404b7b1c6bab3bc4278d'
data/CHANGELOG.md CHANGED
@@ -10,6 +10,41 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
10
10
 
11
11
  ## [Unreleased]
12
12
 
13
+ ## [0.1.0] - 2026-03-08
14
+
15
+ ### Added
16
+
17
+ - **4 new coordinate systems** bringing the total from 11 to 15:
18
+ - `Geodetic::Coordinates::GH36` — Geohash-36 (radix-36 spatial hash, URL-friendly)
19
+ - `Geodetic::Coordinates::GH` — Geohash base-32 (standard geohash, supported by Elasticsearch, Redis, PostGIS)
20
+ - `Geodetic::Coordinates::HAM` — Maidenhead Locator System (amateur radio grid squares)
21
+ - `Geodetic::Coordinates::OLC` — Open Location Code / Plus Codes (Google's location encoding)
22
+ - **Full cross-system conversions** — all 15 coordinate systems convert to/from every other system (225 conversion paths)
23
+ - **Spatial hash features** for GH36, GH, HAM, and OLC:
24
+ - `neighbors` — returns all 8 adjacent grid cells
25
+ - `to_area` — returns the cell as a `Geodetic::Areas::Rectangle`
26
+ - `precision_in_meters` — cell size in meters as `{ lat:, lng: }`
27
+ - `to_slug` — URL-friendly string representation
28
+ - Configurable precision on construction
29
+ - **Geographic areas** — `Geodetic::Areas::Circle`, `Geodetic::Areas::Polygon`, and `Geodetic::Areas::Rectangle` for point-in-area testing
30
+ - **Bearing calculations** — universal `bearing_to` and `elevation_to` methods on all coordinate classes via `BearingMethods` mixin
31
+ - **`Geodetic::Bearing` class** — immutable value type with compass directions, reciprocal, radians, arithmetic, and `Comparable`
32
+ - **Validated setters** with type coercion and range validation on all mutable coordinate classes
33
+ - **`to_s(precision)`** on all coordinate classes, `Distance`, and `Bearing` with class-specific defaults
34
+ - **ENU and NED local methods** — `local_bearing_to` and `local_elevation_angle_to` for tangent-plane operations; NED adds `horizontal_distance_to`
35
+ - **GitHub Pages documentation** site with MkDocs
36
+
37
+ ### Changed
38
+
39
+ - Coordinate classes use `attr_reader` with validated setter methods instead of `attr_accessor`
40
+ - ENU/NED bearing methods renamed: `bearing_to` → `local_bearing_to`, `elevation_angle_to` → `local_elevation_angle_to` (universal `bearing_to`/`elevation_to` now come from the mixin)
41
+
42
+ ### Fixed
43
+
44
+ - ENU/NED argument mismatch in 8 coordinate classes — `to_enu`/`to_ned` passed extra `datum` arg that `LLA#to_enu`/`LLA#to_ned` does not accept
45
+ - StatePlane `lambert_conformal_conic_to_lla` double `DEG_PER_RAD` conversion producing incorrect results
46
+ - OLC floating-point encoding error — pre-computed `PAIR_RESOLUTIONS` with epsilon correction prevents truncation (e.g., Google HQ encoding "849VCWC8+R9")
47
+
13
48
  ## [0.0.1] - 2026-03-07
14
49
 
15
50
  Initial update from an old archived project.
data/README.md CHANGED
@@ -1,23 +1,33 @@
1
1
  # Geodetic
2
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) |
3
+ > [!INFO]
4
+ > See the [CHANGELOG](CHANGELOG.md) for the latest changes. The [examples directory has runnable demo apps](examples/) that show-off the various capabilities of the Geodetic library.
5
+
6
+ <br>
7
+ <table>
8
+ <tr>
9
+ <td width="50%" align="center" valign="top">
10
+ <img src="docs/assets/images/geodetic.jpg" alt="Geodetic"><br>
11
+ <em>"Convert coordinates. Map the world."</em>
12
+ </td>
13
+ <td width="50%" valign="top">
14
+ <strong>Key Features</strong><br>
15
+
16
+ - <strong>15 Coordinate Systems</strong> - LLA, ECEF, UTM, ENU, NED, MGRS, USNG, Web Mercator, UPS, State Plane, BNG, GH36, GH, HAM, OLC<br>
17
+ - <strong>Full Bidirectional Conversions</strong> - Every system converts to and from every other system<br>
18
+ - <strong>Distance Calculations</strong> - Vincenty great-circle and straight-line with unit tracking<br>
19
+ - <strong>Bearing Calculations</strong> - Forward azimuth, back azimuth, compass directions, elevation angles<br>
20
+ - <strong>Geoid Height Support</strong> - EGM96, EGM2008, GEOID18, GEOID12B models<br>
21
+ - <strong>Geographic Areas</strong> - Circle, Polygon, and Rectangle with point-in-area tests<br>
22
+ - <strong>Validated Setters</strong> - Type coercion and range validation on all coordinate attributes<br>
23
+ - <strong>Serialization</strong> - to_s(precision), to_a, from_string, from_array, DMS format<br>
24
+ - <strong>Multiple Datums</strong> - WGS84, Clarke 1866, GRS 1980, Airy 1830, and more<br>
25
+ - <strong>Immutable Value Types</strong> - Distance and Bearing with arithmetic and comparison
26
+ </td>
27
+ </tr>
28
+ </table>
29
+
30
+ <p>Geodetic enables precise conversion between geodetic coordinate systems in Ruby. All 15 coordinate systems support complete bidirectional conversions with high precision. Review the <a href="https://madbomber.github.io/geodetic/">full documentation website</a> and explore the <a href="examples/">runnable examples</a>.</p>
21
31
 
22
32
  ## Installation
23
33
 
@@ -129,7 +139,7 @@ bng = Coordinates::BNG.new(easting: 530000, northing: 180000)
129
139
  bng.easting = 430000 # grid_ref automatically recalculated
130
140
  ```
131
141
 
132
- ECEF, ENU, NED, and WebMercator setters coerce to float with no range constraints. MGRS, USNG, GH36, Distance, and Bearing are immutable.
142
+ ECEF, ENU, NED, and WebMercator setters coerce to float with no range constraints. MGRS, USNG, GH36, GH, HAM, OLC, Distance, and Bearing are immutable.
133
143
 
134
144
  ### DMS (Degrees, Minutes, Seconds)
135
145
 
@@ -396,6 +406,90 @@ gh36.precision # => 10
396
406
  gh36.precision_in_meters # => { lat: 0.31, lng: 0.62 }
397
407
  ```
398
408
 
409
+ ### Geohash (GH)
410
+
411
+ The standard Geohash (base-32) algorithm by Gustavo Niemeyer, widely supported by Elasticsearch, Redis, PostGIS, and geocoding services:
412
+
413
+ ```ruby
414
+ # From a geohash string
415
+ gh = Coordinates::GH.new("dr5ru7")
416
+
417
+ # From any coordinate
418
+ gh = Coordinates::GH.new(lla)
419
+ gh = lla.to_gh(precision: 8)
420
+
421
+ # Decode back to LLA
422
+ lla = gh.to_lla
423
+
424
+ # URL slug (the hash itself is URL-safe)
425
+ gh.to_slug # => "dr5ru7"
426
+
427
+ # Neighbor cells
428
+ gh.neighbors # => { N: GH, S: GH, E: GH, W: GH, NE: ..., NW: ..., SE: ..., SW: ... }
429
+
430
+ # Bounding rectangle of the geohash cell
431
+ area = gh.to_area # => Areas::Rectangle
432
+ area.includes?(gh.to_lla) # => true
433
+
434
+ # Precision info
435
+ gh.precision # => 6
436
+ gh.precision_in_meters # => { lat: 610.98, lng: 1221.97 }
437
+ ```
438
+
439
+ ### Maidenhead Locator (HAM)
440
+
441
+ The Maidenhead Locator System used worldwide in amateur radio for grid square identification:
442
+
443
+ ```ruby
444
+ # From a Maidenhead locator string
445
+ ham = Coordinates::HAM.new("FN31pr")
446
+
447
+ # From any coordinate
448
+ ham = Coordinates::HAM.new(lla)
449
+ ham = lla.to_ham(precision: 8)
450
+
451
+ # Decode back to LLA
452
+ lla = ham.to_lla
453
+
454
+ # Neighbor cells
455
+ ham.neighbors # => { N: HAM, S: HAM, E: HAM, W: HAM, NE: ..., NW: ..., SE: ..., SW: ... }
456
+
457
+ # Bounding rectangle of the grid square
458
+ area = ham.to_area # => Areas::Rectangle
459
+ area.includes?(ham.to_lla) # => true
460
+
461
+ # Precision info
462
+ ham.precision # => 6
463
+ ham.precision_in_meters # => { lat: 4631.0, lng: 9260.0 }
464
+ ```
465
+
466
+ ### Open Location Code / Plus Codes (OLC)
467
+
468
+ Google's open system for encoding locations into short, URL-friendly codes:
469
+
470
+ ```ruby
471
+ # From a plus code string
472
+ olc = Coordinates::OLC.new("849VCWC8+R9")
473
+
474
+ # From any coordinate
475
+ olc = Coordinates::OLC.new(lla)
476
+ olc = lla.to_olc(precision: 11)
477
+
478
+ # Decode back to LLA
479
+ lla = olc.to_lla
480
+
481
+ # Neighbor cells
482
+ olc.neighbors # => { N: OLC, S: OLC, E: OLC, W: OLC, NE: ..., NW: ..., SE: ..., SW: ... }
483
+
484
+ # Bounding rectangle of the plus code cell
485
+ area = olc.to_area # => Areas::Rectangle
486
+ area.includes?(olc.to_lla) # => true
487
+
488
+ # Precision info
489
+ olc.precision # => 10
490
+ olc.precision_in_meters # => { lat: 13.9, lng: 13.9 }
491
+ ```
492
+
399
493
  ### Geographic Areas
400
494
 
401
495
  ```ruby
@@ -448,7 +542,7 @@ The [`examples/`](examples/) directory contains runnable demo scripts showing pr
448
542
  | Script | Description |
449
543
  |--------|-------------|
450
544
  | [`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 |
545
+ | [`02_all_coordinate_systems.rb`](examples/02_all_coordinate_systems.rb) | All 15 coordinate systems, cross-system chains, and areas |
452
546
  | [`03_distance_calculations.rb`](examples/03_distance_calculations.rb) | Distance class features, unit conversions, and arithmetic |
453
547
  | [`04_bearing_calculations.rb`](examples/04_bearing_calculations.rb) | Bearing class, compass directions, elevation angles, and chain bearings |
454
548
 
@@ -460,6 +554,8 @@ ruby -Ilib examples/01_basic_conversions.rb
460
554
 
461
555
  ## Development
462
556
 
557
+ For comprehensive guides and API documentation, visit **[https://madbomber.github.io/geodetic](https://madbomber.github.io/geodetic)**
558
+
463
559
  ```bash
464
560
  bin/setup # Install dependencies
465
561
  rake test # Run tests
Binary file
@@ -0,0 +1,282 @@
1
+ # Geodetic::Coordinates::GH
2
+
3
+ ## Geohash (Base-32)
4
+
5
+ The standard geohash algorithm by Gustavo Niemeyer that encodes latitude/longitude into a compact string using a 32-character alphabet. It uses interleaved longitude and latitude bits, with each 5-bit group mapped to a base-32 character.
6
+
7
+ Character set: `0123456789bcdefghjkmnpqrstuvwxyz`
8
+ (excludes `a`, `i`, `l`, `o` to avoid ambiguity)
9
+
10
+ This is the de facto standard for spatial hashing, natively supported by Elasticsearch, Redis, PostGIS, and many geocoding services.
11
+
12
+ GH is a **2D coordinate system** (no altitude). Conversions to/from other systems go through LLA as the intermediary. Each geohash string represents a rectangular cell; the coordinate's point value is the cell's midpoint.
13
+
14
+ ## Constructor
15
+
16
+ ```ruby
17
+ # From a geohash string
18
+ coord = Geodetic::Coordinates::GH.new("dr5ru7")
19
+
20
+ # From any coordinate (converts via LLA)
21
+ coord = Geodetic::Coordinates::GH.new(lla_coord)
22
+ coord = Geodetic::Coordinates::GH.new(utm_coord, precision: 8)
23
+ ```
24
+
25
+ | Parameter | Type | Default | Description |
26
+ |-------------|-------------------|---------|----------------------------------------------|
27
+ | `source` | String or Coord | — | A geohash string or any coordinate object |
28
+ | `precision` | Integer | 12 | Hash length (ignored when source is a String) |
29
+
30
+ Raises `ArgumentError` if the source is an empty string, contains invalid characters, or is not a recognized coordinate type. String input is case-insensitive (automatically downcased).
31
+
32
+ ## Attributes
33
+
34
+ | Attribute | Type | Access | Description |
35
+ |-----------|--------|-----------|---------------------------------|
36
+ | `geohash` | String | read-only | The geohash base-32 encoded string |
37
+
38
+ GH is **immutable** — there are no setter methods.
39
+
40
+ ## Precision
41
+
42
+ The precision (hash length) determines the size of the cell:
43
+
44
+ | Length | Approximate Resolution |
45
+ |--------|----------------------|
46
+ | 1 | ~5,000 km |
47
+ | 4 | ~20 km |
48
+ | 6 | ~610 m |
49
+ | 8 | ~19 m |
50
+ | 12 | ~0.019 m (default) |
51
+
52
+ ```ruby
53
+ coord.precision # => 12 (hash length)
54
+ coord.precision_in_meters # => { lat: 0.019, lng: 0.019 }
55
+ ```
56
+
57
+ Longer hashes yield finer precision.
58
+
59
+ ## Conversions
60
+
61
+ All conversions chain through LLA. The datum parameter defaults to `Geodetic::WGS84`.
62
+
63
+ ### Instance Methods
64
+
65
+ ```ruby
66
+ coord.to_lla # => LLA (midpoint of the cell)
67
+ coord.to_ecef
68
+ coord.to_utm
69
+ coord.to_enu(reference_lla)
70
+ coord.to_ned(reference_lla)
71
+ coord.to_mgrs
72
+ coord.to_usng
73
+ coord.to_web_mercator
74
+ coord.to_ups
75
+ coord.to_state_plane(zone_code)
76
+ coord.to_bng
77
+ coord.to_gh36
78
+ ```
79
+
80
+ ### Class Methods
81
+
82
+ ```ruby
83
+ GH.from_lla(lla_coord)
84
+ GH.from_ecef(ecef_coord)
85
+ GH.from_utm(utm_coord)
86
+ GH.from_web_mercator(wm_coord)
87
+ GH.from_gh36(gh36_coord)
88
+ # ... and all other coordinate systems
89
+ ```
90
+
91
+ ### LLA Convenience Methods
92
+
93
+ ```ruby
94
+ lla = Geodetic::Coordinates::LLA.new(lat: 40.689167, lng: -74.044444)
95
+ gh = lla.to_gh # default precision 12
96
+ gh = lla.to_gh(precision: 6) # custom precision
97
+
98
+ lla = Geodetic::Coordinates::LLA.from_gh(gh)
99
+ ```
100
+
101
+ ## Serialization
102
+
103
+ ### `to_s(truncate_to = nil)`
104
+
105
+ Returns the geohash string. An optional integer truncates to that length.
106
+
107
+ ```ruby
108
+ coord = GH.new("dr5ru7c5g200")
109
+ coord.to_s # => "dr5ru7c5g200"
110
+ coord.to_s(6) # => "dr5ru7"
111
+ ```
112
+
113
+ ### `to_slug`
114
+
115
+ Alias for `to_s`. The geohash is already URL-safe.
116
+
117
+ ### `to_a`
118
+
119
+ Returns `[lat, lng]` of the cell midpoint.
120
+
121
+ ```ruby
122
+ coord.to_a # => [40.689, -74.044]
123
+ ```
124
+
125
+ ### `from_string` / `from_array`
126
+
127
+ ```ruby
128
+ GH.from_string("dr5ru7") # from geohash string
129
+ GH.from_array([40.689, -74.044]) # from [lat, lng]
130
+ ```
131
+
132
+ ## Neighbors
133
+
134
+ Returns all 8 adjacent geohash cells as GH instances.
135
+
136
+ ```ruby
137
+ coord = GH.new(LLA.new(lat: 40.0, lng: -74.0))
138
+ neighbors = coord.neighbors
139
+ # => { N: GH, S: GH, E: GH, W: GH, NE: GH, NW: GH, SE: GH, SW: GH }
140
+
141
+ neighbors[:N].to_lla.lat > coord.to_lla.lat # => true
142
+ neighbors[:E].to_lla.lng > coord.to_lla.lng # => true
143
+ ```
144
+
145
+ Neighbors preserve the same precision as the original geohash. Longitude wraps correctly near the antimeridian (180/-180 boundary).
146
+
147
+ ## Area
148
+
149
+ The `to_area` method returns the geohash cell as a `Geodetic::Areas::Rectangle`.
150
+
151
+ ```ruby
152
+ area = coord.to_area
153
+ # => Geodetic::Areas::Rectangle
154
+
155
+ area.includes?(coord.to_lla) # => true (midpoint is inside the cell)
156
+ area.nw # => LLA (northwest corner)
157
+ area.se # => LLA (southeast corner)
158
+ ```
159
+
160
+ ## Equality
161
+
162
+ Two GH instances are equal if their geohash strings match exactly.
163
+
164
+ ```ruby
165
+ GH.new("dr5ru7") == GH.new("dr5ru7") # => true
166
+ GH.new("dr5ru7") == GH.new("dr5ru8") # => false
167
+ ```
168
+
169
+ ## `valid?`
170
+
171
+ Returns `true` if all characters are in the valid base-32 geohash alphabet.
172
+
173
+ ```ruby
174
+ coord.valid? # => true
175
+ ```
176
+
177
+ ## Universal Distance and Bearing Methods
178
+
179
+ GH supports all universal distance and bearing methods via the `DistanceMethods` and `BearingMethods` mixins:
180
+
181
+ ```ruby
182
+ a = GH.new(LLA.new(lat: 40.689167, lng: -74.044444))
183
+ b = GH.new(LLA.new(lat: 51.504444, lng: -0.086666))
184
+
185
+ a.distance_to(b) # => Distance (~5,570 km)
186
+ a.straight_line_distance_to(b) # => Distance
187
+ a.bearing_to(b) # => Bearing (~51°)
188
+ a.elevation_to(b) # => Float (degrees)
189
+ ```
190
+
191
+ Cross-system distances work too:
192
+
193
+ ```ruby
194
+ utm = seattle_lla.to_utm
195
+ gh = GH.new(portland_lla)
196
+ utm.distance_to(gh) # => Distance
197
+ ```
198
+
199
+ ## GH vs GH36
200
+
201
+ GH (base-32) and GH36 (base-36) are both spatial hashing systems that encode latitude/longitude into compact strings. They differ in alphabet, encoding strategy, ecosystem support, and resolution characteristics.
202
+
203
+ ### Design Differences
204
+
205
+ | Feature | GH (base-32) | GH36 (base-36) |
206
+ |---------|-------------|----------------|
207
+ | Algorithm | Bit-interleaved lat/lng, 5 bits per character | 6x6 matrix subdivision per character |
208
+ | Alphabet | 32 chars: `0-9, b-z` (excludes `a, i, l, o`) | 36 chars: `23456789bBCdDFgGhHjJKlLMnNPqQrRtTVWX` |
209
+ | Case sensitivity | Case-insensitive | Case-sensitive |
210
+ | Default precision | 12 characters | 10 characters |
211
+ | Information density | 5 bits/char (log2(32) = 5.0) | ~5.17 bits/char (log2(36) = 5.17) |
212
+
213
+ GH interleaves longitude and latitude bits — odd-numbered bits encode longitude, even-numbered bits encode latitude. Each group of 5 bits maps to one base-32 character. This means GH cells alternate between being wider-than-tall and taller-than-wide at each precision level.
214
+
215
+ GH36 subdivides the coordinate space using a 6x6 character matrix at each level. This yields slightly more information per character (5.17 vs 5.0 bits) but requires a case-sensitive alphabet.
216
+
217
+ ### Precision Comparison
218
+
219
+ Cell dimensions at the equator for each hash length:
220
+
221
+ | Length | GH (base-32) | GH36 (base-36) |
222
+ |--------|-------------|----------------|
223
+ | 1 | 5,009 km x 4,628 km | 1,668 km x 3,335 km |
224
+ | 2 | 626 km x 1,251 km | 278 km x 556 km |
225
+ | 3 | 157 km x 157 km | 46 km x 93 km |
226
+ | 4 | 20 km x 39 km | 7.7 km x 15 km |
227
+ | 5 | 4.9 km x 4.9 km | 1.3 km x 2.6 km |
228
+ | 6 | 611 m x 1,223 m | 214 m x 429 m |
229
+ | 7 | 153 m x 153 m | 36 m x 71 m |
230
+ | 8 | 19 m x 38 m | 6.0 m x 11.9 m |
231
+ | 9 | 4.8 m x 4.8 m | 0.99 m x 1.99 m |
232
+ | 10 | 0.60 m x 1.19 m | 0.17 m x 0.33 m |
233
+ | 11 | 0.15 m x 0.15 m | — |
234
+ | 12 | 0.019 m x 0.037 m | — |
235
+
236
+ At their default precisions:
237
+
238
+ - **GH at 12 characters**: ~19 mm x 37 mm (sub-centimeter)
239
+ - **GH36 at 10 characters**: ~17 cm x 33 cm (sub-meter)
240
+
241
+ GH36 achieves better resolution per character due to its larger alphabet, but GH compensates by defaulting to a longer hash. At equal string lengths, GH36 cells are smaller. At their respective defaults, GH cells are roughly 9x smaller.
242
+
243
+ ### Ecosystem Support
244
+
245
+ | System | GH (base-32) | GH36 (base-36) |
246
+ |--------|:------------:|:--------------:|
247
+ | Elasticsearch | Native `geo_point` type | — |
248
+ | Redis | `GEOADD`/`GEOSEARCH` commands | — |
249
+ | PostGIS | `ST_GeoHash()` function | — |
250
+ | MongoDB | `2dsphere` index | — |
251
+ | Google S2 | Compatible cell IDs | — |
252
+ | Geocoding APIs | Widely supported | — |
253
+
254
+ GH (base-32) is the de facto standard. Virtually all spatial databases and services that support geohashing use the base-32 algorithm. GH36 is a niche alternative designed for URL-friendly strings with no ambiguous characters.
255
+
256
+ ### When to Use Which
257
+
258
+ **Choose GH (base-32) when:**
259
+ - Integrating with external services (Elasticsearch, Redis, PostGIS)
260
+ - Storing geohashes in databases that expect the standard format
261
+ - Performing proximity searches using prefix matching
262
+ - Interoperating with third-party geocoding or mapping APIs
263
+
264
+ **Choose GH36 (base-36) when:**
265
+ - You need a self-contained spatial hash with no external dependencies
266
+ - Case-sensitive storage is acceptable
267
+ - You want to avoid vowels and ambiguous characters in user-facing strings
268
+ - Slightly better resolution per character matters
269
+
270
+ ### Converting Between GH and GH36
271
+
272
+ ```ruby
273
+ gh = Geodetic::Coordinates::GH.new("dr5ru7")
274
+ gh36 = gh.to_gh36 # => GH36 instance
275
+ gh_back = gh36.to_gh # => GH instance
276
+
277
+ # Both decode to the same approximate location
278
+ gh.to_lla.lat # => 40.71...
279
+ gh36.to_lla.lat # => 40.71...
280
+ ```
281
+
282
+ Note that roundtrip conversion is not lossless — each system quantizes coordinates to its own grid, so converting GH -> GH36 -> GH may produce a slightly different hash string that decodes to the same approximate location.