webdggrid 1.0.4 → 1.0.7

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.
@@ -1,69 +1,256 @@
1
1
  // @ts-ignore
2
2
  import { loadWasm, unloadWasm } from './libdggrid.wasm.js';
3
+ import { Feature, FeatureCollection, GeoJsonProperties, Polygon, Position } from 'geojson';
3
4
 
4
5
  /**
5
- * Cell Topology
6
+ * The shape of each cell in the Discrete Global Grid System.
7
+ *
8
+ * DGGRID supports four cell topologies. The most common choice for geospatial
9
+ * analysis is `HEXAGON` because hexagonal cells have equal adjacency (every
10
+ * neighbour shares an edge), uniform area, and minimal boundary-to-area ratio.
6
11
  *
7
12
  * @export
8
- * @enum {String}
13
+ * @enum {string}
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * import { Topology } from 'webdggrid';
18
+ *
19
+ * dggs.setDggs({ topology: Topology.HEXAGON, ... }, 4);
20
+ * ```
9
21
  */
10
22
  export enum Topology {
23
+ /** Six-sided cells — the default and most widely used topology. */
11
24
  'HEXAGON' = 'HEXAGON',
25
+ /** Three-sided cells. */
12
26
  'TRIANGLE' = 'TRIANGLE',
13
- 'SQUARE' = 'SQUARE',
27
+ /** Four-sided diamond cells (squares rotated 45°). */
14
28
  'DIAMOND' = 'DIAMOND',
15
29
  }
30
+
16
31
  /**
17
- * Projection type
32
+ * The map projection used to place the polyhedron faces onto the sphere.
33
+ *
34
+ * - **ISEA** (Icosahedral Snyder Equal Area) — preserves cell area at the cost
35
+ * of shape distortion. Recommended for most analytical use-cases.
36
+ * - **FULLER** (Fuller/Dymaxion) — minimises shape distortion but does not
37
+ * preserve equal area.
18
38
  *
19
39
  * @export
20
- * @enum {number}
40
+ * @enum {string}
41
+ *
42
+ * @example
43
+ * ```ts
44
+ * import { Projection } from 'webdggrid';
45
+ *
46
+ * dggs.setDggs({ projection: Projection.ISEA, ... }, 4);
47
+ * ```
21
48
  */
22
49
  export enum Projection {
50
+ /** Icosahedral Snyder Equal Area projection — equal-area cells. */
23
51
  'ISEA' = 'ISEA',
52
+ /** Fuller/Dymaxion projection — shape-preserving cells. */
24
53
  'FULLER' = 'FULLER',
25
54
  }
26
55
 
56
+ /**
57
+ * A simple geographic coordinate expressed as latitude and longitude in
58
+ * decimal degrees (WGS-84).
59
+ *
60
+ * @example
61
+ * ```ts
62
+ * const coord: Coordinate = { lat: 51.5, lng: -0.1 };
63
+ * ```
64
+ */
27
65
  export interface Coordinate {
66
+ /** Latitude in decimal degrees. Range: −90 to 90. */
28
67
  lat: number;
68
+ /** Longitude in decimal degrees. Range: −180 to 180. */
29
69
  lng: number;
30
70
  }
31
71
 
