# geohash _(no submodules)_ ## 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( points: readonly T[], precision = 5, ): Map { const buckets = new Map(); 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` 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.