@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
@@ -0,0 +1,98 @@
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
+ const TILE_SIZE = 512;
9
+
10
+ /** Decoder for the quadkey DGGS */
11
+ export const QuadkeyDecoder = {
12
+ name: 'quadkey',
13
+ hasNumericRepresentation: false,
14
+ cellColumnNames: ['quadkey', 'quadkeyId', 'quadkey_id'],
15
+ cellToLngLat: (cell: DGGSCell): [number, number] =>
16
+ getQuadkeyLngLat(getDGGSCellToken(cell, 'Quadkey')),
17
+ cellToBoundary: (cell: DGGSCell): [number, number][] =>
18
+ getQuadkeyBoundary(getDGGSCellToken(cell, 'Quadkey')),
19
+ cellToBoundaryFlat: (cell: DGGSCell): number[] =>
20
+ getQuadkeyBoundaryFlat(getDGGSCellToken(cell, 'Quadkey')),
21
+ cellToBounds: (cell: DGGSCell): Bounds2D => getQuadkeyBounds(getDGGSCellToken(cell, 'Quadkey'))
22
+ } as const satisfies DGGSDecoder;
23
+
24
+ function getQuadkeyLngLat(quadkey: string): [number, number] {
25
+ const [topLeft, bottomRight] = quadkeyToWorldBounds(quadkey);
26
+ const [w, n] = worldToLngLat(topLeft);
27
+ const [e, s] = worldToLngLat(bottomRight);
28
+ return [(e + w) / 2, (s + n) / 2];
29
+ }
30
+
31
+ function getQuadkeyBoundary(quadkey: string): [number, number][] {
32
+ const [topLeft, bottomRight] = quadkeyToWorldBounds(quadkey);
33
+ const [w, n] = worldToLngLat(topLeft);
34
+ const [e, s] = worldToLngLat(bottomRight);
35
+ return [
36
+ [e, n],
37
+ [e, s],
38
+ [w, s],
39
+ [w, n],
40
+ [e, n]
41
+ ];
42
+ }
43
+
44
+ function getQuadkeyBoundaryFlat(quadkey: string): number[] {
45
+ const [topLeft, bottomRight] = quadkeyToWorldBounds(quadkey);
46
+ const [w, n] = worldToLngLat(topLeft);
47
+ const [e, s] = worldToLngLat(bottomRight);
48
+ return [e, n, e, s, w, s, w, n, e, n];
49
+ }
50
+
51
+ function getQuadkeyBounds(quadkey: string): Bounds2D {
52
+ const [topLeft, bottomRight] = quadkeyToWorldBounds(quadkey);
53
+ const [w, n] = worldToLngLat(topLeft);
54
+ const [e, s] = worldToLngLat(bottomRight);
55
+ return [
56
+ [w, s],
57
+ [e, n]
58
+ ];
59
+ }
60
+
61
+ export function quadkeyToWorldBounds(quadkey: string): [[number, number], [number, number]] {
62
+ let x = 0;
63
+ let y = 0;
64
+ let mask = 1 << quadkey.length;
65
+ const scale = mask / TILE_SIZE;
66
+
67
+ for (let i = 0; i < quadkey.length; i++) {
68
+ mask >>= 1;
69
+ const q = parseInt(quadkey[i], 10);
70
+ if (q % 2) x |= mask;
71
+ if (q > 1) y |= mask;
72
+ }
73
+ return [
74
+ [x / scale, TILE_SIZE - y / scale],
75
+ [(x + 0.99) / scale, TILE_SIZE - (y + 0.99) / scale]
76
+ ];
77
+ }
78
+
79
+ // CONSTANTS
80
+ const PI = Math.PI;
81
+ const PI_4 = PI / 4;
82
+ const RADIANS_TO_DEGREES = 180 / PI;
83
+
84
+ /**
85
+ * Unproject world point [x,y] on map onto {lat, lon} on sphere
86
+ *
87
+ * @param xy - array with [x,y] members
88
+ * representing point on projected map plane
89
+ * @return - array with [x,y] of point on sphere.
90
+ * Has toArray method if you need a GeoJSON Array.
91
+ * Per cartographic tradition, lat and lon are specified as degrees.
92
+ */
93
+ function worldToLngLat(xy: number[]): [number, number] {
94
+ const [x, y] = xy;
95
+ const lambda2 = (x / TILE_SIZE) * (2 * PI) - PI;
96
+ const phi2 = 2 * (Math.atan(Math.exp((y / TILE_SIZE) * (2 * PI) - PI)) - PI_4);
97
+ return [lambda2 * RADIANS_TO_DEGREES, phi2 * RADIANS_TO_DEGREES];
98
+ }
package/src/quadkey.ts ADDED
@@ -0,0 +1,5 @@
1
+ // math.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ export {QuadkeyDecoder, quadkeyToWorldBounds} from './quadkey-decoder';
@@ -0,0 +1,87 @@
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} from './dggs-decoder';
7
+
8
+ import {getS2IndexFromToken, getS2TokenFromIndex} from './s2-geometry/s2-token';
9
+ import {getS2GeoBounds} from './s2-geometry/s2-to-boundary';
10
+ import {getS2Cell, IJToST, STToUV, FaceUVToXYZ, XYZToLngLat} from './s2-geometry/s2-geometry';
11
+
12
+ /** Decoder for the S2 DGGS */
13
+ export const S2Decoder = {
14
+ name: 's2',
15
+ hasNumericRepresentation: true,
16
+ cellColumnNames: ['s2', 's2Token', 's2_token', 's2Cell', 's2_cell', 's2CellId', 's2_cell_id'],
17
+ tokenToCell: (s2Token: string): bigint => getS2IndexFromToken(s2Token),
18
+ cellToToken: (cell: DGGSCell): string =>
19
+ typeof cell === 'string' ? cell : getS2TokenFromIndex(cell),
20
+ cellToLngLat: (cell: DGGSCell): [number, number] => getS2LngLat(cell),
21
+ cellToBoundary: (cell: DGGSCell): [number, number][] => getS2Boundary(cell),
22
+ cellToBoundaryFlat: (cell: DGGSCell): number[] => getS2BoundaryFlat(cell),
23
+ cellToBounds: (cell: DGGSCell): Bounds2D => getS2Bounds(cell)
24
+ } as const satisfies DGGSDecoder;
25
+
26
+ /**
27
+ * Retrieve S2 geometry center
28
+ */
29
+ export function getS2LngLat(cell: DGGSCell): [number, number] {
30
+ const s2Index = getS2CellIndex(cell);
31
+ const s2Cell = getS2Cell(s2Index);
32
+
33
+ const st = IJToST(s2Cell.ij, s2Cell.level, [0.5, 0.5]);
34
+ const uv = STToUV(st);
35
+ const xyz = FaceUVToXYZ(s2Cell.face, uv);
36
+ const lngLat = XYZToLngLat(xyz);
37
+
38
+ return lngLat as [number, number];
39
+ }
40
+
41
+ /**
42
+ * Get a polygon with corner coordinates for an s2 cell
43
+ * @param - This can be an S2 key or token
44
+ * @return {Float64Array} - a simple polygon in flat array format: [lng0, lat0, lng1, lat1, ...]
45
+ * - the polygon is closed, i.e. last coordinate is a copy of the first coordinate
46
+ */
47
+ export function getS2BoundaryFlat(cell: DGGSCell): number[] {
48
+ const s2Index = getS2CellIndex(cell);
49
+ const float64Array = getS2GeoBounds(s2Index);
50
+ // TODO - inefficient
51
+ return Array.from(float64Array);
52
+ }
53
+
54
+ function getS2Boundary(cell: DGGSCell): [number, number][] {
55
+ const flatBoundary = getS2BoundaryFlat(cell);
56
+ const boundary: [number, number][] = [];
57
+ for (let i = 0; i < flatBoundary.length; i += 2) {
58
+ boundary.push([flatBoundary[i], flatBoundary[i + 1]]);
59
+ }
60
+ return boundary;
61
+ }
62
+
63
+ function getS2Bounds(cell: DGGSCell): Bounds2D {
64
+ const flatBoundary = getS2Boundary(cell);
65
+
66
+ // We know that we have at least one point, no Infinity will be returned.
67
+ let xmin = Infinity;
68
+ let ymin = Infinity;
69
+ let xmax = -Infinity;
70
+ let ymax = -Infinity;
71
+
72
+ for (const [x, y] of flatBoundary) {
73
+ if (x < xmin) xmin = x;
74
+ if (x > xmax) xmax = x;
75
+ if (y < ymin) ymin = y;
76
+ if (y > ymax) ymax = y;
77
+ }
78
+
79
+ return [
80
+ [xmin, ymin],
81
+ [xmax, ymax]
82
+ ];
83
+ }
84
+
85
+ function getS2CellIndex(cell: DGGSCell): bigint {
86
+ return typeof cell === 'bigint' ? cell : getS2IndexFromToken(cell);
87
+ }
@@ -0,0 +1,166 @@
1
+ // math.gl
2
+ // SPDX-License-Identifier: MIT and ISC
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ // math.gl, MIT license
6
+ /*
7
+ Adapted from s2-geometry under ISC License (ISC)
8
+ Copyright (c) 2012-2016, Jon Atkins <github@jonatkins.com>
9
+ Copyright (c) 2016, AJ ONeal <aj@daplie.com>
10
+ Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
11
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
12
+ */
13
+
14
+ export type S2Cell = {
15
+ face: number;
16
+ ij: [number, number];
17
+ level: number;
18
+ };
19
+
20
+ export function getS2Cell(s2Index: bigint): S2Cell {
21
+ const key = toHilbertQuadkey(s2Index);
22
+ const s2cell = fromHilbertQuadKey(key);
23
+ return s2cell;
24
+ }
25
+
26
+ //
27
+ // Functional Style
28
+ //
29
+ const FACE_BITS = 3;
30
+ const MAX_LEVEL = 30;
31
+ const POS_BITS = 2 * MAX_LEVEL + 1; // 61 (60 bits of data, 1 bit lsb marker)
32
+ const RADIAN_TO_DEGREE = 180 / Math.PI;
33
+
34
+ /*
35
+ Original function taken from deck.gl doesn't support the case of (face <= 5)
36
+ It's fixed here.
37
+ */
38
+ export function fromHilbertQuadKey(hilbertQuadkey: string): S2Cell {
39
+ const parts = hilbertQuadkey.split('/');
40
+ const face = parseInt(parts[0], 10);
41
+ const position = parts[1] || '';
42
+ const maxLevel = position.length;
43
+ let level = 0;
44
+
45
+ const point = [0, 0] as [number, number];
46
+
47
+ for (let i = maxLevel - 1; i >= 0; i--) {
48
+ level = maxLevel - i;
49
+ const bit = position[i];
50
+ let rx = 0;
51
+ let ry = 0;
52
+ if (bit === '1') {
53
+ ry = 1;
54
+ } else if (bit === '2') {
55
+ rx = 1;
56
+ ry = 1;
57
+ } else if (bit === '3') {
58
+ rx = 1;
59
+ }
60
+
61
+ const val = Math.pow(2, level - 1);
62
+ rotateAndFlipQuadrant(val, point, rx, ry);
63
+
64
+ point[0] += val * rx;
65
+ point[1] += val * ry;
66
+ }
67
+
68
+ if (face % 2 === 1) {
69
+ const t = point[0];
70
+ point[0] = point[1];
71
+ point[1] = t;
72
+ }
73
+
74
+ return {face, ij: point, level};
75
+ }
76
+
77
+ export function toHilbertQuadkey(id: bigint): string {
78
+ let bin = id.toString(2);
79
+
80
+ while (bin.length < FACE_BITS + POS_BITS) {
81
+ // eslint-disable-next-line prefer-template
82
+ bin = '0' + bin;
83
+ }
84
+
85
+ // MUST come AFTER binstr has been left-padded with '0's
86
+ const lsbIndex = bin.lastIndexOf('1');
87
+ // substr(start, len)
88
+ // substring(start, end) // includes start, does not include end
89
+ const faceB = bin.substring(0, 3);
90
+ // posB will always be a multiple of 2 (or it's invalid)
91
+ const posB = bin.substring(3, lsbIndex);
92
+ const levelN = posB.length / 2;
93
+
94
+ const faceS = BigInt(`0b${faceB}`).toString(10);
95
+
96
+ let posS = '';
97
+ if (levelN !== 0) {
98
+ posS = BigInt(`0b${posB}`).toString(4);
99
+
100
+ while (posS.length < levelN) {
101
+ // eslint-disable-next-line prefer-template
102
+ posS = '0' + posS;
103
+ }
104
+ }
105
+ return `${faceS}/${posS}`;
106
+ }
107
+
108
+ export function IJToST(
109
+ ij: [number, number],
110
+ order: number,
111
+ offsets: [number, number]
112
+ ): [number, number] {
113
+ const maxSize = 1 << order;
114
+
115
+ return [(ij[0] + offsets[0]) / maxSize, (ij[1] + offsets[1]) / maxSize];
116
+ }
117
+
118
+ function singleSTtoUV(st: number): number {
119
+ if (st >= 0.5) {
120
+ return (1 / 3.0) * (4 * st * st - 1);
121
+ }
122
+ return (1 / 3.0) * (1 - 4 * (1 - st) * (1 - st));
123
+ }
124
+
125
+ export function STToUV(st: [number, number]): [number, number] {
126
+ return [singleSTtoUV(st[0]), singleSTtoUV(st[1])];
127
+ }
128
+
129
+ export function FaceUVToXYZ(face: number, [u, v]: [number, number]): [number, number, number] {
130
+ switch (face) {
131
+ case 0:
132
+ return [1, u, v];
133
+ case 1:
134
+ return [-u, 1, v];
135
+ case 2:
136
+ return [-u, -v, 1];
137
+ case 3:
138
+ return [-1, -v, -u];
139
+ case 4:
140
+ return [v, -1, -u];
141
+ case 5:
142
+ return [v, u, -1];
143
+ default:
144
+ throw new Error('Invalid face');
145
+ }
146
+ }
147
+
148
+ export function XYZToLngLat([x, y, z]: [number, number, number]): [number, number] {
149
+ const lat = Math.atan2(z, Math.sqrt(x * x + y * y));
150
+ const lng = Math.atan2(y, x);
151
+
152
+ return [lng * RADIAN_TO_DEGREE, lat * RADIAN_TO_DEGREE];
153
+ }
154
+
155
+ function rotateAndFlipQuadrant(n: number, point: [number, number], rx: number, ry: number): void {
156
+ if (ry === 0) {
157
+ if (rx === 1) {
158
+ point[0] = n - 1 - point[0];
159
+ point[1] = n - 1 - point[1];
160
+ }
161
+
162
+ const x = point[0];
163
+ point[0] = point[1];
164
+ point[1] = x;
165
+ }
166
+ }
@@ -0,0 +1,76 @@
1
+ // math.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import {IJToST, STToUV, FaceUVToXYZ, XYZToLngLat, getS2Cell} from './s2-geometry';
6
+
7
+ const MAX_RESOLUTION = 100;
8
+
9
+ export function getS2GeoBounds(s2Index: bigint): Float64Array {
10
+ const s2Cell = getS2Cell(s2Index);
11
+ return getS2GeoBoundsFromCell(s2Cell);
12
+ }
13
+
14
+ /**
15
+ * The S2 cell edge is curved: http://s2geometry.io/
16
+ * This is more prominent at lower levels
17
+ * resolution is the number of segments to generate per edge.
18
+ * We exponentially reduce resolution as level increases so it doesn't affect perf
19
+ * when there are a large number of cells
20
+ */
21
+ // eslint-disable-next-line max-statements
22
+ export function getS2GeoBoundsFromCell({
23
+ face,
24
+ ij,
25
+ level
26
+ }: {
27
+ face: number;
28
+ ij: [number, number];
29
+ level: number;
30
+ }): Float64Array {
31
+ const offsets = [
32
+ [0, 0],
33
+ [0, 1],
34
+ [1, 1],
35
+ [1, 0],
36
+ [0, 0]
37
+ ];
38
+
39
+ const resolution = Math.max(1, Math.ceil(MAX_RESOLUTION * Math.pow(2, -level)));
40
+ const result = new Float64Array(4 * resolution * 2 + 2);
41
+ let ptIndex = 0;
42
+ let prevLng = 0;
43
+
44
+ for (let i = 0; i < 4; i++) {
45
+ const offset = offsets[i].slice(0) as [number, number];
46
+ const nextOffset = offsets[i + 1];
47
+ const stepI = (nextOffset[0] - offset[0]) / resolution;
48
+ const stepJ = (nextOffset[1] - offset[1]) / resolution;
49
+
50
+ for (let j = 0; j < resolution; j++) {
51
+ offset[0] += stepI;
52
+ offset[1] += stepJ;
53
+ // Cell can be represented by coordinates IJ, ST, UV, XYZ
54
+ // http://s2geometry.io/devguide/s2cell_hierarchy#coordinate-systems
55
+ const st = IJToST(ij, level, offset);
56
+ const uv = STToUV(st);
57
+ const xyz = FaceUVToXYZ(face, uv);
58
+ const lngLat = XYZToLngLat(xyz);
59
+
60
+ // Adjust longitude for Web Mercator projection
61
+ if (Math.abs(lngLat[1]) > 89.999) {
62
+ lngLat[0] = prevLng;
63
+ }
64
+ const deltaLng = lngLat[0] - prevLng;
65
+ lngLat[0] += deltaLng > 180 ? -360 : deltaLng < -180 ? 360 : 0;
66
+
67
+ result[ptIndex++] = lngLat[0];
68
+ result[ptIndex++] = lngLat[1];
69
+ prevLng = lngLat[0];
70
+ }
71
+ }
72
+ // close the loop
73
+ result[ptIndex++] = result[0];
74
+ result[ptIndex++] = result[1];
75
+ return result;
76
+ }
@@ -0,0 +1,70 @@
1
+ // math.gl
2
+ // SPDX-License-Identifier: MIT and Apache-2.0
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ // s2-geometry is a pure JavaScript port of Google/Niantic's S2 Geometry library
6
+ // which is perfect since it works in the browser.
7
+
8
+ // const MAXIMUM_TOKEN_LENGTH = 16;
9
+
10
+ // INDEX CALCULATIONS
11
+
12
+ /**
13
+ * Given an S2 token (String) this function convert the token to 64 bit id (Index)
14
+ * 'X' is the empty cell
15
+ * https://github.com/google/s2-geometry-library-java/blob/c04b68bf3197a9c34082327eeb3aec7ab7c85da1/src/com/google/common/geometry/S2CellId.java#L439
16
+ */
17
+ export function getS2IndexFromToken(token: string): bigint {
18
+ if (token === 'X') {
19
+ token = '';
20
+ }
21
+ // pad token with zeros to make the length 16
22
+ const paddedToken = token.padEnd(16, '0');
23
+ return BigInt(`0x${paddedToken}`);
24
+ }
25
+
26
+ /**
27
+ * Convert a 64 bit number to a string token
28
+ * 'X' is the empty cell
29
+ */
30
+ export function getS2TokenFromIndex(cellId: bigint): string {
31
+ if (cellId === 0n) {
32
+ return 'X';
33
+ }
34
+ let numZeroDigits = countTrailingZeros(cellId);
35
+
36
+ const remainder = numZeroDigits % 4;
37
+ numZeroDigits = (numZeroDigits - remainder) / 4;
38
+ const trailingZeroHexChars = numZeroDigits;
39
+ numZeroDigits *= 4;
40
+
41
+ const x = cellId >> BigInt(numZeroDigits);
42
+ const hexString = x.toString(16).replace(/0+$/, '');
43
+ const zeroString = Array(17 - trailingZeroHexChars - hexString.length).join('0');
44
+ return zeroString + hexString;
45
+ }
46
+
47
+ export function getS2ChildIndex(s2Index: bigint, index: number): bigint {
48
+ // Shift sentinel bit 2 positions to the right.
49
+ const newLsb = lsb(s2Index) >> 2n;
50
+ // Insert child index before the sentinel bit.
51
+ const childCellId: bigint = s2Index + BigInt(2 * index + 1 - 4) * newLsb;
52
+ return childCellId;
53
+ }
54
+
55
+ /**
56
+ * Return the lowest-numbered bit that is on for this cell id
57
+ * @private
58
+ */
59
+ function lsb(cellId: bigint): bigint {
60
+ return cellId & (cellId + 1n); // eslint-disable-line
61
+ }
62
+
63
+ function countTrailingZeros(n: bigint): number {
64
+ let count = 0;
65
+ while (n % 2n === 0n) {
66
+ n /= 2n;
67
+ count++;
68
+ }
69
+ return count;
70
+ }
package/src/s2.ts ADDED
@@ -0,0 +1,6 @@
1
+ // math.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ export {S2Decoder} from './s2-decoder';
6
+ export {getS2ChildIndex} from './s2-geometry/s2-token';