72
+ /**
73
+ * GeoJSON `properties` object attached to every cell feature returned by
74
+ * {@link Webdggrid.sequenceNumToGridFeatureCollection}.
75
+ *
76
+ * All numeric identifiers are `BigInt` because DGGRID cell sequence numbers
77
+ * can exceed the safe integer range of IEEE-754 doubles at high resolutions.
78
+ *
79
+ * > **Note for MapLibre / Mapbox users:** structured-clone (used internally
80
+ * > by these libraries' Web Workers) cannot serialise `BigInt`. Convert `id`
81
+ * > to a string before calling `source.setData()`.
82
+ */
83
+ export type DGGSGeoJsonProperty = GeoJsonProperties & {
84
+ /**
85
+ * The DGGS sequence number (cell ID) of this feature.
86
+ * Unique within a given DGGS configuration and resolution.
87
+ */
88
+ id?: BigInt;
89
+ /** Column index in an (i, j) address scheme, if available. */
90
+ i?: BigInt;
91
+ /** Row index in an (i, j) address scheme, if available. */
92
+ j?: BigInt;
93
+ }
94
+
95
+ /**
96
+ * Full configuration of a Discrete Global Grid System.
97
+ *
98
+ * A DGGS is fully defined by its polyhedron orientation (`poleCoordinates`,
99
+ * `azimuth`), the subdivision scheme (`aperture`), the cell shape
100
+ * (`topology`), and the map projection (`projection`).
101
+ *
102
+ * Pass this object to {@link Webdggrid.setDggs} to switch between grid
103
+ * configurations at runtime.
104
+ *
105
+ * @example
106
+ * ```ts
107
+ * const myDggs: IDGGSProps = {
108
+ * poleCoordinates: { lat: 58.28, lng: 11.25 }, // Snyder orientation
109
+ * azimuth: 0,
110
+ * aperture: 4,
111
+ * topology: Topology.HEXAGON,
112
+ * projection: Projection.ISEA,
113
+ * };
114
+ * dggs.setDggs(myDggs, 5);
115
+ * ```
116
+ */
32
117
  export interface IDGGSProps {
118
+ /**
119
+ * Geographic location of the icosahedron pole used to orient the grid.
120
+ * Changing this rotates the entire grid on the globe, which can be used
121
+ * to minimise cell distortion over a region of interest.
122
+ * Defaults to `{ lat: 0, lng: 0 }`.
123
+ */
33
124
  poleCoordinates: Coordinate;
125
+ /**
126
+ * Azimuth of the icosahedron pole in decimal degrees.
127
+ * Rotates the grid around the pole axis. Defaults to `0`.
128
+ */
34
129
  azimuth: number;
130
+ /**
131
+ * Subdivision aperture — the number of child cells each parent cell is
132
+ * divided into when moving to the next finer resolution.
133
+ *
134
+ * | Aperture | Cells at res *r* (HEXAGON/ISEA) |
135
+ * |---|---|
136
+ * | 3 | 2 + 10 × 3^r |
137
+ * | 4 | 2 + 10 × 4^r |
138
+ * | 7 | 2 + 10 × 7^r |
139
+ *
140
+ * Aperture `4` is the most common choice and is the default.
141
+ */
35
142
  aperture: 3 | 4 | 5 | 7;
143
+ /** Shape of each cell. See {@link Topology}. */
36
144
  topology: Topology;
145
+ /** Projection used to map the polyhedron faces onto the sphere. See {@link Projection}. */
37
146
  projection: Projection;
38
147
  }
39
148
 
149
+ /**
150
+ * Rewraps a polygon ring that crosses the antimeridian so that all longitudes
151
+ * are in a contiguous range (some may exceed 180°). This is the format
152
+ * expected by MapLibre GL / Mapbox GL globe projection for antimeridian cells.
153
+ * For renderers that require standard [-180, 180] coordinates, use the raw
154
+ * output from {@link Webdggrid.sequenceNumToGrid} directly.
155
+ */
156
+ export function unwrapAntimeridianRing(ring: Position[]): Position[] {
157
+ const lons = ring.map((p) => p[0]);
158
+ const minLon = Math.min(...lons);
159
+ const maxLon = Math.max(...lons);
160
+ if (maxLon - minLon <= 180) return ring;
161
+ return ring.map(([lon, lat]) => [lon < 0 ? lon + 360 : lon, lat]);
162
+ }
163
+
40
164
  const DEFAULT_RESOLUTION = 1;
41
165
  const DEFAULT_DGGS = {
42
166
  poleCoordinates: { lat: 0, lng: 0 },
43
167
  azimuth: 0,
44
168
  topology: Topology.HEXAGON,
45
169
  projection: Projection.ISEA,
46
- aperture: 7
170
+ aperture: 4
47
171
  } as IDGGSProps;
48
172
 
