geohash-kit 1.0.1 → 1.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.
package/README.md CHANGED
@@ -7,6 +7,8 @@
7
7
  ![zero deps](https://img.shields.io/badge/dependencies-0-brightgreen)
8
8
  ![TypeScript](https://img.shields.io/badge/TypeScript-native-blue)
9
9
 
10
+ **[Interactive Demo](https://thecryptodonkey.github.io/geohash-kit/)** — try every API function on a live map.
11
+
10
12
  ## Why geohash-kit?
11
13
 
12
14
  - **Modern TypeScript** — native types, ESM-only, tree-shakeable subpath exports. Zero dependencies. A drop-in replacement for `ngeohash`.
package/dist/core.d.ts ADDED
@@ -0,0 +1,39 @@
1
+ export interface GeohashBounds {
2
+ minLat: number;
3
+ maxLat: number;
4
+ minLon: number;
5
+ maxLon: number;
6
+ }
7
+ export type Direction = 'n' | 'ne' | 'e' | 'se' | 's' | 'sw' | 'w' | 'nw';
8
+ /** Encode latitude/longitude to a geohash string. Default precision 5 (~4.9km). */
9
+ export declare function encode(lat: number, lon: number, precision?: number): string;
10
+ /** Decode a geohash to its centre point with error margins. */
11
+ export declare function decode(hash: string): {
12
+ lat: number;
13
+ lon: number;
14
+ error: {
15
+ lat: number;
16
+ lon: number;
17
+ };
18
+ };
19
+ /** Get the bounding rectangle of a geohash cell. */
20
+ export declare function bounds(hash: string): GeohashBounds;
21
+ /** Get the 32 children of a geohash at the next precision level. */
22
+ export declare function children(hash: string): string[];
23
+ /** Check if two geohashes overlap (bidirectional prefix containment). */
24
+ export declare function contains(a: string, b: string): boolean;
25
+ /** Check if a geohash matches any candidate in a multi-precision set. */
26
+ export declare function matchesAny(hash: string, candidates: string[]): boolean;
27
+ /** Get a single adjacent geohash cell in the given direction. */
28
+ export declare function neighbour(hash: string, direction: Direction): string;
29
+ /** Get all 8 adjacent geohash cells. */
30
+ export declare function neighbours(hash: string): Record<Direction, string>;
31
+ /** Haversine distance in metres between two coordinate pairs. */
32
+ export declare function distanceFromCoords(lat1: number, lon1: number, lat2: number, lon2: number): number;
33
+ /** Haversine distance in metres between centres of two geohash cells. */
34
+ export declare function distance(hashA: string, hashB: string): number;
35
+ /** Optimal geohash precision for a given search radius in metres. */
36
+ export declare function radiusToPrecision(metres: number): number;
37
+ /** Approximate cell radius in metres for a given precision level. */
38
+ export declare function precisionToRadius(precision: number): number;
39
+ //# sourceMappingURL=core.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;CACf;AAED,MAAM,MAAM,SAAS,GAAG,GAAG,GAAG,IAAI,GAAG,GAAG,GAAG,IAAI,GAAG,GAAG,GAAG,IAAI,GAAG,GAAG,GAAG,IAAI,CAAA;AAczE,mFAAmF;AACnF,wBAAgB,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,SAAS,SAAI,GAAG,MAAM,CA2BtE;AAED,+DAA+D;AAC/D,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAWtG;AAED,oDAAoD;AACpD,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,CAoBlD;AAED,oEAAoE;AACpE,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAG/C;AAID,yEAAyE;AACzE,wBAAgB,QAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAEtD;AAED,yEAAyE;AACzE,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,OAAO,CAEtE;AAID,iEAAiE;AACjE,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,GAAG,MAAM,CA0BpE;AAED,wCAAwC;AACxC,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAWlE;AAMD,iEAAiE;AACjE,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAWjG;AAED,yEAAyE;AACzE,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAI7D;AAkBD,qEAAqE;AACrE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAMxD;AAED,qEAAqE;AACrE,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAI3D"}
package/dist/core.js ADDED
@@ -0,0 +1,213 @@
1
+ // geohash-kit/core — encode, decode, bounds, children, contains, matchesAny
2
+ const BASE32 = '0123456789bcdefghjkmnpqrstuvwxyz';
3
+ const BASE32_DECODE = {};
4
+ for (let i = 0; i < BASE32.length; i++)
5
+ BASE32_DECODE[BASE32[i]] = i;
6
+ // --- Validation ---
7
+ function validateGeohash(hash) {
8
+ for (const ch of hash) {
9
+ if (!(ch in BASE32_DECODE)) {
10
+ throw new TypeError(`Invalid geohash character: '${ch}' in "${hash}"`);
11
+ }
12
+ }
13
+ }
14
+ // --- Encoding ---
15
+ /** Encode latitude/longitude to a geohash string. Default precision 5 (~4.9km). */
16
+ export function encode(lat, lon, precision = 5) {
17
+ if (!Number.isFinite(lat) || lat < -90 || lat > 90)
18
+ throw new RangeError(`Invalid latitude: ${lat}`);
19
+ if (!Number.isFinite(lon) || lon < -180 || lon > 180)
20
+ throw new RangeError(`Invalid longitude: ${lon}`);
21
+ if (!Number.isFinite(precision))
22
+ throw new RangeError(`Invalid precision: ${precision}`);
23
+ precision = Math.round(precision);
24
+ if (precision < 1)
25
+ throw new RangeError(`Invalid precision: ${precision}`);
26
+ precision = Math.min(12, precision);
27
+ let latMin = -90, latMax = 90;
28
+ let lonMin = -180, lonMax = 180;
29
+ let hash = '';
30
+ let bit = 0;
31
+ let ch = 0;
32
+ let isLon = true;
33
+ while (hash.length < precision) {
34
+ if (isLon) {
35
+ const mid = (lonMin + lonMax) / 2;
36
+ if (lon >= mid) {
37
+ ch |= 1 << (4 - bit);
38
+ lonMin = mid;
39
+ }
40
+ else {
41
+ lonMax = mid;
42
+ }
43
+ }
44
+ else {
45
+ const mid = (latMin + latMax) / 2;
46
+ if (lat >= mid) {
47
+ ch |= 1 << (4 - bit);
48
+ latMin = mid;
49
+ }
50
+ else {
51
+ latMax = mid;
52
+ }
53
+ }
54
+ isLon = !isLon;
55
+ bit++;
56
+ if (bit === 5) {
57
+ hash += BASE32[ch];
58
+ bit = 0;
59
+ ch = 0;
60
+ }
61
+ }
62
+ return hash;
63
+ }
64
+ /** Decode a geohash to its centre point with error margins. */
65
+ export function decode(hash) {
66
+ if (hash.length === 0)
67
+ throw new TypeError('Cannot decode an empty geohash');
68
+ const b = bounds(hash);
69
+ return {
70
+ lat: (b.minLat + b.maxLat) / 2,
71
+ lon: (b.minLon + b.maxLon) / 2,
72
+ error: {
73
+ lat: (b.maxLat - b.minLat) / 2,
74
+ lon: (b.maxLon - b.minLon) / 2,
75
+ },
76
+ };
77
+ }
78
+ /** Get the bounding rectangle of a geohash cell. */
79
+ export function bounds(hash) {
80
+ validateGeohash(hash);
81
+ let minLat = -90, maxLat = 90;
82
+ let minLon = -180, maxLon = 180;
83
+ let isLon = true;
84
+ for (const ch of hash) {
85
+ const bits = BASE32_DECODE[ch];
86
+ for (let bit = 4; bit >= 0; bit--) {
87
+ if (isLon) {
88
+ const mid = (minLon + maxLon) / 2;
89
+ if ((bits >> bit) & 1)
90
+ minLon = mid;
91
+ else
92
+ maxLon = mid;
93
+ }
94
+ else {
95
+ const mid = (minLat + maxLat) / 2;
96
+ if ((bits >> bit) & 1)
97
+ minLat = mid;
98
+ else
99
+ maxLat = mid;
100
+ }
101
+ isLon = !isLon;
102
+ }
103
+ }
104
+ return { minLat, maxLat, minLon, maxLon };
105
+ }
106
+ /** Get the 32 children of a geohash at the next precision level. */
107
+ export function children(hash) {
108
+ validateGeohash(hash);
109
+ return Array.from(BASE32, (ch) => hash + ch);
110
+ }
111
+ // --- Matching ---
112
+ /** Check if two geohashes overlap (bidirectional prefix containment). */
113
+ export function contains(a, b) {
114
+ return a.startsWith(b) || b.startsWith(a);
115
+ }
116
+ /** Check if a geohash matches any candidate in a multi-precision set. */
117
+ export function matchesAny(hash, candidates) {
118
+ return candidates.some(c => contains(hash, c));
119
+ }
120
+ // --- Neighbours ---
121
+ /** Get a single adjacent geohash cell in the given direction. */
122
+ export function neighbour(hash, direction) {
123
+ const b = bounds(hash);
124
+ const latHeight = b.maxLat - b.minLat;
125
+ const lonWidth = b.maxLon - b.minLon;
126
+ const centreLat = (b.minLat + b.maxLat) / 2;
127
+ const centreLon = (b.minLon + b.maxLon) / 2;
128
+ let dLat = 0;
129
+ let dLon = 0;
130
+ if (direction.includes('n'))
131
+ dLat = latHeight;
132
+ if (direction.includes('s'))
133
+ dLat = -latHeight;
134
+ if (direction.includes('e'))
135
+ dLon = lonWidth;
136
+ if (direction.includes('w'))
137
+ dLon = -lonWidth;
138
+ let newLat = centreLat + dLat;
139
+ let newLon = centreLon + dLon;
140
+ // Wrap longitude around the antimeridian
141
+ if (newLon > 180)
142
+ newLon -= 360;
143
+ if (newLon < -180)
144
+ newLon += 360;
145
+ // Clamp latitude at poles (no wrapping)
146
+ newLat = Math.max(-89.99999, Math.min(89.99999, newLat));
147
+ return encode(newLat, newLon, hash.length);
148
+ }
149
+ /** Get all 8 adjacent geohash cells. */
150
+ export function neighbours(hash) {
151
+ return {
152
+ n: neighbour(hash, 'n'),
153
+ ne: neighbour(hash, 'ne'),
154
+ e: neighbour(hash, 'e'),
155
+ se: neighbour(hash, 'se'),
156
+ s: neighbour(hash, 's'),
157
+ sw: neighbour(hash, 'sw'),
158
+ w: neighbour(hash, 'w'),
159
+ nw: neighbour(hash, 'nw'),
160
+ };
161
+ }
162
+ // --- Distance ---
163
+ const EARTH_RADIUS_M = 6_371_000; // Earth mean radius in metres
164
+ /** Haversine distance in metres between two coordinate pairs. */
165
+ export function distanceFromCoords(lat1, lon1, lat2, lon2) {
166
+ if (!Number.isFinite(lat1) || !Number.isFinite(lon1) || !Number.isFinite(lat2) || !Number.isFinite(lon2)) {
167
+ throw new RangeError('All coordinate arguments must be finite numbers');
168
+ }
169
+ const toRad = (deg) => (deg * Math.PI) / 180;
170
+ const dLat = toRad(lat2 - lat1);
171
+ const dLon = toRad(lon2 - lon1);
172
+ const a = Math.sin(dLat / 2) ** 2 +
173
+ Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.sin(dLon / 2) ** 2;
174
+ return EARTH_RADIUS_M * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
175
+ }
176
+ /** Haversine distance in metres between centres of two geohash cells. */
177
+ export function distance(hashA, hashB) {
178
+ const a = decode(hashA);
179
+ const b = decode(hashB);
180
+ return distanceFromCoords(a.lat, a.lon, b.lat, b.lon);
181
+ }
182
+ // --- Precision ↔ Radius ---
183
+ // Approximate cell half-diagonal in metres at each precision level (equator).
184
+ const PRECISION_RADIUS_M = [
185
+ /* 0 (unused) */ 0,
186
+ /* 1 */ 2_500_000,
187
+ /* 2 */ 630_000,
188
+ /* 3 */ 78_000,
189
+ /* 4 */ 20_000,
190
+ /* 5 */ 2_400,
191
+ /* 6 */ 610,
192
+ /* 7 */ 76,
193
+ /* 8 */ 19,
194
+ /* 9 */ 2.4,
195
+ ];
196
+ /** Optimal geohash precision for a given search radius in metres. */
197
+ export function radiusToPrecision(metres) {
198
+ if (!Number.isFinite(metres) || metres < 0)
199
+ throw new RangeError(`Invalid radius: ${metres}`);
200
+ for (let p = 1; p <= 9; p++) {
201
+ if (PRECISION_RADIUS_M[p] <= metres)
202
+ return p;
203
+ }
204
+ return 9;
205
+ }
206
+ /** Approximate cell radius in metres for a given precision level. */
207
+ export function precisionToRadius(precision) {
208
+ if (!Number.isFinite(precision))
209
+ throw new RangeError(`Invalid precision: ${precision}`);
210
+ const p = Math.max(1, Math.min(9, Math.round(precision)));
211
+ return PRECISION_RADIUS_M[p];
212
+ }
213
+ //# sourceMappingURL=core.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core.js","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,86 @@
1
+ import type { GeohashBounds } from './core.js';
2
+ import type { PolygonInput } from './geojson.js';
3
+ export type { GeohashBounds } from './core.js';
4
+ export type { PolygonInput, GeoJSONPolygon, GeoJSONMultiPolygon } from './geojson.js';
5
+ /**
6
+ * Ray-casting algorithm: test whether a point [x, y] lies inside a polygon.
7
+ * Polygon is an array of [x, y] vertices (closed automatically).
8
+ */
9
+ export declare function pointInPolygon(point: [number, number], polygon: [number, number][]): boolean;
10
+ /**
11
+ * Test whether all four corners of bounds lie inside the polygon.
12
+ */
13
+ export declare function boundsFullyInsidePolygon(bounds: GeohashBounds, polygon: [number, number][]): boolean;
14
+ /**
15
+ * Test whether a bounds rectangle overlaps a polygon at all.
16
+ * Checks: (1) any bounds corner inside polygon, (2) any polygon vertex inside bounds,
17
+ * (3) any edge intersection.
18
+ */
19
+ export declare function boundsOverlapsPolygon(bounds: GeohashBounds, polygon: [number, number][]): boolean;
20
+ export interface CoverageOptions {
21
+ minPrecision?: number;
22
+ maxPrecision?: number;
23
+ maxCells?: number;
24
+ mergeThreshold?: number;
25
+ }
26
+ /**
27
+ * Convert a polygon (array of [lon, lat] vertices) to an efficient set of
28
+ * multi-precision geohash strings using recursive subdivision.
29
+ *
30
+ * Edges always subdivide to maxPrecision for a tight boundary. Interior
31
+ * cells use the coarsest precision allowed by mergeThreshold. If the result
32
+ * exceeds maxCells, maxPrecision is stepped down until it fits.
33
+ *
34
+ * Throws RangeError if the polygon cannot be covered within maxCells at
35
+ * the given minPrecision.
36
+ *
37
+ * **Antimeridian:** polygons crossing ±180° longitude are not supported.
38
+ * Split at the antimeridian and cover each half separately.
39
+ */
40
+ export declare function polygonToGeohashes(input: PolygonInput, options?: CoverageOptions): string[];
41
+ /**
42
+ * Compute a convex hull polygon from an array of geohash strings.
43
+ * Collects all unique cell corners, then builds the hull using
44
+ * Andrew's monotone chain algorithm.
45
+ * Returns `[lon, lat][]`.
46
+ *
47
+ * **Antimeridian:** throws if the input hashes straddle ±180° longitude.
48
+ * Dateline-crossing hulls cannot be consumed by planar geometry functions
49
+ * (`pointInPolygon`, `polygonToGeohashes`). Split hash sets at the
50
+ * antimeridian and compute separate hulls for each side.
51
+ */
52
+ export declare function geohashesToConvexHull(hashes: string[]): [number, number][];
53
+ export interface DeduplicateOptions {
54
+ /**
55
+ * Allow near-complete sibling merges (30/32) for a smaller result array.
56
+ * Trades a tiny boundary overshoot for fewer cells. Default: `false` (exact).
57
+ */
58
+ lossy?: boolean;
59
+ }
60
+ /**
61
+ * Remove redundant geohashes and merge sibling groups.
62
+ * 1. Remove any geohash whose ancestor (shorter prefix) is already in the set.
63
+ * 2. Merge sibling sets bottom-up — exact (all 32) by default, or
64
+ * near-complete (≥30/32) when `lossy: true`.
65
+ */
66
+ export declare function deduplicateGeohashes(hashes: string[], options?: DeduplicateOptions): string[];
67
+ export interface GeohashGeoJSON {
68
+ type: 'FeatureCollection';
69
+ features: {
70
+ type: 'Feature';
71
+ geometry: {
72
+ type: 'Polygon';
73
+ coordinates: [number, number][][];
74
+ };
75
+ properties: {
76
+ geohash: string;
77
+ precision: number;
78
+ };
79
+ }[];
80
+ }
81
+ /**
82
+ * Convert an array of geohash strings to a GeoJSON FeatureCollection
83
+ * of polygon rectangles, suitable for rendering on a MapLibre map.
84
+ */
85
+ export declare function geohashesToGeoJSON(hashes: string[]): GeohashGeoJSON;
86
+ //# sourceMappingURL=coverage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coverage.d.ts","sourceRoot":"","sources":["../src/coverage.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAA;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAuC,MAAM,cAAc,CAAA;AAGrF,YAAY,EAAE,aAAa,EAAE,MAAM,WAAW,CAAA;AAC9C,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAA;AAgBrF;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EACvB,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,GAC1B,OAAO,CAcT;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,GAC1B,OAAO,CAwBT;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,GAC1B,OAAO,CAoCT;AAoCD,MAAM,WAAW,eAAe;IAC9B,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AA+CD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,YAAY,EACnB,OAAO,GAAE,eAAoB,GAC5B,MAAM,EAAE,CAmHV;AAyKD;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CA8D1E;AAID,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,OAAO,GAAE,kBAAuB,GAAG,MAAM,EAAE,CAgBjG;AAID,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,mBAAmB,CAAA;IACzB,QAAQ,EAAE;QACR,IAAI,EAAE,SAAS,CAAA;QACf,QAAQ,EAAE;YAAE,IAAI,EAAE,SAAS,CAAC;YAAC,WAAW,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,EAAE,CAAA;SAAE,CAAA;QAChE,UAAU,EAAE;YAAE,OAAO,EAAE,MAAM,CAAC;YAAC,SAAS,EAAE,MAAM,CAAA;SAAE,CAAA;KACnD,EAAE,CAAA;CACJ;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,cAAc,CAqBnE"}
@@ -0,0 +1,555 @@
1
+ // geohash-kit/coverage — polygon-to-geohash coverage, GeoJSON, convex hull
2
+ import { bounds as geohashBounds, children as geohashChildren } from './core.js';
3
+ // --- Validation ---
4
+ const BASE32 = '0123456789bcdefghjkmnpqrstuvwxyz';
5
+ function validateGeohash(hash) {
6
+ for (const ch of hash) {
7
+ if (!BASE32.includes(ch)) {
8
+ throw new TypeError(`Invalid geohash character: '${ch}' in "${hash}"`);
9
+ }
10
+ }
11
+ }
12
+ // --- Point-in-polygon (ray-casting) ---
13
+ /**
14
+ * Ray-casting algorithm: test whether a point [x, y] lies inside a polygon.
15
+ * Polygon is an array of [x, y] vertices (closed automatically).
16
+ */
17
+ export function pointInPolygon(point, polygon) {
18
+ const [px, py] = point;
19
+ let inside = false;
20
+ for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
21
+ const [xi, yi] = polygon[i];
22
+ const [xj, yj] = polygon[j];
23
+ if ((yi > py) !== (yj > py) && px < ((xj - xi) * (py - yi)) / (yj - yi) + xi) {
24
+ inside = !inside;
25
+ }
26
+ }
27
+ return inside;
28
+ }
29
+ /**
30
+ * Test whether all four corners of bounds lie inside the polygon.
31
+ */
32
+ export function boundsFullyInsidePolygon(bounds, polygon) {
33
+ const corners = [
34
+ [bounds.minLon, bounds.minLat],
35
+ [bounds.maxLon, bounds.minLat],
36
+ [bounds.maxLon, bounds.maxLat],
37
+ [bounds.minLon, bounds.maxLat],
38
+ ];
39
+ if (!corners.every((c) => pointInPolygon(c, polygon)))
40
+ return false;
41
+ // For concave polygons, all corners can be inside while the polygon edge
42
+ // cuts through the cell. If any polygon edge intersects a cell edge, the
43
+ // cell is not fully inside.
44
+ const boundsEdges = [
45
+ [corners[0], corners[1]],
46
+ [corners[1], corners[2]],
47
+ [corners[2], corners[3]],
48
+ [corners[3], corners[0]],
49
+ ];
50
+ for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
51
+ for (const be of boundsEdges) {
52
+ if (segmentsIntersect(be[0], be[1], polygon[j], polygon[i]))
53
+ return false;
54
+ }
55
+ }
56
+ return true;
57
+ }
58
+ /**
59
+ * Test whether a bounds rectangle overlaps a polygon at all.
60
+ * Checks: (1) any bounds corner inside polygon, (2) any polygon vertex inside bounds,
61
+ * (3) any edge intersection.
62
+ */
63
+ export function boundsOverlapsPolygon(bounds, polygon) {
64
+ const corners = [
65
+ [bounds.minLon, bounds.minLat],
66
+ [bounds.maxLon, bounds.minLat],
67
+ [bounds.maxLon, bounds.maxLat],
68
+ [bounds.minLon, bounds.maxLat],
69
+ ];
70
+ // Any bounds corner inside polygon?
71
+ if (corners.some((c) => pointInPolygon(c, polygon)))
72
+ return true;
73
+ // Any polygon vertex inside bounds?
74
+ for (const [px, py] of polygon) {
75
+ if (px >= bounds.minLon && px <= bounds.maxLon && py >= bounds.minLat && py <= bounds.maxLat) {
76
+ return true;
77
+ }
78
+ }
79
+ // Check edge intersections between bounds edges and polygon edges
80
+ const boundsEdges = [
81
+ [corners[0], corners[1]],
82
+ [corners[1], corners[2]],
83
+ [corners[2], corners[3]],
84
+ [corners[3], corners[0]],
85
+ ];
86
+ for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
87
+ const polyEdge = [polygon[j], polygon[i]];
88
+ for (const boundsEdge of boundsEdges) {
89
+ if (segmentsIntersect(boundsEdge[0], boundsEdge[1], polyEdge[0], polyEdge[1])) {
90
+ return true;
91
+ }
92
+ }
93
+ }
94
+ return false;
95
+ }
96
+ /** Test whether two line segments (a1->a2) and (b1->b2) intersect. */
97
+ function segmentsIntersect(a1, a2, b1, b2) {
98
+ const d1 = cross(b1, b2, a1);
99
+ const d2 = cross(b1, b2, a2);
100
+ const d3 = cross(a1, a2, b1);
101
+ const d4 = cross(a1, a2, b2);
102
+ if (((d1 > 0 && d2 < 0) || (d1 < 0 && d2 > 0)) &&
103
+ ((d3 > 0 && d4 < 0) || (d3 < 0 && d4 > 0))) {
104
+ return true;
105
+ }
106
+ if (d1 === 0 && onSegment(b1, b2, a1))
107
+ return true;
108
+ if (d2 === 0 && onSegment(b1, b2, a2))
109
+ return true;
110
+ if (d3 === 0 && onSegment(a1, a2, b1))
111
+ return true;
112
+ if (d4 === 0 && onSegment(a1, a2, b2))
113
+ return true;
114
+ return false;
115
+ }
116
+ function cross(a, b, c) {
117
+ return (b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0]);
118
+ }
119
+ function onSegment(a, b, p) {
120
+ return Math.min(a[0], b[0]) <= p[0] && p[0] <= Math.max(a[0], b[0]) &&
121
+ Math.min(a[1], b[1]) <= p[1] && p[1] <= Math.max(a[1], b[1]);
122
+ }
123
+ /** Strip closing vertex from a ring if it duplicates the first vertex. */
124
+ function stripClosingVertex(ring) {
125
+ if (ring.length > 1 &&
126
+ ring[0][0] === ring[ring.length - 1][0] &&
127
+ ring[0][1] === ring[ring.length - 1][1]) {
128
+ return ring.slice(0, -1);
129
+ }
130
+ return ring;
131
+ }
132
+ /**
133
+ * Normalise a PolygonInput to a NormalisedPolygon with outer ring and holes.
134
+ * - `[number, number][]` → outer ring only, no holes
135
+ * - GeoJSON Polygon → outer ring + inner rings (holes) extracted
136
+ * - GeoJSON MultiPolygon → not handled here (caller processes each polygon separately)
137
+ */
138
+ function normalisePolygonInput(input) {
139
+ if (Array.isArray(input)) {
140
+ return { outer: input, holes: [] };
141
+ }
142
+ if (input.type === 'Polygon') {
143
+ const ring = input.coordinates[0];
144
+ if (!ring || ring.length === 0) {
145
+ throw new Error('GeoJSON Polygon has no outer ring');
146
+ }
147
+ const outer = stripClosingVertex(ring);
148
+ // Extract inner rings (holes), stripping closing vertices and ignoring degenerate rings
149
+ const holes = [];
150
+ for (let i = 1; i < input.coordinates.length; i++) {
151
+ const holeRing = stripClosingVertex(input.coordinates[i]);
152
+ if (holeRing.length >= 3) {
153
+ holes.push(holeRing);
154
+ }
155
+ }
156
+ return { outer, holes };
157
+ }
158
+ throw new Error(`Unsupported input type: ${input.type}`);
159
+ }
160
+ /**
161
+ * Convert a polygon (array of [lon, lat] vertices) to an efficient set of
162
+ * multi-precision geohash strings using recursive subdivision.
163
+ *
164
+ * Edges always subdivide to maxPrecision for a tight boundary. Interior
165
+ * cells use the coarsest precision allowed by mergeThreshold. If the result
166
+ * exceeds maxCells, maxPrecision is stepped down until it fits.
167
+ *
168
+ * Throws RangeError if the polygon cannot be covered within maxCells at
169
+ * the given minPrecision.
170
+ *
171
+ * **Antimeridian:** polygons crossing ±180° longitude are not supported.
172
+ * Split at the antimeridian and cover each half separately.
173
+ */
174
+ export function polygonToGeohashes(input, options = {}) {
175
+ // Parse and validate options upfront (shared by single and multi paths)
176
+ const { minPrecision: rawMin = 1, maxPrecision: rawMax = 9, maxCells = 500, mergeThreshold: rawThreshold = 1.0 } = options;
177
+ if (!Number.isFinite(rawMin))
178
+ throw new RangeError(`Invalid minPrecision: ${rawMin}`);
179
+ if (!Number.isFinite(rawMax))
180
+ throw new RangeError(`Invalid maxPrecision: ${rawMax}`);
181
+ if (!Number.isFinite(maxCells) || maxCells < 1)
182
+ throw new RangeError(`Invalid maxCells: ${maxCells}`);
183
+ if (!Number.isFinite(rawThreshold))
184
+ throw new RangeError(`Invalid mergeThreshold: ${rawThreshold}`);
185
+ const minPrecision = Math.max(1, Math.min(9, Math.round(rawMin)));
186
+ const maxPrecision = Math.max(minPrecision, Math.min(9, Math.round(rawMax)));
187
+ const threshold = Math.max(0, Math.min(1, rawThreshold));
188
+ // Handle MultiPolygon: global maxCells cap across all child polygons
189
+ if (!Array.isArray(input) && input.type === 'MultiPolygon') {
190
+ if (input.coordinates.length === 0)
191
+ return [];
192
+ // Normalise all children upfront
193
+ const children = input.coordinates.map((polyCoords) => normalisePolygonInput({ type: 'Polygon', coordinates: polyCoords }));
194
+ // Validate all children before computing
195
+ for (const { outer } of children) {
196
+ if (outer.length < 3) {
197
+ throw new Error('Polygon must have at least 3 vertices');
198
+ }
199
+ for (let i = 0; i < outer.length; i++) {
200
+ const j = (i + 1) % outer.length;
201
+ if (Math.abs(outer[i][0] - outer[j][0]) > 180) {
202
+ throw new Error('Polygons crossing the antimeridian (±180° longitude) are not supported');
203
+ }
204
+ }
205
+ }
206
+ // Retry loop from maxPrecision down to minPrecision
207
+ const bailout = maxCells * 4;
208
+ for (let mp = maxPrecision; mp >= minPrecision; mp--) {
209
+ const allHashes = [];
210
+ let bailed = false;
211
+ for (const { outer, holes } of children) {
212
+ const result = computeGeohashes(outer, holes, minPrecision, mp, threshold, bailout);
213
+ if (result === null) {
214
+ bailed = true;
215
+ break;
216
+ }
217
+ allHashes.push(...result);
218
+ }
219
+ if (bailed)
220
+ continue;
221
+ const merged = deduplicateGeohashes(allHashes);
222
+ if (merged.length <= maxCells)
223
+ return merged;
224
+ }
225
+ // Fallback: minPrecision with threshold=0
226
+ const fallbackAll = [];
227
+ for (const { outer, holes } of children) {
228
+ const result = computeGeohashes(outer, holes, minPrecision, minPrecision, 0) ?? [];
229
+ fallbackAll.push(...result);
230
+ }
231
+ const fallback = deduplicateGeohashes(fallbackAll);
232
+ if (fallback.length <= maxCells)
233
+ return fallback;
234
+ throw new RangeError(`MultiPolygon requires at least ${fallback.length} cells at precision ${minPrecision}, but maxCells is ${maxCells}. ` +
235
+ 'Increase maxCells or reduce the polygon area.');
236
+ }
237
+ const { outer: polygon, holes } = normalisePolygonInput(input);
238
+ // Guard: degenerate polygons
239
+ if (polygon.length < 3) {
240
+ throw new Error('Polygon must have at least 3 vertices');
241
+ }
242
+ // Guard: antimeridian-crossing polygons are not supported
243
+ for (let i = 0; i < polygon.length; i++) {
244
+ const j = (i + 1) % polygon.length;
245
+ if (Math.abs(polygon[i][0] - polygon[j][0]) > 180) {
246
+ throw new Error('Polygons crossing the antimeridian (±180° longitude) are not supported');
247
+ }
248
+ }
249
+ // Guard: all coordinates must be within valid geographic bounds
250
+ for (const [lon, lat] of polygon) {
251
+ if (!Number.isFinite(lat) || lat < -90 || lat > 90) {
252
+ throw new RangeError(`Invalid latitude in polygon: ${lat}`);
253
+ }
254
+ if (!Number.isFinite(lon) || lon < -180 || lon > 180) {
255
+ throw new RangeError(`Invalid longitude in polygon: ${lon}`);
256
+ }
257
+ }
258
+ for (const hole of holes) {
259
+ for (const [lon, lat] of hole) {
260
+ if (!Number.isFinite(lat) || lat < -90 || lat > 90) {
261
+ throw new RangeError(`Invalid latitude in hole: ${lat}`);
262
+ }
263
+ if (!Number.isFinite(lon) || lon < -180 || lon > 180) {
264
+ throw new RangeError(`Invalid longitude in hole: ${lon}`);
265
+ }
266
+ }
267
+ }
268
+ // Early bailout limit
269
+ const bailout = maxCells * 4;
270
+ // Try at requested maxPrecision, stepping down if too many cells
271
+ for (let mp = maxPrecision; mp >= minPrecision; mp--) {
272
+ const result = computeGeohashes(polygon, holes, minPrecision, mp, threshold, bailout);
273
+ if (result !== null && result.length <= maxCells)
274
+ return result;
275
+ }
276
+ // Fallback: minPrecision with threshold=0
277
+ const fallback = computeGeohashes(polygon, holes, minPrecision, minPrecision, 0) ?? [];
278
+ if (fallback.length <= maxCells)
279
+ return fallback;
280
+ throw new RangeError(`Polygon requires at least ${fallback.length} cells at precision ${minPrecision}, but maxCells is ${maxCells}. ` +
281
+ 'Increase maxCells or reduce the polygon area.');
282
+ }
283
+ /**
284
+ * Compute geohashes covering a polygon via greedy recursive subdivision.
285
+ *
286
+ * - Edges always subdivide to maxPrecision (tight boundary).
287
+ * - Interior cells emit at the coarsest precision allowed by coverageThreshold.
288
+ * threshold 1.0 → interior at maxPrecision (uniform cells, most cells).
289
+ * threshold 0.0 → interior at minPrecision (coarsest blocks, fewest cells).
290
+ * - Only fully-inside cells are emitted below maxPrecision, so no blocks
291
+ * extend outside the polygon boundary.
292
+ */
293
+ function computeGeohashes(polygon, holes, minPrecision, maxPrecision, coverageThreshold, bailout) {
294
+ const result = [];
295
+ const limit = bailout ?? Infinity;
296
+ const hasHoles = holes.length > 0;
297
+ // Pre-compute polygon AABB for fast rejection.
298
+ let polyMinLon = Infinity, polyMaxLon = -Infinity;
299
+ let polyMinLat = Infinity, polyMaxLat = -Infinity;
300
+ for (const [lon, lat] of polygon) {
301
+ if (lon < polyMinLon)
302
+ polyMinLon = lon;
303
+ if (lon > polyMaxLon)
304
+ polyMaxLon = lon;
305
+ if (lat < polyMinLat)
306
+ polyMinLat = lat;
307
+ if (lat > polyMaxLat)
308
+ polyMaxLat = lat;
309
+ }
310
+ /**
311
+ * Check if bounds are fully inside the outer polygon and not fully inside any hole.
312
+ * A cell is "fully inside" the effective polygon when:
313
+ * - all corners are inside the outer ring, AND
314
+ * - the cell does not overlap any hole
315
+ */
316
+ const isFullyInside = (b) => {
317
+ if (!boundsFullyInsidePolygon(b, polygon))
318
+ return false;
319
+ if (hasHoles) {
320
+ for (const hole of holes) {
321
+ if (boundsOverlapsPolygon(b, hole))
322
+ return false;
323
+ }
324
+ }
325
+ return true;
326
+ };
327
+ /**
328
+ * Check if bounds overlap the effective polygon (outer minus holes).
329
+ * A cell overlaps when it overlaps the outer ring AND is not fully inside any hole.
330
+ */
331
+ const doesOverlap = (b) => {
332
+ if (!boundsOverlapsPolygon(b, polygon))
333
+ return false;
334
+ if (hasHoles) {
335
+ for (const hole of holes) {
336
+ if (boundsFullyInsidePolygon(b, hole))
337
+ return false;
338
+ }
339
+ }
340
+ return true;
341
+ };
342
+ // Interior cells must reach at least this precision before being emitted.
343
+ // At threshold 1.0 → all cells at maxPrecision (uniform).
344
+ // At threshold 0.0 → interior cells as coarse as minPrecision.
345
+ const interiorMinPrecision = Math.ceil(minPrecision + (maxPrecision - minPrecision) * coverageThreshold);
346
+ // Seed: precision-1 cells filtered by AABB then polygon overlap.
347
+ const queue = geohashChildren('').filter((hash) => {
348
+ const b = geohashBounds(hash);
349
+ if (b.maxLon < polyMinLon || b.minLon > polyMaxLon ||
350
+ b.maxLat < polyMinLat || b.minLat > polyMaxLat)
351
+ return false;
352
+ return doesOverlap(b);
353
+ });
354
+ while (queue.length > 0) {
355
+ const hash = queue.pop();
356
+ const b = geohashBounds(hash);
357
+ if (isFullyInside(b)) {
358
+ if (hash.length >= interiorMinPrecision) {
359
+ result.push(hash);
360
+ }
361
+ else {
362
+ for (const child of geohashChildren(hash)) {
363
+ queue.push(child);
364
+ }
365
+ }
366
+ }
367
+ else if (hash.length >= maxPrecision) {
368
+ // At max precision: only emit if it overlaps the effective polygon
369
+ if (doesOverlap(b)) {
370
+ result.push(hash);
371
+ }
372
+ }
373
+ else {
374
+ for (const child of geohashChildren(hash)) {
375
+ const cb = geohashBounds(child);
376
+ // Fast AABB rejection before expensive polygon checks
377
+ if (cb.maxLon < polyMinLon || cb.minLon > polyMaxLon ||
378
+ cb.maxLat < polyMinLat || cb.minLat > polyMaxLat)
379
+ continue;
380
+ if (isFullyInside(cb)) {
381
+ if (child.length >= interiorMinPrecision) {
382
+ result.push(child);
383
+ }
384
+ else {
385
+ queue.push(child);
386
+ }
387
+ }
388
+ else if (doesOverlap(cb)) {
389
+ queue.push(child);
390
+ }
391
+ }
392
+ }
393
+ if (result.length > limit)
394
+ return null;
395
+ }
396
+ // Map coverageThreshold to merge aggressiveness:
397
+ // threshold 1.0 (tight) → require all 32 siblings (exact boundary)
398
+ // threshold 0.0 (loose) → require only 24 siblings (aggressive merge, fewer cells)
399
+ const minSiblings = Math.round(24 + coverageThreshold * 8);
400
+ return mergeCompleteSiblings(result, minPrecision, minSiblings).sort();
401
+ }
402
+ /**
403
+ * Post-processing merge: bottom-up consolidation of sibling sets.
404
+ * When at least `minSiblings` of 32 children of a parent are present,
405
+ * replace them with the parent. With `minSiblings < 32` this trades a
406
+ * tiny boundary overshoot for a significantly smaller result array.
407
+ * Iterates from finest to coarsest so merges can cascade.
408
+ */
409
+ function mergeCompleteSiblings(hashes, minPrecision, minSiblings = 30) {
410
+ const set = new Set(hashes);
411
+ let maxP = 0;
412
+ for (const h of set) {
413
+ if (h.length > maxP)
414
+ maxP = h.length;
415
+ }
416
+ for (let p = maxP; p > minPrecision; p--) {
417
+ const parentCounts = new Map();
418
+ for (const h of set) {
419
+ if (h.length === p) {
420
+ const parent = h.slice(0, -1);
421
+ parentCounts.set(parent, (parentCounts.get(parent) ?? 0) + 1);
422
+ }
423
+ }
424
+ for (const [parent, count] of parentCounts) {
425
+ if (count >= minSiblings) {
426
+ // Near-complete or complete — replace children with parent
427
+ for (const ch of geohashChildren(parent)) {
428
+ set.delete(ch);
429
+ }
430
+ set.add(parent);
431
+ }
432
+ }
433
+ }
434
+ return Array.from(set);
435
+ }
436
+ // --- geohashesToConvexHull — reconstruct editable polygon ---
437
+ /** 2D cross product of vectors OA and OB where O is the origin. */
438
+ function cross2D(o, a, b) {
439
+ return (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]);
440
+ }
441
+ /**
442
+ * Compute a convex hull polygon from an array of geohash strings.
443
+ * Collects all unique cell corners, then builds the hull using
444
+ * Andrew's monotone chain algorithm.
445
+ * Returns `[lon, lat][]`.
446
+ *
447
+ * **Antimeridian:** throws if the input hashes straddle ±180° longitude.
448
+ * Dateline-crossing hulls cannot be consumed by planar geometry functions
449
+ * (`pointInPolygon`, `polygonToGeohashes`). Split hash sets at the
450
+ * antimeridian and compute separate hulls for each side.
451
+ */
452
+ export function geohashesToConvexHull(hashes) {
453
+ if (hashes.length === 0)
454
+ return [];
455
+ // Collect unique corners from all geohash bounding boxes
456
+ const seen = new Set();
457
+ const points = [];
458
+ for (const hash of hashes) {
459
+ const b = geohashBounds(hash);
460
+ const corners = [
461
+ [b.minLon, b.minLat],
462
+ [b.maxLon, b.minLat],
463
+ [b.maxLon, b.maxLat],
464
+ [b.minLon, b.maxLat],
465
+ ];
466
+ for (const c of corners) {
467
+ const key = `${c[0]},${c[1]}`;
468
+ if (!seen.has(key)) {
469
+ seen.add(key);
470
+ points.push(c);
471
+ }
472
+ }
473
+ }
474
+ if (points.length < 3)
475
+ return points;
476
+ // Guard: antimeridian-straddling hashes produce hulls that break planar geometry
477
+ const lons = points.map(p => p[0]);
478
+ if (Math.max(...lons) - Math.min(...lons) > 180) {
479
+ throw new Error('Geohashes straddle the antimeridian (±180° longitude). ' +
480
+ 'Split into separate sets and compute hulls independently.');
481
+ }
482
+ // Sort by x then y
483
+ points.sort((a, b) => a[0] - b[0] || a[1] - b[1]);
484
+ // Andrew's monotone chain — lower hull
485
+ const lower = [];
486
+ for (const p of points) {
487
+ while (lower.length >= 2 && cross2D(lower[lower.length - 2], lower[lower.length - 1], p) <= 0) {
488
+ lower.pop();
489
+ }
490
+ lower.push(p);
491
+ }
492
+ // Upper hull
493
+ const upper = [];
494
+ for (let i = points.length - 1; i >= 0; i--) {
495
+ const p = points[i];
496
+ while (upper.length >= 2 && cross2D(upper[upper.length - 2], upper[upper.length - 1], p) <= 0) {
497
+ upper.pop();
498
+ }
499
+ upper.push(p);
500
+ }
501
+ // Remove last point of each half because it's repeated
502
+ lower.pop();
503
+ upper.pop();
504
+ return [...lower, ...upper];
505
+ }
506
+ /**
507
+ * Remove redundant geohashes and merge sibling groups.
508
+ * 1. Remove any geohash whose ancestor (shorter prefix) is already in the set.
509
+ * 2. Merge sibling sets bottom-up — exact (all 32) by default, or
510
+ * near-complete (≥30/32) when `lossy: true`.
511
+ */
512
+ export function deduplicateGeohashes(hashes, options = {}) {
513
+ // Validate all input hashes
514
+ for (const h of hashes)
515
+ validateGeohash(h);
516
+ // Step 1: remove children when ancestor is present
517
+ const set = new Set(hashes);
518
+ const filtered = Array.from(set).filter((h) => {
519
+ for (let len = 1; len < h.length; len++) {
520
+ if (set.has(h.slice(0, len)))
521
+ return false;
522
+ }
523
+ return true;
524
+ });
525
+ // Step 2: merge sibling groups bottom-up
526
+ const minSiblings = options.lossy ? 30 : 32;
527
+ return mergeCompleteSiblings(filtered, 1, minSiblings).sort();
528
+ }
529
+ /**
530
+ * Convert an array of geohash strings to a GeoJSON FeatureCollection
531
+ * of polygon rectangles, suitable for rendering on a MapLibre map.
532
+ */
533
+ export function geohashesToGeoJSON(hashes) {
534
+ return {
535
+ type: 'FeatureCollection',
536
+ features: hashes.map((hash) => {
537
+ const b = geohashBounds(hash);
538
+ return {
539
+ type: 'Feature',
540
+ geometry: {
541
+ type: 'Polygon',
542
+ coordinates: [[
543
+ [b.minLon, b.minLat],
544
+ [b.maxLon, b.minLat],
545
+ [b.maxLon, b.maxLat],
546
+ [b.minLon, b.maxLat],
547
+ [b.minLon, b.minLat],
548
+ ]],
549
+ },
550
+ properties: { geohash: hash, precision: hash.length },
551
+ };
552
+ }),
553
+ };
554
+ }
555
+ //# sourceMappingURL=coverage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coverage.js","sourceRoot":"","sources":["../src/coverage.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /** GeoJSON Polygon geometry. Coordinates are [lon, lat] rings; first ring is the outer boundary. */
2
+ export interface GeoJSONPolygon {
3
+ type: 'Polygon';
4
+ coordinates: number[][][];
5
+ }
6
+ /** GeoJSON MultiPolygon geometry. Each element of coordinates is a Polygon's coordinate array. */
7
+ export interface GeoJSONMultiPolygon {
8
+ type: 'MultiPolygon';
9
+ coordinates: number[][][][];
10
+ }
11
+ /** Input types accepted by polygonToGeohashes. */
12
+ export type PolygonInput = [number, number][] | GeoJSONPolygon | GeoJSONMultiPolygon;
13
+ //# sourceMappingURL=geojson.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"geojson.d.ts","sourceRoot":"","sources":["../src/geojson.ts"],"names":[],"mappings":"AAEA,oGAAoG;AACpG,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,SAAS,CAAA;IACf,WAAW,EAAE,MAAM,EAAE,EAAE,EAAE,CAAA;CAC1B;AAED,kGAAkG;AAClG,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,cAAc,CAAA;IACpB,WAAW,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,CAAA;CAC5B;AAED,kDAAkD;AAClD,MAAM,MAAM,YAAY,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,cAAc,GAAG,mBAAmB,CAAA"}
@@ -0,0 +1,3 @@
1
+ // src/geojson.ts — minimal GeoJSON geometry types (zero-dependency alternative to @types/geojson)
2
+ export {};
3
+ //# sourceMappingURL=geojson.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"geojson.js","sourceRoot":"","sources":["../src/geojson.ts"],"names":[],"mappings":"AAAA,kGAAkG"}
@@ -0,0 +1,4 @@
1
+ export { encode, decode, bounds, children, contains, matchesAny, neighbour, neighbours, distance, distanceFromCoords, radiusToPrecision, precisionToRadius, type GeohashBounds, type Direction, } from './core.js';
2
+ export { pointInPolygon, boundsOverlapsPolygon, boundsFullyInsidePolygon, polygonToGeohashes, geohashesToGeoJSON, geohashesToConvexHull, deduplicateGeohashes, type CoverageOptions, type DeduplicateOptions, type GeohashGeoJSON, type PolygonInput, type GeoJSONPolygon, type GeoJSONMultiPolygon, } from './coverage.js';
3
+ export { createGTagLadder, createGTagFilter, createGTagFilterFromGeohashes, expandRings, nearbyFilter, parseGTags, bestGeohash, } from './nostr.js';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,EACtD,SAAS,EAAE,UAAU,EACrB,QAAQ,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,iBAAiB,EAClE,KAAK,aAAa,EAAE,KAAK,SAAS,GACnC,MAAM,WAAW,CAAA;AAElB,OAAO,EACL,cAAc,EAAE,qBAAqB,EAAE,wBAAwB,EAC/D,kBAAkB,EAAE,kBAAkB,EAAE,qBAAqB,EAC7D,oBAAoB,EACpB,KAAK,eAAe,EAAE,KAAK,kBAAkB,EAAE,KAAK,cAAc,EAClE,KAAK,YAAY,EAAE,KAAK,cAAc,EAAE,KAAK,mBAAmB,GACjE,MAAM,eAAe,CAAA;AAEtB,OAAO,EACL,gBAAgB,EAAE,gBAAgB,EAAE,6BAA6B,EACjE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,WAAW,GACnD,MAAM,YAAY,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export { encode, decode, bounds, children, contains, matchesAny, neighbour, neighbours, distance, distanceFromCoords, radiusToPrecision, precisionToRadius, } from './core.js';
2
+ export { pointInPolygon, boundsOverlapsPolygon, boundsFullyInsidePolygon, polygonToGeohashes, geohashesToGeoJSON, geohashesToConvexHull, deduplicateGeohashes, } from './coverage.js';
3
+ export { createGTagLadder, createGTagFilter, createGTagFilterFromGeohashes, expandRings, nearbyFilter, parseGTags, bestGeohash, } from './nostr.js';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,EACtD,SAAS,EAAE,UAAU,EACrB,QAAQ,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,iBAAiB,GAEnE,MAAM,WAAW,CAAA;AAElB,OAAO,EACL,cAAc,EAAE,qBAAqB,EAAE,wBAAwB,EAC/D,kBAAkB,EAAE,kBAAkB,EAAE,qBAAqB,EAC7D,oBAAoB,GAGrB,MAAM,eAAe,CAAA;AAEtB,OAAO,EACL,gBAAgB,EAAE,gBAAgB,EAAE,6BAA6B,EACjE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,WAAW,GACnD,MAAM,YAAY,CAAA"}
@@ -0,0 +1,31 @@
1
+ /** Generate multi-precision g-tag ladder for Nostr event publishing. */
2
+ export declare function createGTagLadder(geohash: string, minPrecision?: number): string[][];
3
+ /**
4
+ * Extract and parse g tags from a Nostr event's tag array.
5
+ * Invalid geohashes (containing non-base32 characters) are silently filtered out,
6
+ * since relay data is untrusted. Empty strings are also excluded.
7
+ */
8
+ export declare function parseGTags(tags: string[][]): Array<{
9
+ geohash: string;
10
+ precision: number;
11
+ }>;
12
+ /** Return the highest-precision g tag from an event's tag array. */
13
+ export declare function bestGeohash(tags: string[][]): string | undefined;
14
+ /** Expand geohash into concentric rings of neighbours. */
15
+ export declare function expandRings(hash: string, rings?: number): string[][];
16
+ /** Generate a #g filter for Nostr REQ from coordinates and radius. */
17
+ export declare function createGTagFilter(lat: number, lon: number, radiusMetres: number): {
18
+ '#g': string[];
19
+ };
20
+ /** Generate a #g filter from an existing geohash set. */
21
+ export declare function createGTagFilterFromGeohashes(hashes: string[]): {
22
+ '#g': string[];
23
+ };
24
+ /** Convenience: encode + expand rings + flatten to filter. */
25
+ export declare function nearbyFilter(lat: number, lon: number, options?: {
26
+ precision?: number;
27
+ rings?: number;
28
+ }): {
29
+ '#g': string[];
30
+ };
31
+ //# sourceMappingURL=nostr.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nostr.d.ts","sourceRoot":"","sources":["../src/nostr.ts"],"names":[],"mappings":"AAkBA,wEAAwE;AACxE,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,SAAI,GAAG,MAAM,EAAE,EAAE,CAM9E;AAID;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,KAAK,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,CAI1F;AAED,oEAAoE;AACpE,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,MAAM,GAAG,SAAS,CAIhE;AAID,0DAA0D;AAC1D,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,SAAI,GAAG,MAAM,EAAE,EAAE,CAqB/D;AAID,sEAAsE;AACtE,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,EACX,YAAY,EAAE,MAAM,GACnB;IAAE,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE,CASpB;AAED,yDAAyD;AACzD,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG;IAAE,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE,CAElF;AAED,8DAA8D;AAC9D,wBAAgB,YAAY,CAC1B,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAC/C;IAAE,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE,CAOpB"}
package/dist/nostr.js ADDED
@@ -0,0 +1,87 @@
1
+ // geohash-kit/nostr — Nostr g-tag utilities
2
+ import { encode, neighbours, radiusToPrecision } from './core.js';
3
+ // --- Validation ---
4
+ const BASE32 = '0123456789bcdefghjkmnpqrstuvwxyz';
5
+ function isValidGeohash(hash) {
6
+ if (hash.length === 0)
7
+ return false;
8
+ for (const ch of hash) {
9
+ if (!BASE32.includes(ch))
10
+ return false;
11
+ }
12
+ return true;
13
+ }
14
+ // --- Publishing (event tags) ---
15
+ /** Generate multi-precision g-tag ladder for Nostr event publishing. */
16
+ export function createGTagLadder(geohash, minPrecision = 1) {
17
+ const tags = [];
18
+ for (let p = Math.max(1, minPrecision); p <= geohash.length; p++) {
19
+ tags.push(['g', geohash.slice(0, p)]);
20
+ }
21
+ return tags;
22
+ }
23
+ // --- Parsing ---
24
+ /**
25
+ * Extract and parse g tags from a Nostr event's tag array.
26
+ * Invalid geohashes (containing non-base32 characters) are silently filtered out,
27
+ * since relay data is untrusted. Empty strings are also excluded.
28
+ */
29
+ export function parseGTags(tags) {
30
+ return tags
31
+ .filter((t) => t[0] === 'g' && t[1] && isValidGeohash(t[1]))
32
+ .map((t) => ({ geohash: t[1], precision: t[1].length }));
33
+ }
34
+ /** Return the highest-precision g tag from an event's tag array. */
35
+ export function bestGeohash(tags) {
36
+ const parsed = parseGTags(tags);
37
+ if (parsed.length === 0)
38
+ return undefined;
39
+ return parsed.reduce((best, curr) => curr.precision > best.precision ? curr : best).geohash;
40
+ }
41
+ // --- Ring expansion ---
42
+ /** Expand geohash into concentric rings of neighbours. */
43
+ export function expandRings(hash, rings = 1) {
44
+ const result = [[hash]];
45
+ const seen = new Set([hash]);
46
+ for (let ring = 1; ring <= rings; ring++) {
47
+ const prevRing = result[ring - 1];
48
+ const candidates = new Set();
49
+ for (const cell of prevRing) {
50
+ const n = neighbours(cell);
51
+ for (const adj of Object.values(n)) {
52
+ if (!seen.has(adj)) {
53
+ candidates.add(adj);
54
+ }
55
+ }
56
+ }
57
+ const ringCells = Array.from(candidates);
58
+ for (const c of ringCells)
59
+ seen.add(c);
60
+ result.push(ringCells);
61
+ }
62
+ return result;
63
+ }
64
+ // --- Filter generation (subscribing) ---
65
+ /** Generate a #g filter for Nostr REQ from coordinates and radius. */
66
+ export function createGTagFilter(lat, lon, radiusMetres) {
67
+ const precision = radiusToPrecision(radiusMetres);
68
+ const hash = encode(lat, lon, precision);
69
+ // Expand one ring to cover cell boundaries
70
+ const rings = expandRings(hash, 1);
71
+ const allHashes = rings.flat();
72
+ return { '#g': [...new Set(allHashes)] };
73
+ }
74
+ /** Generate a #g filter from an existing geohash set. */
75
+ export function createGTagFilterFromGeohashes(hashes) {
76
+ return { '#g': [...new Set(hashes)] };
77
+ }
78
+ /** Convenience: encode + expand rings + flatten to filter. */
79
+ export function nearbyFilter(lat, lon, options) {
80
+ const precision = options?.precision ?? 5;
81
+ const ringCount = options?.rings ?? 1;
82
+ const hash = encode(lat, lon, precision);
83
+ const rings = expandRings(hash, ringCount);
84
+ const allHashes = rings.flat();
85
+ return { '#g': [...new Set(allHashes)] };
86
+ }
87
+ //# sourceMappingURL=nostr.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nostr.js","sourceRoot":"","sources":["../src/nostr.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "geohash-kit",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "Modern TypeScript geohash toolkit — encode, decode, cover polygons, and build Nostr filters",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",