@math.gl/dggs 5.0.0-alpha.2
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/LICENSE +140 -0
- package/README.md +7 -0
- package/dist/a5-decoder.d.ts +15 -0
- package/dist/a5-decoder.d.ts.map +1 -0
- package/dist/a5-decoder.js +36 -0
- package/dist/a5-decoder.js.map +1 -0
- package/dist/a5.cjs +77 -0
- package/dist/a5.cjs.map +6 -0
- package/dist/a5.d.ts +2 -0
- package/dist/a5.d.ts.map +1 -0
- package/dist/a5.js +5 -0
- package/dist/a5.js.map +1 -0
- package/dist/converters/s2-to-obb-points.d.ts +15 -0
- package/dist/converters/s2-to-obb-points.d.ts.map +1 -0
- package/dist/converters/s2-to-obb-points.js +60 -0
- package/dist/converters/s2-to-obb-points.js.map +1 -0
- package/dist/converters/s2-to-region.d.ts +6 -0
- package/dist/converters/s2-to-region.d.ts.map +1 -0
- package/dist/converters/s2-to-region.js +27 -0
- package/dist/converters/s2-to-region.js.map +1 -0
- package/dist/dggs-decoder.d.ts +37 -0
- package/dist/dggs-decoder.d.ts.map +1 -0
- package/dist/dggs-decoder.js +28 -0
- package/dist/dggs-decoder.js.map +1 -0
- package/dist/geohash-decoder.d.ts +17 -0
- package/dist/geohash-decoder.d.ts.map +1 -0
- package/dist/geohash-decoder.js +88 -0
- package/dist/geohash-decoder.js.map +1 -0
- package/dist/geohash.cjs +108 -0
- package/dist/geohash.cjs.map +6 -0
- package/dist/geohash.d.ts +2 -0
- package/dist/geohash.d.ts.map +1 -0
- package/dist/geohash.js +5 -0
- package/dist/geohash.js.map +1 -0
- package/dist/h3-decoder.d.ts +15 -0
- package/dist/h3-decoder.d.ts.map +1 -0
- package/dist/h3-decoder.js +46 -0
- package/dist/h3-decoder.js.map +1 -0
- package/dist/h3.cjs +87 -0
- package/dist/h3.cjs.map +6 -0
- package/dist/h3.d.ts +2 -0
- package/dist/h3.d.ts.map +1 -0
- package/dist/h3.js +5 -0
- package/dist/h3.js.map +1 -0
- package/dist/index.cjs +615 -0
- package/dist/index.cjs.map +6 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +52 -0
- package/dist/index.js.map +1 -0
- package/dist/plus-code-decoder.d.ts +16 -0
- package/dist/plus-code-decoder.d.ts.map +1 -0
- package/dist/plus-code-decoder.js +50 -0
- package/dist/plus-code-decoder.js.map +1 -0
- package/dist/plus-code.cjs +88 -0
- package/dist/plus-code.cjs.map +6 -0
- package/dist/plus-code.d.ts +2 -0
- package/dist/plus-code.d.ts.map +1 -0
- package/dist/plus-code.js +5 -0
- package/dist/plus-code.js.map +1 -0
- package/dist/quadkey-decoder.d.ts +14 -0
- package/dist/quadkey-decoder.d.ts.map +1 -0
- package/dist/quadkey-decoder.js +86 -0
- package/dist/quadkey-decoder.js.map +1 -0
- package/dist/quadkey.cjs +106 -0
- package/dist/quadkey.cjs.map +6 -0
- package/dist/quadkey.d.ts +2 -0
- package/dist/quadkey.d.ts.map +1 -0
- package/dist/quadkey.js +5 -0
- package/dist/quadkey.js.map +1 -0
- package/dist/s2-decoder.d.ts +26 -0
- package/dist/s2-decoder.d.ts.map +1 -0
- package/dist/s2-decoder.js +76 -0
- package/dist/s2-decoder.js.map +1 -0
- package/dist/s2-geometry/s2-geometry.d.ts +13 -0
- package/dist/s2-geometry/s2-geometry.d.ts.map +1 -0
- package/dist/s2-geometry/s2-geometry.js +126 -0
- package/dist/s2-geometry/s2-geometry.js.map +1 -0
- package/dist/s2-geometry/s2-to-boundary.d.ts +14 -0
- package/dist/s2-geometry/s2-to-boundary.d.ts.map +1 -0
- package/dist/s2-geometry/s2-to-boundary.js +60 -0
- package/dist/s2-geometry/s2-to-boundary.js.map +1 -0
- package/dist/s2-geometry/s2-token.d.ts +13 -0
- package/dist/s2-geometry/s2-token.d.ts.map +1 -0
- package/dist/s2-geometry/s2-token.js +61 -0
- package/dist/s2-geometry/s2-token.js.map +1 -0
- package/dist/s2.cjs +278 -0
- package/dist/s2.cjs.map +6 -0
- package/dist/s2.d.ts +3 -0
- package/dist/s2.d.ts.map +1 -0
- package/dist/s2.js +6 -0
- package/dist/s2.js.map +1 -0
- package/package.json +80 -0
- package/src/a5-decoder.ts +42 -0
- package/src/a5.ts +5 -0
- package/src/converters/s2-to-obb-points.ts +78 -0
- package/src/converters/s2-to-region.ts +30 -0
- package/src/dggs-decoder.ts +71 -0
- package/src/geohash-decoder.ts +99 -0
- package/src/geohash.ts +5 -0
- package/src/h3-decoder.ts +53 -0
- package/src/h3.ts +5 -0
- package/src/index.ts +61 -0
- package/src/open-location-code.d.ts +19 -0
- package/src/plus-code-decoder.ts +56 -0
- package/src/plus-code.ts +5 -0
- package/src/quadkey-decoder.ts +98 -0
- package/src/quadkey.ts +5 -0
- package/src/s2-decoder.ts +87 -0
- package/src/s2-geometry/s2-geometry.ts +166 -0
- package/src/s2-geometry/s2-to-boundary.ts +76 -0
- package/src/s2-geometry/s2-token.ts +70 -0
- package/src/s2.ts +6 -0
package/package.json
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@math.gl/dggs",
|
|
3
|
+
"description": "Lightweight cell geometry decoders for DGGS and DGGS-like systems",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"version": "5.0.0-alpha.2",
|
|
10
|
+
"keywords": [
|
|
11
|
+
"javascript",
|
|
12
|
+
"math",
|
|
13
|
+
"geospatial",
|
|
14
|
+
"dggs",
|
|
15
|
+
"a5",
|
|
16
|
+
"h3",
|
|
17
|
+
"s2",
|
|
18
|
+
"geohash",
|
|
19
|
+
"quadkey",
|
|
20
|
+
"plus-codes"
|
|
21
|
+
],
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "https://github.com/visgl/math.gl.git"
|
|
25
|
+
},
|
|
26
|
+
"types": "dist/index.d.ts",
|
|
27
|
+
"main": "dist/index.cjs",
|
|
28
|
+
"module": "dist/index.js",
|
|
29
|
+
"exports": {
|
|
30
|
+
".": {
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
32
|
+
"import": "./dist/index.js",
|
|
33
|
+
"require": "./dist/index.cjs"
|
|
34
|
+
},
|
|
35
|
+
"./a5": {
|
|
36
|
+
"types": "./dist/a5.d.ts",
|
|
37
|
+
"import": "./dist/a5.js",
|
|
38
|
+
"require": "./dist/a5.cjs"
|
|
39
|
+
},
|
|
40
|
+
"./geohash": {
|
|
41
|
+
"types": "./dist/geohash.d.ts",
|
|
42
|
+
"import": "./dist/geohash.js",
|
|
43
|
+
"require": "./dist/geohash.cjs"
|
|
44
|
+
},
|
|
45
|
+
"./h3": {
|
|
46
|
+
"types": "./dist/h3.d.ts",
|
|
47
|
+
"import": "./dist/h3.js",
|
|
48
|
+
"require": "./dist/h3.cjs"
|
|
49
|
+
},
|
|
50
|
+
"./plus-code": {
|
|
51
|
+
"types": "./dist/plus-code.d.ts",
|
|
52
|
+
"import": "./dist/plus-code.js",
|
|
53
|
+
"require": "./dist/plus-code.cjs"
|
|
54
|
+
},
|
|
55
|
+
"./quadkey": {
|
|
56
|
+
"types": "./dist/quadkey.d.ts",
|
|
57
|
+
"import": "./dist/quadkey.js",
|
|
58
|
+
"require": "./dist/quadkey.cjs"
|
|
59
|
+
},
|
|
60
|
+
"./s2": {
|
|
61
|
+
"types": "./dist/s2.d.ts",
|
|
62
|
+
"import": "./dist/s2.js",
|
|
63
|
+
"require": "./dist/s2.cjs"
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
"files": [
|
|
67
|
+
"dist",
|
|
68
|
+
"src"
|
|
69
|
+
],
|
|
70
|
+
"dependencies": {
|
|
71
|
+
"@math.gl/types": "5.0.0-alpha.2",
|
|
72
|
+
"a5-js": "^0.5.0",
|
|
73
|
+
"h3-js": "^4.2.1",
|
|
74
|
+
"open-location-code": "^1.0.3"
|
|
75
|
+
},
|
|
76
|
+
"devDependencies": {
|
|
77
|
+
"s2-geometry": "^1.2.10"
|
|
78
|
+
},
|
|
79
|
+
"gitHead": "1b21ecbe8872e604b1b362fe09ab59240bd5b41e"
|
|
80
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// math.gl
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
|
|
5
|
+
import {cellToBoundary, cellToLonLat, hexToU64, u64ToHex} from 'a5-js';
|
|
6
|
+
import {type Bounds2D} from '@math.gl/types';
|
|
7
|
+
import {type DGGSCell, type DGGSDecoder, getDGGSCellBounds} from './dggs-decoder';
|
|
8
|
+
|
|
9
|
+
/** Lightweight cell geometry decoder for the A5 pentagonal DGGS. */
|
|
10
|
+
export const A5Decoder = {
|
|
11
|
+
name: 'a5',
|
|
12
|
+
hasNumericRepresentation: true,
|
|
13
|
+
cellColumnNames: [
|
|
14
|
+
'a5',
|
|
15
|
+
'a5Token',
|
|
16
|
+
'a5_token',
|
|
17
|
+
'a5Cell',
|
|
18
|
+
'a5_cell',
|
|
19
|
+
'a5CellId',
|
|
20
|
+
'a5_cell_id',
|
|
21
|
+
'pentagon'
|
|
22
|
+
],
|
|
23
|
+
tokenToCell: (token: string): bigint => hexToU64(removeHexPrefix(token)),
|
|
24
|
+
cellToToken: (cell: DGGSCell): string =>
|
|
25
|
+
typeof cell === 'string' ? removeHexPrefix(cell) : u64ToHex(cell),
|
|
26
|
+
cellToLngLat: (cell: DGGSCell): [number, number] => cellToLonLat(getA5CellIndex(cell)),
|
|
27
|
+
cellToBoundary: (cell: DGGSCell): [number, number][] => getA5Boundary(cell),
|
|
28
|
+
cellToBoundaryFlat: (cell: DGGSCell): number[] => getA5Boundary(cell).flat(),
|
|
29
|
+
cellToBounds: (cell: DGGSCell): Bounds2D => getDGGSCellBounds(getA5Boundary(cell))
|
|
30
|
+
} as const satisfies DGGSDecoder;
|
|
31
|
+
|
|
32
|
+
function getA5CellIndex(cell: DGGSCell): bigint {
|
|
33
|
+
return typeof cell === 'bigint' ? cell : hexToU64(removeHexPrefix(cell));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function getA5Boundary(cell: DGGSCell): [number, number][] {
|
|
37
|
+
return cellToBoundary(getA5CellIndex(cell)).map(([longitude, latitude]) => [longitude, latitude]);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function removeHexPrefix(token: string): string {
|
|
41
|
+
return token.startsWith('0x') || token.startsWith('0X') ? token.slice(2) : token;
|
|
42
|
+
}
|
package/src/a5.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// math.gl
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
|
|
5
|
+
import {getS2Cell} from '../s2-geometry/s2-geometry';
|
|
6
|
+
import {getS2Region} from './s2-to-region';
|
|
7
|
+
import {getS2IndexFromToken} from '../s2-geometry/s2-token';
|
|
8
|
+
|
|
9
|
+
// import {OrientedBoundingBox, makeOrientedBoundingBoxFromPoints} from '@math.gl/culling';
|
|
10
|
+
// import {Ellipsoid} from '@math.gl/geospatial';
|
|
11
|
+
|
|
12
|
+
export type S2HeightInfo = {
|
|
13
|
+
minimumHeight: number;
|
|
14
|
+
maximumHeight: number;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Converts S2HeightInfo to corner points of an oriented bounding box
|
|
19
|
+
* Can be used to constuct an OrientedBoundingBox instance
|
|
20
|
+
*
|
|
21
|
+
* @param s2bv - s2 bounding volume to convert
|
|
22
|
+
* @param [result] Optional object onto which to store the result.
|
|
23
|
+
* @returns The modified result parameter or a new `OrientedBoundingBox` instance if not provided.
|
|
24
|
+
*/
|
|
25
|
+
export function getS2OrientedBoundingBoxCornerPoints(
|
|
26
|
+
s2token: string, // This can be an S2 key or token
|
|
27
|
+
heighInfo?: S2HeightInfo
|
|
28
|
+
): number[][] {
|
|
29
|
+
const min: number = heighInfo?.minimumHeight || 0;
|
|
30
|
+
const max: number = heighInfo?.maximumHeight || 0;
|
|
31
|
+
|
|
32
|
+
const s2index = getS2IndexFromToken(s2token);
|
|
33
|
+
const s2cell = getS2Cell(s2index);
|
|
34
|
+
const region = getS2Region(s2cell);
|
|
35
|
+
|
|
36
|
+
// region is {lngWest, latSouth, lngEast, latNorth} in degrees
|
|
37
|
+
const W = region[0];
|
|
38
|
+
const S = region[1];
|
|
39
|
+
const E = region[2];
|
|
40
|
+
const N = region[3];
|
|
41
|
+
|
|
42
|
+
const points: number[][] = [];
|
|
43
|
+
|
|
44
|
+
points.push([W, N, min]);
|
|
45
|
+
points.push([E, N, min]);
|
|
46
|
+
points.push([E, S, min]);
|
|
47
|
+
points.push([W, S, min]);
|
|
48
|
+
|
|
49
|
+
points.push([W, N, max]);
|
|
50
|
+
points.push([E, N, max]);
|
|
51
|
+
points.push([E, S, max]);
|
|
52
|
+
points.push([W, S, max]);
|
|
53
|
+
|
|
54
|
+
return points;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/*
|
|
58
|
+
function convertCartToXYZ(longitude, latitude, height, result?) {
|
|
59
|
+
const point = Ellipsoid.WGS84.cartographicToCartesian([longitude, latitude, height]);
|
|
60
|
+
return new Vector3(point[0], point[1], point[2]);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Add a point that doesn't allow the box dive under the Earth
|
|
64
|
+
// This point is actually a center of a face that could be a tangent to the Earth surface if max==0
|
|
65
|
+
points.push(convertCartToXYZ((W + E) / 2.0, (S + N) / 2.0, max));
|
|
66
|
+
|
|
67
|
+
// points should be an array of Vector3 (XYZ)
|
|
68
|
+
// Passing result===null throws an exception from makeOrientedBoundingBoxFromPoints
|
|
69
|
+
const obb: OrientedBoundingBox = makeOrientedBoundingBoxFromPoints(
|
|
70
|
+
points,
|
|
71
|
+
result !== null ? result : undefined
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
const box: number[] = [...obb.center, ...obb.halfAxes];
|
|
75
|
+
|
|
76
|
+
return box;
|
|
77
|
+
}
|
|
78
|
+
*/
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// math.gl
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
|
|
5
|
+
import {getS2GeoBoundsFromCell} from '../s2-geometry/s2-to-boundary';
|
|
6
|
+
|
|
7
|
+
export function getS2Region(s2cell: {face: number; ij: [number, number]; level: number}): number[] {
|
|
8
|
+
const corners = getS2GeoBoundsFromCell(s2cell);
|
|
9
|
+
const region = getRegionFromS2Corners(corners);
|
|
10
|
+
return region;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function getRegionFromS2Corners(corners: Float64Array): number[] {
|
|
14
|
+
const longitudes: number[] = [];
|
|
15
|
+
const latitudes: number[] = [];
|
|
16
|
+
for (let i = 0; i < corners.length; i += 2) {
|
|
17
|
+
longitudes.push(corners[i]);
|
|
18
|
+
latitudes.push(corners[i + 1]);
|
|
19
|
+
}
|
|
20
|
+
longitudes.sort((a, b) => a - b);
|
|
21
|
+
latitudes.sort((a, b) => a - b);
|
|
22
|
+
|
|
23
|
+
// Return the region in degrees
|
|
24
|
+
return [
|
|
25
|
+
longitudes[0],
|
|
26
|
+
latitudes[0],
|
|
27
|
+
longitudes[longitudes.length - 1],
|
|
28
|
+
latitudes[latitudes.length - 1]
|
|
29
|
+
];
|
|
30
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// math.gl
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
|
|
5
|
+
import {type Bounds2D} from '@math.gl/types';
|
|
6
|
+
|
|
7
|
+
/** Cell identifiers used by the lightweight decoder API. */
|
|
8
|
+
export type DGGSCell = string | bigint;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A deliberately small common API for decoding DGGS and DGGS-like cell tokens.
|
|
12
|
+
* Decoder objects satisfy this interface so visualization code can use them interchangeably.
|
|
13
|
+
*/
|
|
14
|
+
export type DGGSDecoder = {
|
|
15
|
+
/** The name of the DGGS */
|
|
16
|
+
name: string;
|
|
17
|
+
|
|
18
|
+
/** Whether cells have a 64-bit numeric representation. */
|
|
19
|
+
hasNumericRepresentation: boolean;
|
|
20
|
+
|
|
21
|
+
/** Conventional data column names that identify cells in this grid. */
|
|
22
|
+
cellColumnNames: readonly string[];
|
|
23
|
+
|
|
24
|
+
/** Convert a string token to a binary cell index. */
|
|
25
|
+
tokenToCell?: (token: string) => bigint;
|
|
26
|
+
/** Convert a cell to its string token. */
|
|
27
|
+
cellToToken?: (cell: DGGSCell) => string;
|
|
28
|
+
/** Return the center of the cell as `[longitude, latitude]`. */
|
|
29
|
+
cellToLngLat: (cell: DGGSCell) => [number, number];
|
|
30
|
+
/** Return the closed cell boundary as longitude/latitude pairs. */
|
|
31
|
+
cellToBoundary: (cell: DGGSCell) => [number, number][];
|
|
32
|
+
|
|
33
|
+
/** @returns the boundary of the cell, as a flat array */
|
|
34
|
+
cellToBoundaryFlat: (cell: DGGSCell) => number[];
|
|
35
|
+
/** @returns the longitude/latitude bounds of the cell */
|
|
36
|
+
cellToBounds: (cell: DGGSCell) => Bounds2D;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/** A cell column and the decoder selected for it. */
|
|
40
|
+
export type DGGSCellColumn = {
|
|
41
|
+
columnName: string;
|
|
42
|
+
decoder: DGGSDecoder;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/** Require a string cell identifier for string-only grids. */
|
|
46
|
+
export function getDGGSCellToken(cell: DGGSCell, gridName: string): string {
|
|
47
|
+
if (typeof cell !== 'string') {
|
|
48
|
+
throw new Error(`${gridName} cell identifiers must be strings`);
|
|
49
|
+
}
|
|
50
|
+
return cell;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Calculate longitude/latitude bounds from a cell boundary. */
|
|
54
|
+
export function getDGGSCellBounds(boundary: [number, number][]): Bounds2D {
|
|
55
|
+
let west = Infinity;
|
|
56
|
+
let south = Infinity;
|
|
57
|
+
let east = -Infinity;
|
|
58
|
+
let north = -Infinity;
|
|
59
|
+
|
|
60
|
+
for (const [longitude, latitude] of boundary) {
|
|
61
|
+
west = Math.min(west, longitude);
|
|
62
|
+
south = Math.min(south, latitude);
|
|
63
|
+
east = Math.max(east, longitude);
|
|
64
|
+
north = Math.max(north, latitude);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return [
|
|
68
|
+
[west, south],
|
|
69
|
+
[east, north]
|
|
70
|
+
];
|
|
71
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// math.gl
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
|
|
5
|
+
import {type Bounds2D} from '@math.gl/types';
|
|
6
|
+
import {type DGGSCell, type DGGSDecoder, getDGGSCellToken} from './dggs-decoder';
|
|
7
|
+
|
|
8
|
+
/** Decoder for the geohash dggs */
|
|
9
|
+
export const GeohashDecoder = {
|
|
10
|
+
name: 'geohash',
|
|
11
|
+
hasNumericRepresentation: false,
|
|
12
|
+
cellColumnNames: ['geohash', 'geohashId', 'geohash_id'],
|
|
13
|
+
cellToLngLat: (cell: DGGSCell): [number, number] =>
|
|
14
|
+
getGeohashLngLat(getDGGSCellToken(cell, 'GeoHash')),
|
|
15
|
+
cellToBoundary: (cell: DGGSCell): [number, number][] =>
|
|
16
|
+
getGeohashBoundary(getDGGSCellToken(cell, 'GeoHash')),
|
|
17
|
+
cellToBoundaryFlat: (cell: DGGSCell): number[] =>
|
|
18
|
+
getGeohashBoundaryFlat(getDGGSCellToken(cell, 'GeoHash')),
|
|
19
|
+
cellToBounds: (cell: DGGSCell): Bounds2D => getGeohashBounds(getDGGSCellToken(cell, 'GeoHash'))
|
|
20
|
+
} as const satisfies DGGSDecoder;
|
|
21
|
+
|
|
22
|
+
const BASE32_CODES = '0123456789bcdefghjkmnpqrstuvwxyz';
|
|
23
|
+
const BASE32_CODES_DICT: Record<string, number> = {};
|
|
24
|
+
for (let i = 0; i < BASE32_CODES.length; i++) {
|
|
25
|
+
BASE32_CODES_DICT[BASE32_CODES.charAt(i)] = i;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const MIN_LAT = -90;
|
|
29
|
+
const MAX_LAT = 90;
|
|
30
|
+
const MIN_LON = -180;
|
|
31
|
+
const MAX_LON = 180;
|
|
32
|
+
|
|
33
|
+
/** Return center lng,lat of geohash cell */
|
|
34
|
+
function getGeohashLngLat(geohash: string): [number, number] {
|
|
35
|
+
const [[w, s], [e, n]] = getGeohashBounds(geohash);
|
|
36
|
+
return [(e + w) / 2, (n + s) / 2];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Return boundary polygon of geohash cell as lng,lat array */
|
|
40
|
+
function getGeohashBoundary(geohash: string): [number, number][] {
|
|
41
|
+
const [[w, s], [e, n]] = getGeohashBounds(geohash);
|
|
42
|
+
return [
|
|
43
|
+
[e, n],
|
|
44
|
+
[e, s],
|
|
45
|
+
[w, s],
|
|
46
|
+
[w, n],
|
|
47
|
+
[e, n]
|
|
48
|
+
];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Return boundary polygon of geohash cell as flat array */
|
|
52
|
+
function getGeohashBoundaryFlat(geohash: string): number[] {
|
|
53
|
+
const [[w, s], [e, n]] = getGeohashBounds(geohash);
|
|
54
|
+
return [e, n, e, s, w, s, w, n, e, n];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @note Adapted from ngeohash decode_bbox
|
|
59
|
+
*/
|
|
60
|
+
/* eslint-disable max-depth */
|
|
61
|
+
export function getGeohashBounds(geohash: string): Bounds2D {
|
|
62
|
+
let isLon = true;
|
|
63
|
+
let maxLat = MAX_LAT;
|
|
64
|
+
let minLat = MIN_LAT;
|
|
65
|
+
let maxLon = MAX_LON;
|
|
66
|
+
let minLon = MIN_LON;
|
|
67
|
+
let mid: number;
|
|
68
|
+
|
|
69
|
+
let hashValue = 0;
|
|
70
|
+
for (let i = 0, l = geohash.length; i < l; i++) {
|
|
71
|
+
const code = geohash[i].toLowerCase();
|
|
72
|
+
hashValue = BASE32_CODES_DICT[code];
|
|
73
|
+
|
|
74
|
+
for (let bits = 4; bits >= 0; bits--) {
|
|
75
|
+
const bit = (hashValue >> bits) & 1;
|
|
76
|
+
if (isLon) {
|
|
77
|
+
mid = (maxLon + minLon) / 2;
|
|
78
|
+
if (bit === 1) {
|
|
79
|
+
minLon = mid;
|
|
80
|
+
} else {
|
|
81
|
+
maxLon = mid;
|
|
82
|
+
}
|
|
83
|
+
} else {
|
|
84
|
+
mid = (maxLat + minLat) / 2;
|
|
85
|
+
if (bit === 1) {
|
|
86
|
+
minLat = mid;
|
|
87
|
+
} else {
|
|
88
|
+
maxLat = mid;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
isLon = !isLon;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return [
|
|
96
|
+
[minLon, minLat],
|
|
97
|
+
[maxLon, maxLat]
|
|
98
|
+
];
|
|
99
|
+
}
|
package/src/geohash.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// math.gl
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
|
|
5
|
+
import {cellToBoundary, cellToLatLng} from 'h3-js';
|
|
6
|
+
import {type Bounds2D} from '@math.gl/types';
|
|
7
|
+
import {type DGGSCell, type DGGSDecoder, getDGGSCellBounds} from './dggs-decoder';
|
|
8
|
+
|
|
9
|
+
/** Lightweight cell geometry decoder for the H3 hexagonal DGGS. */
|
|
10
|
+
export const H3Decoder = {
|
|
11
|
+
name: 'h3',
|
|
12
|
+
hasNumericRepresentation: true,
|
|
13
|
+
cellColumnNames: [
|
|
14
|
+
'h3',
|
|
15
|
+
'h3Index',
|
|
16
|
+
'h3_index',
|
|
17
|
+
'h3Cell',
|
|
18
|
+
'h3_cell',
|
|
19
|
+
'h3CellId',
|
|
20
|
+
'h3_cell_id',
|
|
21
|
+
'hexagon'
|
|
22
|
+
],
|
|
23
|
+
tokenToCell: (token: string): bigint => getBigIntFromHexToken(token),
|
|
24
|
+
cellToToken: (cell: DGGSCell): string => getH3Token(cell),
|
|
25
|
+
cellToLngLat: (cell: DGGSCell): [number, number] => getH3LngLat(cell),
|
|
26
|
+
cellToBoundary: (cell: DGGSCell): [number, number][] => getH3Boundary(cell),
|
|
27
|
+
cellToBoundaryFlat: (cell: DGGSCell): number[] => getH3Boundary(cell).flat(),
|
|
28
|
+
cellToBounds: (cell: DGGSCell): Bounds2D => getDGGSCellBounds(getH3Boundary(cell))
|
|
29
|
+
} as const satisfies DGGSDecoder;
|
|
30
|
+
|
|
31
|
+
function getH3Token(cell: DGGSCell): string {
|
|
32
|
+
return typeof cell === 'bigint' ? cell.toString(16) : removeHexPrefix(cell);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function getH3LngLat(cell: DGGSCell): [number, number] {
|
|
36
|
+
const [latitude, longitude] = cellToLatLng(getH3Token(cell));
|
|
37
|
+
return [longitude, latitude];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function getH3Boundary(cell: DGGSCell): [number, number][] {
|
|
41
|
+
return cellToBoundary(getH3Token(cell), true).map(([longitude, latitude]) => [
|
|
42
|
+
longitude,
|
|
43
|
+
latitude
|
|
44
|
+
]);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function getBigIntFromHexToken(token: string): bigint {
|
|
48
|
+
return BigInt(`0x${removeHexPrefix(token)}`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function removeHexPrefix(token: string): string {
|
|
52
|
+
return token.startsWith('0x') || token.startsWith('0X') ? token.slice(2) : token;
|
|
53
|
+
}
|
package/src/h3.ts
ADDED
package/src/index.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// math.gl
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
|
|
5
|
+
import {type DGGSCell, type DGGSCellColumn, type DGGSDecoder} from './dggs-decoder';
|
|
6
|
+
import {A5Decoder} from './a5-decoder';
|
|
7
|
+
import {GeohashDecoder} from './geohash-decoder';
|
|
8
|
+
import {H3Decoder} from './h3-decoder';
|
|
9
|
+
import {PlusCodeDecoder} from './plus-code-decoder';
|
|
10
|
+
import {QuadkeyDecoder} from './quadkey-decoder';
|
|
11
|
+
import {S2Decoder} from './s2-decoder';
|
|
12
|
+
|
|
13
|
+
export {type DGGSCell, type DGGSCellColumn, type DGGSDecoder};
|
|
14
|
+
export {A5Decoder};
|
|
15
|
+
export {GeohashDecoder};
|
|
16
|
+
export {H3Decoder};
|
|
17
|
+
export {PlusCodeDecoder};
|
|
18
|
+
export {QuadkeyDecoder, quadkeyToWorldBounds} from './quadkey-decoder';
|
|
19
|
+
export {S2Decoder};
|
|
20
|
+
export {getS2ChildIndex} from './s2-geometry/s2-token';
|
|
21
|
+
|
|
22
|
+
/** Lightweight decoders bundled with this module. */
|
|
23
|
+
export const DGGS_DECODERS: readonly DGGSDecoder[] = [
|
|
24
|
+
A5Decoder,
|
|
25
|
+
GeohashDecoder,
|
|
26
|
+
H3Decoder,
|
|
27
|
+
PlusCodeDecoder,
|
|
28
|
+
QuadkeyDecoder,
|
|
29
|
+
S2Decoder
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Finds a conventional DGGS cell column by name.
|
|
34
|
+
*
|
|
35
|
+
* Returns `null` when there is no match or when columns from more than one
|
|
36
|
+
* decoder match. Callers should ask the user to choose in the ambiguous case.
|
|
37
|
+
*/
|
|
38
|
+
export function findDGGSCellColumn(
|
|
39
|
+
columnNames: Iterable<string>,
|
|
40
|
+
decoders: readonly DGGSDecoder[] = DGGS_DECODERS
|
|
41
|
+
): DGGSCellColumn | null {
|
|
42
|
+
const actualNames = new Map<string, string>();
|
|
43
|
+
for (const columnName of columnNames) {
|
|
44
|
+
actualNames.set(columnName.toLowerCase(), columnName);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let match: DGGSCellColumn | null = null;
|
|
48
|
+
for (const decoder of decoders) {
|
|
49
|
+
for (const candidate of decoder.cellColumnNames) {
|
|
50
|
+
const columnName = actualNames.get(candidate.toLowerCase());
|
|
51
|
+
if (!columnName) {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (match && (match.decoder !== decoder || match.columnName !== columnName)) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
match = {columnName, decoder};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return match;
|
|
61
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
declare module 'open-location-code' {
|
|
2
|
+
export type OpenLocationCodeArea = {
|
|
3
|
+
latitudeLo: number;
|
|
4
|
+
longitudeLo: number;
|
|
5
|
+
latitudeHi: number;
|
|
6
|
+
longitudeHi: number;
|
|
7
|
+
latitudeCenter: number;
|
|
8
|
+
longitudeCenter: number;
|
|
9
|
+
codeLength: number;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export class OpenLocationCode {
|
|
13
|
+
isFull(code: string): boolean;
|
|
14
|
+
decode(code: string): OpenLocationCodeArea;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const openLocationCodePackage: {OpenLocationCode: typeof OpenLocationCode};
|
|
18
|
+
export default openLocationCodePackage;
|
|
19
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// math.gl
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
|
|
5
|
+
import OpenLocationCodePackage, {type OpenLocationCodeArea} from 'open-location-code';
|
|
6
|
+
import {type Bounds2D} from '@math.gl/types';
|
|
7
|
+
import {type DGGSCell, type DGGSDecoder, getDGGSCellToken} from './dggs-decoder';
|
|
8
|
+
|
|
9
|
+
const openLocationCode = new OpenLocationCodePackage.OpenLocationCode();
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Lightweight geometry decoder for full Google Plus Codes (Open Location Codes).
|
|
13
|
+
* Short codes require a reference location and are intentionally unsupported.
|
|
14
|
+
*/
|
|
15
|
+
export const PlusCodeDecoder = {
|
|
16
|
+
name: 'plus-code',
|
|
17
|
+
hasNumericRepresentation: false,
|
|
18
|
+
cellColumnNames: ['plusCode', 'plus_code', 'openLocationCode', 'open_location_code', 'olc'],
|
|
19
|
+
cellToLngLat: (cell: DGGSCell): [number, number] => getPlusCodeLngLat(cell),
|
|
20
|
+
cellToBoundary: (cell: DGGSCell): [number, number][] => getPlusCodeBoundary(decodePlusCode(cell)),
|
|
21
|
+
cellToBoundaryFlat: (cell: DGGSCell): number[] => {
|
|
22
|
+
const area = decodePlusCode(cell);
|
|
23
|
+
return getPlusCodeBoundary(area).flat();
|
|
24
|
+
},
|
|
25
|
+
cellToBounds: (cell: DGGSCell): Bounds2D => {
|
|
26
|
+
const area = decodePlusCode(cell);
|
|
27
|
+
return [
|
|
28
|
+
[area.longitudeLo, area.latitudeLo],
|
|
29
|
+
[area.longitudeHi, area.latitudeHi]
|
|
30
|
+
];
|
|
31
|
+
}
|
|
32
|
+
} as const satisfies DGGSDecoder;
|
|
33
|
+
|
|
34
|
+
function getPlusCodeLngLat(cell: DGGSCell): [number, number] {
|
|
35
|
+
const area = decodePlusCode(cell);
|
|
36
|
+
return [area.longitudeCenter, area.latitudeCenter];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function decodePlusCode(cell: DGGSCell): OpenLocationCodeArea {
|
|
40
|
+
const code = getDGGSCellToken(cell, 'Plus Code');
|
|
41
|
+
if (!openLocationCode.isFull(code)) {
|
|
42
|
+
throw new Error(`Plus Code decoder requires a full code: ${code}`);
|
|
43
|
+
}
|
|
44
|
+
return openLocationCode.decode(code);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function getPlusCodeBoundary(area: OpenLocationCodeArea): [number, number][] {
|
|
48
|
+
const {longitudeLo: west, latitudeLo: south, longitudeHi: east, latitudeHi: north} = area;
|
|
49
|
+
return [
|
|
50
|
+
[east, north],
|
|
51
|
+
[east, south],
|
|
52
|
+
[west, south],
|
|
53
|
+
[west, north],
|
|
54
|
+
[east, north]
|
|
55
|
+
];
|
|
56
|
+
}
|