173
+ /**
174
+ * Main entry point for the WebDggrid library.
175
+ *
176
+ * `Webdggrid` wraps the DGGRID C++ library compiled to WebAssembly and exposes
177
+ * methods for:
178
+ * - **Grid configuration** — choose topology, projection, aperture, and
179
+ * resolution via {@link setDggs} / {@link setResolution}.
180
+ * - **Coordinate conversion** — convert between geographic coordinates and
181
+ * DGGS cell IDs (sequence numbers) with {@link geoToSequenceNum} and
182
+ * {@link sequenceNumToGeo}.
183
+ * - **Grid geometry** — retrieve the polygon boundary of any cell with
184
+ * {@link sequenceNumToGrid} or export a ready-to-render GeoJSON
185
+ * `FeatureCollection` with {@link sequenceNumToGridFeatureCollection}.
186
+ * - **Grid statistics** — query cell counts, areas, and spacings with
187
+ * {@link nCells}, {@link cellAreaKM}, and {@link cellDistKM}.
188
+ *
189
+ * ## Quick start
190
+ *
191
+ * ```ts
192
+ * import { Webdggrid } from 'webdggrid';
193
+ *
194
+ * const dggs = await Webdggrid.load();
195
+ *
196
+ * // Convert a geographic point to its DGGS cell ID at resolution 5
197
+ * const [cellId] = dggs.geoToSequenceNum([[-73.9857, 40.7484]], 5);
198
+ *
199
+ * // Get the polygon boundary of that cell as GeoJSON
200
+ * const geojson = dggs.sequenceNumToGridFeatureCollection([cellId], 5);
201
+ * ```
202
+ *
203
+ * ## Lifecycle
204
+ *
205
+ * The WASM module is a singleton. Call {@link Webdggrid.load} once and reuse
206
+ * the returned instance throughout your application. Call
207
+ * {@link Webdggrid.unload} when you are completely done to free memory.
208
+ */
49
209
  export class Webdggrid {
50
210
 
211
+ /**
212
+ * The active DGGS configuration used by all conversion and statistics
213
+ * methods. Change it at any time via {@link setDggs}.
214
+ *
215
+ * Defaults to ISEA4H (ISEA projection, aperture 4, hexagon topology,
216
+ * pole at 0° N 0° E, azimuth 0°).
217
+ */
51
218
  dggs: IDGGSProps = DEFAULT_DGGS;
219
+
220
+ /**
221
+ * The active grid resolution. Higher values produce finer, smaller cells.
222
+ * The valid range depends on the aperture — for aperture 4 the practical
223
+ * limit is around resolution 15 before cell counts become unwieldy.
224
+ *
225
+ * Change via {@link setResolution} or pass an explicit `resolution`
226
+ * argument to any conversion method.
227
+ *
228
+ * Defaults to `1`.
229
+ */
52
230
  resolution: number = DEFAULT_RESOLUTION;
53
231
 
54
232
  private constructor(protected _module: any) {
55
233
  this._module = _module;
56
-
57
234
  }
58
235
 
59
236
  /**
60
- * Compiles and instantiates the raw wasm.
61
- *
237
+ * Compiles and instantiates the DGGRID WebAssembly module.
238
+ *
239
+ * This is the only way to construct a `Webdggrid` instance. The method is
240
+ * asynchronous because WebAssembly compilation is prohibited on the main
241
+ * thread for buffers larger than 4 KB.
242
+ *
243
+ * ```ts
244
+ * const dggs = await Webdggrid.load();
245
+ * ```
246
+ *
62
247
  * ::: info
63
- * In general WebAssembly compilation is disallowed on the main thread if the buffer size is larger than 4KB, hence forcing `load` to be asynchronous;
248
+ * In general WebAssembly compilation is disallowed on the main thread if
249
+ * the buffer size is larger than 4 KB, hence forcing `load` to be
250
+ * asynchronous.
64
251
  * :::
65
- *
66
- * @returns A promise to an instance of the Webdggrid class.
252
+ *
253
+ * @returns A promise that resolves to a fully initialised `Webdggrid` instance.
67
254
  */
68
255
  static load(): Promise<typeof Webdggrid> {
69
256
  return loadWasm().then((module: any) => {
@@ -72,22 +259,47 @@ export class Webdggrid {
72
259
  }
73
260
 
74
261
  /**
75
- * Unloades the compiled wasm instance.
262
+ * Releases the compiled WASM instance and frees its memory.
263
+ *
264
+ * Call this when your application no longer needs the library. After
265
+ * calling `unload`, any existing `Webdggrid` instances become unusable —
266
+ * you must call {@link load} again to create a new one.
76
267
  */
77
268
  static unload() {
78
269
  unloadWasm();
79
270
  }
80
271
 
81
272
  /**
82
- * @returns The Webdggrid c++ version
273
+ * Returns the version string of the underlying DGGRID C++ library.
274
+ *
275
+ * ```ts
276
+ * console.log(dggs.version()); // e.g. "8.3b"
277
+ * ```
278
+ *
279
+ * @returns The DGGRID C++ library version string.
83
280
  */
84
281
  version(): string {
85
282
  return this._module.Webdggrid.prototype.version();
86
283
  }
87
284
 
88
285
  /**
89
- * Set the main dggs configuration
90
- * @param dggs A dggs object
286
+ * Sets both the DGGS configuration and the resolution in one call.
287
+ *
288
+ * All subsequent conversion and statistics methods will use this
289
+ * configuration unless they receive an explicit `resolution` argument.
290
+ *
291
+ * ```ts
292
+ * dggs.setDggs({
293
+ * poleCoordinates: { lat: 0, lng: 0 },
294
+ * azimuth: 0,
295
+ * aperture: 4,
296
+ * topology: Topology.HEXAGON,
297
+ * projection: Projection.ISEA,
298
+ * }, 5);
299
+ * ```
300
+ *
301
+ * @param dggs - The new DGGS configuration. Defaults to ISEA4H at pole (0,0).
302
+ * @param resolution - The new resolution level. Defaults to `1`.
91
303
  */
92
304
  setDggs(dggs: IDGGSProps = DEFAULT_DGGS, resolution: number = DEFAULT_RESOLUTION) {
93
305
  this.dggs = dggs;
@@ -95,38 +307,66 @@ export class Webdggrid {
95
307
  }
96
308
 
97
309
  /**
98
- * Get the resolution of the current dggs
99
- * @returns {number} the current dggs resolution
100
- * @memberof WebDggrid
310
+ * Returns the currently active grid resolution.
311
+ *
312
+ * ```ts
313
+ * dggs.setResolution(7);
314
+ * console.log(dggs.getResolution()); // 7
315
+ * ```
316
+ *
317
+ * @returns The current resolution level.
101
318
  */
102
319
  getResolution(): number {
103
320
  return this.resolution;
104
321
  }
322
+
105
323
  /**
106
- * Set the resolution of the dggs
107
- * @param {number} [resolution=DEFAULT_RESOLUTION] the resolution. It should be a valid integer
108
- * @memberof WebDggrid
324
+ * Sets the grid resolution used by default in all conversion and
325
+ * statistics methods.
326
+ *
327
+ * ```ts
328
+ * dggs.setResolution(5);
329
+ * const count = dggs.nCells(); // uses resolution 5
330
+ * ```
331
+ *
332
+ * @param resolution - The new resolution level. Must be a positive integer.
109
333
  */
110
334
  setResolution(resolution: number) {
111
335
  this.resolution = resolution;
112
336
  }
113
337
 
114
338
  /**
115
- * test function
116
- *
117
- * @return {*}
118
- * @memberof WebDggrid
339
+ * Internal test helper that invokes the WASM module's `_main` entry point.
340
+ * Not intended for production use.
341
+ * @internal
119
342
  */
120
343
  _main() {
121
344
  return this._module._main();
122
345
  }
123
346
 
124
347
  /**
125
- * @follow Hi
126
- * Returns the number of the cells in specific resolution
127
- * @param {number} [resolution=DEFAULT_RESOLUTION]
128
- * @return {number}
129
- * @memberof WebDggrid
348
+ * Returns the total number of cells that tile the entire globe at the
349
+ * given resolution under the current DGGS configuration.
350
+ *
351
+ * Cell counts grow exponentially with resolution. For the default ISEA4H
352
+ * grid:
353
+ *
354
+ * | Resolution | Approx. cell count |
355
+ * |---|---|
356
+ * | 1 | 42 |
357
+ * | 2 | 162 |
358
+ * | 3 | 642 |
359
+ * | 4 | 2 562 |
360
+ * | 5 | 10 242 |
361
+ * | 6 | 40 962 |
362
+ *
363
+ * ```ts
364
+ * const total = dggs.nCells(3); // 642
365
+ * ```
366
+ *
367
+ * @param resolution - Resolution level to query. Defaults to the instance's
368
+ * current {@link resolution}.
369
+ * @returns Total number of cells at the given resolution.
130
370
  */
131
371
  nCells(resolution: number = DEFAULT_RESOLUTION): number {
132
372
  const {
@@ -138,8 +378,8 @@ export class Webdggrid {
138
378
  } = this.dggs;
139
379
 
140
380
  const cellCount = this._module.nCells(
141
- lat,
142
381
  lng,
382
+ lat,
143
383
  azimuth,
144
384
  aperture,
145
385
  resolution,
@@ -150,6 +390,21 @@ export class Webdggrid {
150
390
  return cellCount as number;
151
391
  }
152
392
 
393
+ /**
394
+ * Returns the average area of a single cell in square kilometres at the
395
+ * given resolution.
396
+ *
397
+ * Because ISEA guarantees equal-area cells, all cells have the same area
398
+ * when using the `ISEA` projection. With `FULLER` the value is an average.
399
+ *
400
+ * ```ts
401
+ * const areakm2 = dggs.cellAreaKM(5);
402
+ * ```
403
+ *
404
+ * @param resolution - Resolution level to query. Defaults to the instance's
405
+ * current {@link resolution}.
406
+ * @returns Average cell area in km².
407
+ */
153
408
  cellAreaKM(resolution: number = DEFAULT_RESOLUTION): number {
154
409
  const {
155
410
  poleCoordinates: { lat, lng },
@@ -159,9 +414,9 @@ export class Webdggrid {
159
414
  aperture,
160
415
  } = this.dggs;
161
416
 
162
- const cellCount = this._module.nCells(
163
- lat,
417
+ const cellCount = this._module.cellAreaKM(
164
418
  lng,
419
+ lat,
165
420
  azimuth,
166
421
  aperture,
167
422
  resolution,
@@ -172,6 +427,21 @@ export class Webdggrid {
172
427
  return cellCount as number;
173
428
  }
174
429
 
430
+ /**
431
+ * Returns the average centre-to-centre distance between neighbouring cells
432
+ * in kilometres at the given resolution.
433
+ *
434
+ * This is useful for estimating spatial join radii or selecting a
435
+ * resolution that matches a target spatial scale.
436
+ *
437
+ * ```ts
438
+ * const spacingKm = dggs.cellDistKM(5);
439
+ * ```
440
+ *
441
+ * @param resolution - Resolution level to query. Defaults to the instance's
442
+ * current {@link resolution}.
443
+ * @returns Average cell spacing in km.
444
+ */
175
445
  cellDistKM(resolution: number = DEFAULT_RESOLUTION): number {
176
446
  const {
177
447
  poleCoordinates: { lat, lng },
@@ -181,9 +451,9 @@ export class Webdggrid {
181
451
  aperture,
182
452
  } = this.dggs;
183
453
 
184
- const cellCount = this._module.nCells(
185
- lat,
454
+ const cellCount = this._module.cellDistKM(
186
455
  lng,
456
+ lat,
187
457
  azimuth,
188
458
  aperture,
189
459
  resolution,
@@ -194,6 +464,22 @@ export class Webdggrid {
194
464
  return cellCount as number;
195
465
  }
196
466
 
467
+ /**
468
+ * Returns the characteristic length scale (CLS) of the grid at the given
469
+ * resolution — defined as the square root of the average cell area.
470
+ *
471
+ * CLS provides a single scalar that summarises the spatial granularity of
472
+ * the grid, useful for comparing resolutions across different DGGS
473
+ * configurations.
474
+ *
475
+ * ```ts
476
+ * const cls = dggs.gridStatCLS(4);
477
+ * ```
478
+ *
479
+ * @param resolution - Resolution level to query. Defaults to the instance's
480
+ * current {@link resolution}.
481
+ * @returns Grid CLS value.
482
+ */
197
483
  gridStatCLS(resolution: number = DEFAULT_RESOLUTION): number {
198
484
  const {
199
485
  poleCoordinates: { lat, lng },
@@ -203,9 +489,9 @@ export class Webdggrid {
203
489
  aperture,
204
490
  } = this.dggs;
205
491
 
206
- const cellCount = this._module.nCells(
207
- lat,
492
+ const cellCount = this._module.gridStatCLS(
208
493
  lng,
494
+ lat,
209
495
  azimuth,
210
496
  aperture,
211
497
  resolution,
@@ -215,11 +501,35 @@ export class Webdggrid {
215
501
 
216
502
  return cellCount as number;
217
503
  }
504
+
218
505
  /**
219
- * Converts an array of geography coordinates to the list of the sequence numbers AKA DggId
220
- * @param coordinates A 2d array of [[lng, lat]] values
221
- * @param resolution [resolution=DEFAULT_RESOLUTION] The dggs resolution
222
- * @returns An array of the DggIds
506
+ * Converts an array of geographic coordinates to their corresponding DGGS
507
+ * cell sequence numbers (cell IDs) at the given resolution.
508
+ *
509
+ * Each coordinate is mapped to the single cell whose boundary contains it.
510
+ * Multiple coordinates that fall within the same cell will return the same
511
+ * sequence number.
512
+ *
513
+ * Coordinates must be supplied in **`[lng, lat]`** order (GeoJSON
514
+ * convention), **not** `[lat, lng]`.
515
+ *
516
+ * ```ts
517
+ * // New York City
518
+ * const ids = dggs.geoToSequenceNum([[-74.006, 40.7128]], 5);
519
+ * console.log(ids); // [12345n]
520
+ *
521
+ * // Multiple points at once
522
+ * const ids2 = dggs.geoToSequenceNum(
523
+ * [[-74.006, 40.7128], [2.3522, 48.8566]],
524
+ * 5
525
+ * );
526
+ * ```
527
+ *
528
+ * @param coordinates - Array of `[lng, lat]` pairs in decimal degrees.
529
+ * @param resolution - Resolution at which to perform the lookup. Defaults
530
+ * to the instance's current {@link resolution}.
531
+ * @returns Array of `BigInt` sequence numbers, one per input coordinate,
532
+ * in the same order.
223
533
  */
224
534
  geoToSequenceNum(
225
535
  coordinates: number[][],
@@ -237,8 +547,8 @@ export class Webdggrid {
237
547
  const yCoords = coordinates.map((coord) => coord[1]);
238
548
 
239
549
  const resultArray = this._module.DgGEO_to_SEQNUM(
240
- lat,
241
550
  lng,
551
+ lat,
242
552
  azimuth,
243
553
  aperture,
244
554
  resolution,
@@ -250,16 +560,29 @@ export class Webdggrid {
250
560
 
251
561
  return resultArray;
252
562
  }
563
+
253
564
  /**
254
- * Convert a sequence number to the [lng,lat] of the center of the related cell
255
- * @param sequenceNum
256
- * @param resolution [resolution=DEFAULT_RESOLUTION]
257
- * @returns An array of [lng,lat]
565
+ * Converts an array of DGGS cell sequence numbers to the geographic
566
+ * coordinates of their centroids.
567
+ *
568
+ * The returned coordinates are in **`[lng, lat]`** order (GeoJSON
569
+ * convention).
570
+ *
571
+ * ```ts
572
+ * const centroids = dggs.sequenceNumToGeo([1n, 2n, 3n], 3);
573
+ * // [[lng0, lat0], [lng1, lat1], [lng2, lat2]]
574
+ * ```
575
+ *
576
+ * @param sequenceNum - Array of `BigInt` cell IDs to look up.
577
+ * @param resolution - Resolution at which the IDs were generated. Defaults
578
+ * to the instance's current {@link resolution}.
579
+ * @returns Array of `[lng, lat]` centroid positions, one per input ID, in
580
+ * the same order.
258
581
  */
259
582
  sequenceNumToGeo(
260
583
  sequenceNum: bigint[],
261
584
  resolution: number = DEFAULT_RESOLUTION
262
- ): number[][] {
585
+ ): Position[] {
263
586
  const {
264
587
  poleCoordinates: { lat, lng },
265
588
  azimuth,
@@ -269,8 +592,8 @@ export class Webdggrid {
269
592
  } = this.dggs;
270
593
 
271
594
  const resultArray = this._module.SEQNUM_to_GEO(
272
- lat,
273
595
  lng,
596
+ lat,
274
597
  azimuth,
275
598
  aperture,
276
599
  resolution,
@@ -279,24 +602,46 @@ export class Webdggrid {
279
602
  sequenceNum
280
603
  );
281
604
 
282
- const size = resultArray.length/2;
283
- const arrayOfArrays:number[][] = [];
605
+ const size = resultArray.length / 2;
606
+ const arrayOfArrays: number[][] = [];
284
607
  for (let i = 0; i < size; i += 1) {
285
- arrayOfArrays.push([resultArray[i], resultArray[i+size]]);
608
+ arrayOfArrays.push([resultArray[i], resultArray[i + size]]);
286
609
  }
287
610
 
288
611
  return arrayOfArrays;
289
612
  }
613
+
290
614
  /**
291
- * Converts a set of coordinates to the cell centroid values
292
- * @param coordinates A 2d array of lng and lat values
293
- * @param resolution [resolution=DEFAULT_RESOLUTION] The resolution of the dggs
294
- * @returns An array of dggs cell centroid coordinates
615
+ * Snaps an array of geographic coordinates to the centroid of the DGGS
616
+ * cell that contains each point.
617
+ *
618
+ * This is equivalent to calling {@link geoToSequenceNum} followed by
619
+ * {@link sequenceNumToGeo} but is more efficient because it avoids
620
+ * returning the intermediate sequence numbers.
621
+ *
622
+ * Useful for spatial aggregation: all points that fall within the same
623
+ * cell will map to the identical centroid coordinate.
624
+ *
625
+ * Coordinates must be in **`[lng, lat]`** order.
626
+ *
627
+ * ```ts
628
+ * const snapped = dggs.geoToGeo(
629
+ * [[-74.006, 40.7128], [-74.010, 40.720]],
630
+ * 5
631
+ * );
632
+ * // Both points snap to the same centroid if they share a cell
633
+ * ```
634
+ *
635
+ * @param coordinates - Array of `[lng, lat]` pairs in decimal degrees.
636
+ * @param resolution - Resolution at which to perform the snapping. Defaults
637
+ * to the instance's current {@link resolution}.
638
+ * @returns Array of `[lng, lat]` cell centroid positions, one per input
639
+ * coordinate, in the same order.
295
640
  */
296
641
  geoToGeo(
297
642
  coordinates: number[][],
298
643
  resolution: number = DEFAULT_RESOLUTION
299
- ): number[][] {
644
+ ): Position[] {
300
645
  const {
301
646
  poleCoordinates: { lat, lng },
302
647
  azimuth,
@@ -309,8 +654,8 @@ export class Webdggrid {
309
654
  const yCoords = coordinates.map((coord) => coord[1]);
310
655
 
311
656
  const resultArray = this._module.GEO_to_GEO(
312
- lat,
313
657
  lng,
658
+ lat,
314
659
  azimuth,
315
660
  aperture,
316
661
  resolution,
@@ -320,47 +665,155 @@ export class Webdggrid {
320
665
  yCoords
321
666
  );
322
667
 
323
- const size = resultArray.length/2;
324
- const arrayOfArrays:number[][] = [];
668
+ const size = resultArray.length / 2;
669
+ const arrayOfArrays: number[][] = [];
325
670
  for (let i = 0; i < size; i += 1) {
326
- arrayOfArrays.push([resultArray[i], resultArray[i+size]]);
671
+ arrayOfArrays.push([resultArray[i], resultArray[i + size]]);
327
672
  }
328
673
 
329
674
  return arrayOfArrays;
330
675
  }
331
676
 
332
- _is2dArray(array: any): boolean { return array.some((item: any) => Array.isArray(item)); }
333
-
334
- _arrayToVector(array: any) {
335
- const is2d = this._is2dArray(array);
336
- if (is2d) {
337
- const dDVector = new this._module.DoubleVectorVector();
338
- array.forEach((item: any) => {
339
- const dVector = new this._module.DoubleVector();
340
- dVector.push_back(item[0]);
341
- dVector.push_back(item[1]);
342
- dDVector.push_back(dVector);
343
- });
344
- return dDVector;
345
- }
346
- }
347
- _vectorToArray(vector: any) { return new Array(vector.size()).fill(0).map((_, id) => vector.get(id)); }
677
+ /**
678
+ * Returns the polygon boundary vertices for each cell in `sequenceNum`.
679
+ *
680
+ * Each cell is represented as an array of `[lng, lat]` vertex positions.
681
+ * The ring is **not** automatically closed (the first and last vertex are
682
+ * different) close it yourself if your renderer requires it.
683
+ *
684
+ * Prefer {@link sequenceNumToGridFeatureCollection} when you need
685
+ * GeoJSON output ready for a mapping library.
686
+ *
687
+ * ```ts
688
+ * const rings = dggs.sequenceNumToGrid([1n, 2n], 3);
689
+ * // rings[0] = [[lng0,lat0], [lng1,lat1], ..., [lng5,lat5]] (hexagon)
690
+ * ```
691
+ *
692
+ * @param sequenceNum - Array of `BigInt` cell IDs whose boundaries to
693
+ * retrieve.
694
+ * @param resolution - Resolution at which the IDs were generated. Defaults
695
+ * to the instance's current {@link resolution}.
696
+ * @returns A 2-D array: `result[i]` is the vertex ring of `sequenceNum[i]`.
697
+ * Each vertex is a `[lng, lat]` position.
698
+ * @throws If the WASM module encounters an invalid cell ID.
699
+ */
700
+ sequenceNumToGrid(
701
+ sequenceNum: bigint[],
702
+ resolution: number = DEFAULT_RESOLUTION
703
+ ): Position[][] {
704
+ const {
705
+ poleCoordinates: { lat, lng },
706
+ azimuth,
707
+ topology,
708
+ projection,
709
+ aperture,
710
+ } = this.dggs;
348
711
 
349
- _wVectorToArray = (vector: any) => {
350
- if (vector.size() === 0) {
351
- return [];
712
+ const inputSize = sequenceNum.length;
713
+
714
+ let resultArray = [];
715
+ try {
716
+ resultArray = this._module.SeqNumGrid(
717
+ lng,
718
+ lat,
719
+ azimuth,
720
+ aperture,
721
+ resolution,
722
+ topology,
723
+ projection,
724
+ sequenceNum
725
+ );
726
+ } catch (e) {
727
+ console.error(this._module.getExceptionMessage(e).toString());
728
+ throw(e);
352
729
  }
353
730
 
354
- const objectType = vector.$$.ptrType.name;
731
+ const allShapeVertexes = resultArray.slice(0, inputSize);
732
+
733
+ const sumVertexes = allShapeVertexes.reduce((accumulator, currentValue) => {
734
+ return accumulator + currentValue;
735
+ }, 0);
736
+
737
+ const featureSet: Position[][] = [];
355
738
 
356
- switch (objectType) {
357
- case 'BigIntegerVector*':
358
- return this._vectorToArray(vector);
739
+ let xOffset = inputSize;
740
+ let yOffset = inputSize + sumVertexes;
741
+ for (let i = 0; i < allShapeVertexes.length; i += 1) {
742
+ const numVertexes = allShapeVertexes[i];
359
743
 
360
- default:
361
- return [];
744
+ const currentShapeXVertexes = resultArray.slice(xOffset, xOffset + numVertexes);
745
+ const currentShapeYVertexes = resultArray.slice(yOffset, yOffset + numVertexes);
746
+
747
+ const coordinates: Position[] = [];
748
+ for (let i = 0; i < numVertexes; i += 1) {
749
+ coordinates.push([currentShapeXVertexes[i], currentShapeYVertexes[i]]);
750
+ }
751
+ featureSet.push(unwrapAntimeridianRing(coordinates));
752
+ xOffset += numVertexes;
753
+ yOffset += numVertexes;
362
754
  }
363
- };
364
755
 
365
- // _extractColumn(arr: any, column: number) { return arr.map((x: any) => x[column]); }
756
+ return featureSet;
757
+ }
758
+
759
+ /**
760
+ * Converts an array of DGGS cell IDs into a GeoJSON `FeatureCollection`
761
+ * where each `Feature` is a `Polygon` representing the cell boundary.
762
+ *
763
+ * This is the primary method for rendering DGGS cells with mapping
764
+ * libraries such as MapLibre GL JS, Leaflet, or deck.gl.
765
+ *
766
+ * Each feature includes:
767
+ * - `geometry` — a closed `Polygon` in `[lng, lat]` coordinate order.
768
+ * - `id` — the cell sequence number (converted to `string` recommended
769
+ * before passing to MapLibre to avoid BigInt serialisation errors).
770
+ * - `properties.id` — same value as `id`, accessible inside layer
771
+ * expressions.
772
+ *
773
+ * ```ts
774
+ * const ids = dggs.geoToSequenceNum([[-74.006, 40.7128]], 5);
775
+ * const fc = dggs.sequenceNumToGridFeatureCollection(ids, 5);
776
+ *
777
+ * // MapLibre / structured-clone safe: convert BigInt → string
778
+ * fc.features.forEach(f => {
779
+ * if (typeof f.id === 'bigint') f.id = f.id.toString();
780
+ * if (f.properties?.id) f.properties.id = f.properties.id.toString();
781
+ * });
782
+ *
783
+ * map.getSource('grid').setData(fc);
784
+ * ```
785
+ *
786
+ * @param sequenceNum - Array of `BigInt` cell IDs to convert.
787
+ * @param resolution - Resolution at which the IDs were generated. Defaults
788
+ * to the instance's current {@link resolution}.
789
+ * @returns A GeoJSON `FeatureCollection` of `Polygon` features, one per
790
+ * input cell ID.
791
+ */
792
+ sequenceNumToGridFeatureCollection(
793
+ sequenceNum: bigint[],
794
+ resolution: number = DEFAULT_RESOLUTION
795
+ ): FeatureCollection<Polygon, DGGSGeoJsonProperty> {
796
+
797
+ const coordinatesArray = this.sequenceNumToGrid(sequenceNum, resolution);
798
+
799
+ const features = coordinatesArray.map((coordinates, index) => {
800
+ const seqNum = sequenceNum[index];
801
+ return {
802
+ type: 'Feature',
803
+ geometry: {
804
+ type: 'Polygon',
805
+ coordinates: [coordinates]
806
+ },
807
+ id: seqNum as unknown as number,
808
+ properties: {
809
+ id: seqNum
810
+ } as DGGSGeoJsonProperty
811
+ } as Feature<Polygon, DGGSGeoJsonProperty>;
812
+ });
813
+
814
+ return {
815
+ type: 'FeatureCollection',
816
+ features,
817
+ };
818
+ }
366
819
  }