@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.
Files changed (113) hide show
  1. package/LICENSE +140 -0
  2. package/README.md +7 -0
  3. package/dist/a5-decoder.d.ts +15 -0
  4. package/dist/a5-decoder.d.ts.map +1 -0
  5. package/dist/a5-decoder.js +36 -0
  6. package/dist/a5-decoder.js.map +1 -0
  7. package/dist/a5.cjs +77 -0
  8. package/dist/a5.cjs.map +6 -0
  9. package/dist/a5.d.ts +2 -0
  10. package/dist/a5.d.ts.map +1 -0
  11. package/dist/a5.js +5 -0
  12. package/dist/a5.js.map +1 -0
  13. package/dist/converters/s2-to-obb-points.d.ts +15 -0
  14. package/dist/converters/s2-to-obb-points.d.ts.map +1 -0
  15. package/dist/converters/s2-to-obb-points.js +60 -0
  16. package/dist/converters/s2-to-obb-points.js.map +1 -0
  17. package/dist/converters/s2-to-region.d.ts +6 -0
  18. package/dist/converters/s2-to-region.d.ts.map +1 -0
  19. package/dist/converters/s2-to-region.js +27 -0
  20. package/dist/converters/s2-to-region.js.map +1 -0
  21. package/dist/dggs-decoder.d.ts +37 -0
  22. package/dist/dggs-decoder.d.ts.map +1 -0
  23. package/dist/dggs-decoder.js +28 -0
  24. package/dist/dggs-decoder.js.map +1 -0
  25. package/dist/geohash-decoder.d.ts +17 -0
  26. package/dist/geohash-decoder.d.ts.map +1 -0
  27. package/dist/geohash-decoder.js +88 -0
  28. package/dist/geohash-decoder.js.map +1 -0
  29. package/dist/geohash.cjs +108 -0
  30. package/dist/geohash.cjs.map +6 -0
  31. package/dist/geohash.d.ts +2 -0
  32. package/dist/geohash.d.ts.map +1 -0
  33. package/dist/geohash.js +5 -0
  34. package/dist/geohash.js.map +1 -0
  35. package/dist/h3-decoder.d.ts +15 -0
  36. package/dist/h3-decoder.d.ts.map +1 -0
  37. package/dist/h3-decoder.js +46 -0
  38. package/dist/h3-decoder.js.map +1 -0
  39. package/dist/h3.cjs +87 -0
  40. package/dist/h3.cjs.map +6 -0
  41. package/dist/h3.d.ts +2 -0
  42. package/dist/h3.d.ts.map +1 -0
  43. package/dist/h3.js +5 -0
  44. package/dist/h3.js.map +1 -0
  45. package/dist/index.cjs +615 -0
  46. package/dist/index.cjs.map +6 -0
  47. package/dist/index.d.ts +24 -0
  48. package/dist/index.d.ts.map +1 -0
  49. package/dist/index.js +52 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/plus-code-decoder.d.ts +16 -0
  52. package/dist/plus-code-decoder.d.ts.map +1 -0
  53. package/dist/plus-code-decoder.js +50 -0
  54. package/dist/plus-code-decoder.js.map +1 -0
  55. package/dist/plus-code.cjs +88 -0
  56. package/dist/plus-code.cjs.map +6 -0
  57. package/dist/plus-code.d.ts +2 -0
  58. package/dist/plus-code.d.ts.map +1 -0
  59. package/dist/plus-code.js +5 -0
  60. package/dist/plus-code.js.map +1 -0
  61. package/dist/quadkey-decoder.d.ts +14 -0
  62. package/dist/quadkey-decoder.d.ts.map +1 -0
  63. package/dist/quadkey-decoder.js +86 -0
  64. package/dist/quadkey-decoder.js.map +1 -0
  65. package/dist/quadkey.cjs +106 -0
  66. package/dist/quadkey.cjs.map +6 -0
  67. package/dist/quadkey.d.ts +2 -0
  68. package/dist/quadkey.d.ts.map +1 -0
  69. package/dist/quadkey.js +5 -0
  70. package/dist/quadkey.js.map +1 -0
  71. package/dist/s2-decoder.d.ts +26 -0
  72. package/dist/s2-decoder.d.ts.map +1 -0
  73. package/dist/s2-decoder.js +76 -0
  74. package/dist/s2-decoder.js.map +1 -0
  75. package/dist/s2-geometry/s2-geometry.d.ts +13 -0
  76. package/dist/s2-geometry/s2-geometry.d.ts.map +1 -0
  77. package/dist/s2-geometry/s2-geometry.js +126 -0
  78. package/dist/s2-geometry/s2-geometry.js.map +1 -0
  79. package/dist/s2-geometry/s2-to-boundary.d.ts +14 -0
  80. package/dist/s2-geometry/s2-to-boundary.d.ts.map +1 -0
  81. package/dist/s2-geometry/s2-to-boundary.js +60 -0
  82. package/dist/s2-geometry/s2-to-boundary.js.map +1 -0
  83. package/dist/s2-geometry/s2-token.d.ts +13 -0
  84. package/dist/s2-geometry/s2-token.d.ts.map +1 -0
  85. package/dist/s2-geometry/s2-token.js +61 -0
  86. package/dist/s2-geometry/s2-token.js.map +1 -0
  87. package/dist/s2.cjs +278 -0
  88. package/dist/s2.cjs.map +6 -0
  89. package/dist/s2.d.ts +3 -0
  90. package/dist/s2.d.ts.map +1 -0
  91. package/dist/s2.js +6 -0
  92. package/dist/s2.js.map +1 -0
  93. package/package.json +80 -0
  94. package/src/a5-decoder.ts +42 -0
  95. package/src/a5.ts +5 -0
  96. package/src/converters/s2-to-obb-points.ts +78 -0
  97. package/src/converters/s2-to-region.ts +30 -0
  98. package/src/dggs-decoder.ts +71 -0
  99. package/src/geohash-decoder.ts +99 -0
  100. package/src/geohash.ts +5 -0
  101. package/src/h3-decoder.ts +53 -0
  102. package/src/h3.ts +5 -0
  103. package/src/index.ts +61 -0
  104. package/src/open-location-code.d.ts +19 -0
  105. package/src/plus-code-decoder.ts +56 -0
  106. package/src/plus-code.ts +5 -0
  107. package/src/quadkey-decoder.ts +98 -0
  108. package/src/quadkey.ts +5 -0
  109. package/src/s2-decoder.ts +87 -0
  110. package/src/s2-geometry/s2-geometry.ts +166 -0
  111. package/src/s2-geometry/s2-to-boundary.ts +76 -0
  112. package/src/s2-geometry/s2-token.ts +70 -0
  113. 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,5 @@
1
+ // math.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ export {A5Decoder} from './a5-decoder';
@@ -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,5 @@
1
+ // math.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ export {GeohashDecoder, getGeohashBounds} from './geohash-decoder';
@@ -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
@@ -0,0 +1,5 @@
1
+ // math.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ export {H3Decoder} from './h3-decoder';
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
+ }
@@ -0,0 +1,5 @@
1
+ // math.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ export {PlusCodeDecoder} from './plus-code-decoder';