first commit
This commit is contained in:
73
docs/geospatial/geo/geo-earth/src/geohash/geohash.md
Normal file
73
docs/geospatial/geo/geo-earth/src/geohash/geohash.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# geohash
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Encodes a `(latitude, longitude)` pair into a Geohash base32 string and decodes it back to the centre coordinate of the cell. Geohash is a hierarchical spatial index in which a longer string addresses a smaller bounding box, and any prefix of a hash addresses the enclosing cell — making it useful for proximity bucketing, key-prefix range scans on KV stores, and tiled spatial joins without pulling in a full R-tree or quadkey library.
|
||||
|
||||
This implementation follows the canonical Niemeyer/Morton algorithm: alternating-axis bit interleaving (longitude on even bits, latitude on odd) with the Geohash base32 alphabet `0123456789bcdefghjkmnpqrstuvwxyz` (intentionally omits `a`, `i`, `l`, `o` to avoid visual confusion).
|
||||
|
||||
## Features
|
||||
|
||||
- `encodeGeohash(coord, precision = 9)` — produces a base32 string of length `precision`.
|
||||
- `decodeGeohash(hash)` — returns the centre `{ latitude, longitude }` of the addressed cell.
|
||||
- Default precision `9` (~4.77 m × 4.77 m at the equator) suits typical store-front / asset-tracking use.
|
||||
- Pure, deterministic, allocation-light: a single growing string and integer state.
|
||||
- Universal — no DOM, no Node, no clock, no RNG.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { decodeGeohash, encodeGeohash } from "@suaveplan/geo-earth";
|
||||
|
||||
const hash = encodeGeohash({ latitude: 51.5, longitude: -0.1 }, 9);
|
||||
const coord = decodeGeohash(hash);
|
||||
// coord ≈ { latitude: 51.5, longitude: -0.1 }
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { encodeGeohash } from "@suaveplan/geo-earth";
|
||||
|
||||
// Bucket a stream of points by 5-character prefix (~4.9 km × 4.9 km cell)
|
||||
// for cheap proximity grouping in a key/value store.
|
||||
function bucketByCell<T extends { latitude: number; longitude: number }>(
|
||||
points: readonly T[],
|
||||
precision = 5,
|
||||
): Map<string, T[]> {
|
||||
const buckets = new Map<string, T[]>();
|
||||
for (const p of points) {
|
||||
const cell = encodeGeohash(p, precision);
|
||||
const list = buckets.get(cell);
|
||||
if (list) list.push(p);
|
||||
else buckets.set(cell, [p]);
|
||||
}
|
||||
return buckets;
|
||||
}
|
||||
|
||||
const grouped = bucketByCell([
|
||||
{ latitude: 51.5, longitude: -0.1 },
|
||||
{ latitude: 51.51, longitude: -0.09 },
|
||||
{ latitude: 48.86, longitude: 2.35 },
|
||||
]);
|
||||
// grouped has two keys: one shared London bucket and one Paris bucket.
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `encodeGeohash(coord: { latitude: number; longitude: number }, precision?: number): string`
|
||||
- `precision` defaults to `9`. Each character contributes 5 bits of resolution (~halving cell size every two characters).
|
||||
- Returns a string of exactly `precision` characters from the Geohash base32 alphabet.
|
||||
- `decodeGeohash(hash: string): { latitude: number; longitude: number }`
|
||||
- Returns the centre of the cell addressed by `hash`. Round-trip fidelity is bounded by the cell size at the chosen precision; the unit tests assert `toBeCloseTo` at one decimal for precision-9 round-trips.
|
||||
|
||||
Inputs outside `[-90, 90]` / `[-180, 180]` are not validated; callers that accept arbitrary user input should clamp first.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- Bit interleaving is implemented with the `evenBit` boolean flipping every iteration; on even iterations longitude is bisected, on odd iterations latitude is bisected. This matches the canonical Geohash bit ordering and is what makes string prefixes correspond to enclosing cells.
|
||||
- Base32 lookup uses `BASE32.indexOf(char)` per character in `decodeGeohash`. For very large decode batches a precomputed `Map<string, number>` would be marginally faster but adds module-load cost; the linear scan is fine for typical workloads at length ≤ 12.
|
||||
- The decoder returns the cell centre, not the bounding box. Callers needing the box (e.g. for tile rendering) can re-run the bisection loop and capture `minLat/maxLat/minLon/maxLon` themselves.
|
||||
Reference in New Issue
Block a user