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 +4 -4
- data/CHANGELOG.md +35 -0
- data/README.md +116 -20
- data/docs/assets/images/geodetic.jpg +0 -0
- data/docs/coordinate-systems/gh.md +282 -0
- data/docs/coordinate-systems/ham.md +207 -0
- data/docs/coordinate-systems/index.md +23 -17
- data/docs/coordinate-systems/olc.md +221 -0
- data/docs/getting-started/installation.md +5 -1
- data/docs/getting-started/quick-start.md +4 -4
- data/docs/index.md +29 -2
- data/docs/reference/conversions.md +7 -1
- data/docs/reference/datums.md +10 -17
- data/docs/reference/serialization.md +13 -5
- data/lib/geodetic/coordinates/bng.rb +27 -0
- data/lib/geodetic/coordinates/ecef.rb +24 -0
- data/lib/geodetic/coordinates/enu.rb +27 -0
- data/lib/geodetic/coordinates/gh.rb +372 -0
- data/lib/geodetic/coordinates/gh36.rb +24 -0
- data/lib/geodetic/coordinates/ham.rb +435 -0
- data/lib/geodetic/coordinates/lla.rb +35 -2
- data/lib/geodetic/coordinates/mgrs.rb +24 -0
- data/lib/geodetic/coordinates/ned.rb +27 -0
- data/lib/geodetic/coordinates/olc.rb +479 -0
- data/lib/geodetic/coordinates/state_plane.rb +27 -0
- data/lib/geodetic/coordinates/ups.rb +27 -0
- data/lib/geodetic/coordinates/usng.rb +24 -0
- data/lib/geodetic/coordinates/utm.rb +24 -0
- data/lib/geodetic/coordinates/web_mercator.rb +24 -0
- data/lib/geodetic/coordinates.rb +6 -0
- data/lib/geodetic/version.rb +1 -1
- metadata +12 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4b413145dfecf16a20a9c7ac563811ed5cc2ae5784ae07a246a88d589704173b
|
|
4
|
+
data.tar.gz: 64b5d90e49b653a42ba469248dbcd4723e4b561f59475862a20ba4c7ce28366e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
|
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.
|