@math.gl/geoarrow 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 (47) hide show
  1. package/LICENSE +140 -0
  2. package/README.md +311 -0
  3. package/dist/builder.d.ts +90 -0
  4. package/dist/builder.d.ts.map +1 -0
  5. package/dist/builder.js +428 -0
  6. package/dist/builder.js.map +1 -0
  7. package/dist/codecs.d.ts +10 -0
  8. package/dist/codecs.d.ts.map +1 -0
  9. package/dist/codecs.js +122 -0
  10. package/dist/codecs.js.map +1 -0
  11. package/dist/index.cjs +1651 -0
  12. package/dist/index.cjs.map +6 -0
  13. package/dist/index.d.ts +12 -0
  14. package/dist/index.d.ts.map +1 -0
  15. package/dist/index.js +10 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/kernels.d.ts +51 -0
  18. package/dist/kernels.d.ts.map +1 -0
  19. package/dist/kernels.js +359 -0
  20. package/dist/kernels.js.map +1 -0
  21. package/dist/layout.d.ts +52 -0
  22. package/dist/layout.d.ts.map +1 -0
  23. package/dist/layout.js +669 -0
  24. package/dist/layout.js.map +1 -0
  25. package/dist/tessellate.d.ts +31 -0
  26. package/dist/tessellate.d.ts.map +1 -0
  27. package/dist/tessellate.js +102 -0
  28. package/dist/tessellate.js.map +1 -0
  29. package/dist/types.d.ts +113 -0
  30. package/dist/types.d.ts.map +1 -0
  31. package/dist/types.js +41 -0
  32. package/dist/types.js.map +1 -0
  33. package/dist/worker.cjs +73 -0
  34. package/dist/worker.cjs.map +6 -0
  35. package/dist/worker.d.ts +11 -0
  36. package/dist/worker.d.ts.map +1 -0
  37. package/dist/worker.js +10 -0
  38. package/dist/worker.js.map +1 -0
  39. package/package.json +48 -0
  40. package/src/builder.ts +569 -0
  41. package/src/codecs.ts +155 -0
  42. package/src/index.ts +93 -0
  43. package/src/kernels.ts +463 -0
  44. package/src/layout.ts +833 -0
  45. package/src/tessellate.ts +133 -0
  46. package/src/types.ts +200 -0
  47. package/src/worker.ts +20 -0
package/src/builder.ts ADDED
@@ -0,0 +1,569 @@
1
+ // math.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import type {
6
+ GeoArrowArray,
7
+ GeoArrowColumn,
8
+ GeoArrowCoordinateLayout,
9
+ GeoArrowDenseUnion,
10
+ GeoArrowDimension,
11
+ GeoArrowEncoding,
12
+ GeoArrowGeometryValue,
13
+ GeoArrowOffsets,
14
+ GeoArrowStruct
15
+ } from './types';
16
+ import {
17
+ getGeoArrowDimensionSize,
18
+ getGeoArrowEncodingForGeometry,
19
+ getGeoArrowGeometryType
20
+ } from './types';
21
+
22
+ /** Concrete native encodings accepted by {@link GeoArrowBuilder}. */
23
+ export type GeoArrowBuilderEncoding = Exclude<
24
+ GeoArrowEncoding,
25
+ | 'geoarrow.geometry'
26
+ | 'geoarrow.geometrycollection'
27
+ | 'geoarrow.box'
28
+ | 'geoarrow.wkb'
29
+ | 'geoarrow.wkt'
30
+ >;
31
+
32
+ /** Buffer counts produced by a measure pass. */
33
+ export type GeoArrowBuilderMeasurement = Readonly<{
34
+ length: number;
35
+ nullCount: number;
36
+ coordinateCount: number;
37
+ geometryOffsetCount: number;
38
+ partOffsetCount: number;
39
+ ringOffsetCount: number;
40
+ }>;
41
+
42
+ /** Caller-owned buffers filled by a builder write pass. */
43
+ export type GeoArrowBuilderTarget = {
44
+ validity: Uint8Array;
45
+ coordinates:
46
+ | Float32Array
47
+ | Float64Array
48
+ | {
49
+ x: Float32Array | Float64Array;
50
+ y: Float32Array | Float64Array;
51
+ z?: Float32Array | Float64Array;
52
+ m?: Float32Array | Float64Array;
53
+ };
54
+ geometryOffsets?: GeoArrowOffsets;
55
+ partOffsets?: GeoArrowOffsets;
56
+ ringOffsets?: GeoArrowOffsets;
57
+ };
58
+
59
+ /** Common builder options. */
60
+ export type GeoArrowBuilderOptions = Readonly<{
61
+ encoding: GeoArrowBuilderEncoding;
62
+ dimension?: GeoArrowDimension;
63
+ coordinateLayout?: GeoArrowCoordinateLayout;
64
+ offsetType?: 'int32' | 'int64';
65
+ coordinateType?: 'float32' | 'float64';
66
+ }>;
67
+
68
+ /** Options for materializing a column from geometry values. */
69
+ export type GeoArrowColumnFromRowsOptions = Omit<GeoArrowBuilderOptions, 'encoding'> &
70
+ Readonly<{
71
+ /** Forces homogeneous values into a dense-union column. */
72
+ encoding?: 'geoarrow.geometry';
73
+ }>;
74
+
75
+ /** Measure-pass options. */
76
+ export type GeoArrowBuilderMeasureOptions = GeoArrowBuilderOptions & Readonly<{mode: 'measure'}>;
77
+
78
+ /** Write-pass options. */
79
+ export type GeoArrowBuilderWriteOptions = GeoArrowBuilderOptions &
80
+ Readonly<{mode: 'write'; target: GeoArrowBuilderTarget}>;
81
+
82
+ /** Options accepted by the incremental builder. */
83
+ export type GeoArrowBuilderModeOptions =
84
+ | GeoArrowBuilderMeasureOptions
85
+ | GeoArrowBuilderWriteOptions;
86
+
87
+ /**
88
+ * Two-pass writer for homogeneous native GeoArrow geometry columns.
89
+ *
90
+ * Feed the same rows to a measure builder, allocate its target, and then feed them to a write
91
+ * builder. The write result borrows the supplied target buffers.
92
+ */
93
+ export class GeoArrowBuilder {
94
+ readonly encoding: GeoArrowBuilderEncoding;
95
+ readonly dimension: GeoArrowDimension;
96
+ readonly coordinateLayout: GeoArrowCoordinateLayout;
97
+ readonly offsetType: 'int32' | 'int64';
98
+ readonly coordinateType: 'float32' | 'float64';
99
+ private readonly mode: 'measure' | 'write';
100
+ private readonly target?: GeoArrowBuilderTarget;
101
+ private length = 0;
102
+ private nullCount = 0;
103
+ private coordinateCount = 0;
104
+ private partCount = 0;
105
+ private ringCount = 0;
106
+
107
+ constructor(options: GeoArrowBuilderModeOptions) {
108
+ this.mode = options.mode;
109
+ this.encoding = options.encoding;
110
+ this.dimension = options.dimension || 'xy';
111
+ this.coordinateLayout = options.coordinateLayout || 'interleaved';
112
+ this.offsetType = options.offsetType || 'int32';
113
+ this.coordinateType = options.coordinateType || 'float64';
114
+ this.target = options.mode === 'write' ? options.target : undefined;
115
+ if (this.target) this.initializeTargetOffsets(this.target);
116
+ }
117
+
118
+ /** Appends one geometry row or null. */
119
+ append(geometry: GeoArrowGeometryValue | null | undefined): this {
120
+ const rowIndex = this.length++;
121
+ if (!geometry) {
122
+ this.nullCount++;
123
+ this.appendNull(rowIndex);
124
+ return this;
125
+ }
126
+ const expectedType = getGeoArrowGeometryType(this.encoding);
127
+ if (geometry.type !== expectedType) {
128
+ throw new Error(`GeoArrowBuilder for ${this.encoding} cannot append ${geometry.type}`);
129
+ }
130
+ if (geometry.type === 'GeometryCollection') {
131
+ throw new Error('GeoArrowBuilder only accepts concrete geometry families');
132
+ }
133
+ if (this.target) setValidityBit(this.target.validity, rowIndex);
134
+
135
+ const depth = getBuilderDepth(this.encoding);
136
+ const coordinates = geometry.coordinates as readonly unknown[];
137
+ if (depth === 0) {
138
+ this.writeCoordinate(coordinates as readonly number[]);
139
+ } else if (depth === 1) {
140
+ this.writeCoordinateList(coordinates as readonly (readonly number[])[]);
141
+ this.writeOffset(this.target?.geometryOffsets, rowIndex + 1, this.coordinateCount);
142
+ } else if (depth === 2) {
143
+ for (const part of coordinates as readonly (readonly (readonly number[])[])[]) {
144
+ this.writeCoordinateList(part);
145
+ this.partCount++;
146
+ this.writeOffset(this.target?.partOffsets, this.partCount, this.coordinateCount);
147
+ }
148
+ this.writeOffset(this.target?.geometryOffsets, rowIndex + 1, this.partCount);
149
+ } else {
150
+ for (const polygon of coordinates as readonly (readonly (readonly (readonly number[])[])[])[]) {
151
+ for (const ring of polygon) {
152
+ this.writeCoordinateList(ring);
153
+ this.ringCount++;
154
+ this.writeOffset(this.target?.ringOffsets, this.ringCount, this.coordinateCount);
155
+ }
156
+ this.partCount++;
157
+ this.writeOffset(this.target?.partOffsets, this.partCount, this.ringCount);
158
+ }
159
+ this.writeOffset(this.target?.geometryOffsets, rowIndex + 1, this.partCount);
160
+ }
161
+ return this;
162
+ }
163
+
164
+ /** Returns current exact allocation counts. */
165
+ getMeasurement(): GeoArrowBuilderMeasurement {
166
+ const depth = getBuilderDepth(this.encoding);
167
+ return {
168
+ length: this.length,
169
+ nullCount: this.nullCount,
170
+ coordinateCount: this.coordinateCount,
171
+ geometryOffsetCount: depth >= 1 ? this.length + 1 : 0,
172
+ partOffsetCount: depth >= 2 ? this.partCount + 1 : 0,
173
+ ringOffsetCount: depth >= 3 ? this.ringCount + 1 : 0
174
+ };
175
+ }
176
+
177
+ /** Allocates a write target from the current measure pass. */
178
+ allocateTarget(): GeoArrowBuilderTarget {
179
+ if (this.mode !== 'measure') throw new Error('Only a measure builder can allocate a target');
180
+ return allocateGeoArrowBuilderTarget(this.getMeasurement(), {
181
+ encoding: this.encoding,
182
+ dimension: this.dimension,
183
+ coordinateLayout: this.coordinateLayout,
184
+ offsetType: this.offsetType,
185
+ coordinateType: this.coordinateType
186
+ });
187
+ }
188
+
189
+ /** Finishes a write pass and returns a one-chunk borrowed column. */
190
+ finish(): GeoArrowColumn {
191
+ if (!this.target) throw new Error('A measure builder has no finished column');
192
+ const measurement = this.getMeasurement();
193
+ assertTargetCapacity(this.target, measurement, getGeoArrowDimensionSize(this.dimension));
194
+ const coordinates = makeCoordinateArray(
195
+ this.target.coordinates,
196
+ measurement.coordinateCount,
197
+ this.dimension,
198
+ this.coordinateLayout
199
+ );
200
+ const depth = getBuilderDepth(this.encoding);
201
+ let chunk: GeoArrowArray = coordinates;
202
+ if (depth >= 3) {
203
+ chunk = {
204
+ kind: 'list',
205
+ length: this.ringCount,
206
+ offsets: this.target.ringOffsets!,
207
+ child: chunk
208
+ };
209
+ }
210
+ if (depth >= 2) {
211
+ chunk = {
212
+ kind: 'list',
213
+ length: this.partCount,
214
+ offsets: this.target.partOffsets!,
215
+ child: chunk
216
+ };
217
+ }
218
+ if (depth >= 1) {
219
+ chunk = {
220
+ kind: 'list',
221
+ length: this.length,
222
+ offsets: this.target.geometryOffsets!,
223
+ child: chunk,
224
+ validity: {values: this.target.validity}
225
+ };
226
+ } else {
227
+ chunk = {...chunk, validity: {values: this.target.validity}};
228
+ }
229
+ return {
230
+ encoding: this.encoding,
231
+ dimension: this.dimension,
232
+ coordinateLayout: this.coordinateLayout,
233
+ chunks: [chunk]
234
+ };
235
+ }
236
+
237
+ /** Builds a homogeneous column using an internal measure/write pair. */
238
+ static build(
239
+ rows: readonly (GeoArrowGeometryValue | null | undefined)[],
240
+ options: GeoArrowBuilderOptions
241
+ ): GeoArrowColumn {
242
+ const measure = new GeoArrowBuilder({...options, mode: 'measure'});
243
+ for (const row of rows) measure.append(row);
244
+ const write = new GeoArrowBuilder({
245
+ ...options,
246
+ mode: 'write',
247
+ target: measure.allocateTarget()
248
+ });
249
+ for (const row of rows) write.append(row);
250
+ return write.finish();
251
+ }
252
+
253
+ private appendNull(rowIndex: number): void {
254
+ const depth = getBuilderDepth(this.encoding);
255
+ if (depth === 0) {
256
+ this.writeCoordinate(new Array(getGeoArrowDimensionSize(this.dimension)).fill(0));
257
+ } else {
258
+ this.writeOffset(
259
+ this.target?.geometryOffsets,
260
+ rowIndex + 1,
261
+ depth === 1 ? this.coordinateCount : this.partCount
262
+ );
263
+ }
264
+ }
265
+
266
+ private writeCoordinateList(coordinates: readonly (readonly number[])[]): void {
267
+ for (const coordinate of coordinates) this.writeCoordinate(coordinate);
268
+ }
269
+
270
+ private writeCoordinate(coordinate: readonly number[]): void {
271
+ const size = getGeoArrowDimensionSize(this.dimension);
272
+ if (coordinate.length !== size) {
273
+ throw new Error(`Expected ${size} coordinate values for ${this.dimension}`);
274
+ }
275
+ if (this.target)
276
+ writeCoordinate(this.target.coordinates, this.coordinateCount, coordinate, this.dimension);
277
+ this.coordinateCount++;
278
+ }
279
+
280
+ private writeOffset(target: GeoArrowOffsets | undefined, index: number, value: number): void {
281
+ if (!target) return;
282
+ if (target instanceof BigInt64Array) target[index] = BigInt(value);
283
+ else target[index] = value;
284
+ }
285
+
286
+ private initializeTargetOffsets(target: GeoArrowBuilderTarget): void {
287
+ this.writeOffset(target.geometryOffsets, 0, 0);
288
+ this.writeOffset(target.partOffsets, 0, 0);
289
+ this.writeOffset(target.ringOffsets, 0, 0);
290
+ }
291
+ }
292
+
293
+ /** Allocates exact buffers for one measured builder pass. */
294
+ export function allocateGeoArrowBuilderTarget(
295
+ measurement: GeoArrowBuilderMeasurement,
296
+ options: GeoArrowBuilderOptions
297
+ ): GeoArrowBuilderTarget {
298
+ const dimension = options.dimension || 'xy';
299
+ const size = getGeoArrowDimensionSize(dimension);
300
+ const FloatArray = options.coordinateType === 'float32' ? Float32Array : Float64Array;
301
+ const coordinateLayout = options.coordinateLayout || 'interleaved';
302
+ const coordinates =
303
+ coordinateLayout === 'interleaved'
304
+ ? new FloatArray(measurement.coordinateCount * size)
305
+ : makeSeparatedCoordinateTarget(FloatArray, measurement.coordinateCount, dimension);
306
+ const OffsetArray = options.offsetType === 'int64' ? BigInt64Array : Int32Array;
307
+ return {
308
+ validity: new Uint8Array(Math.ceil(measurement.length / 8)),
309
+ coordinates,
310
+ geometryOffsets: measurement.geometryOffsetCount
311
+ ? new OffsetArray(measurement.geometryOffsetCount)
312
+ : undefined,
313
+ partOffsets: measurement.partOffsetCount
314
+ ? new OffsetArray(measurement.partOffsetCount)
315
+ : undefined,
316
+ ringOffsets: measurement.ringOffsetCount
317
+ ? new OffsetArray(measurement.ringOffsetCount)
318
+ : undefined
319
+ };
320
+ }
321
+
322
+ /** Builds a concrete or dense-union column from materialized rows. */
323
+ export function makeGeoArrowColumnFromGeometryRows(
324
+ rows: readonly (GeoArrowGeometryValue | null)[],
325
+ options: GeoArrowColumnFromRowsOptions = {}
326
+ ): GeoArrowColumn {
327
+ const geometryTypes = [...new Set(rows.filter(Boolean).map(row => row!.type))];
328
+ const dimension = options.dimension || inferRowsDimension(rows);
329
+ if (options.encoding === 'geoarrow.geometry') {
330
+ return {
331
+ encoding: 'geoarrow.geometry',
332
+ dimension,
333
+ coordinateLayout: options.coordinateLayout || 'interleaved',
334
+ chunks: [makeDenseUnionArray(rows, options, dimension)]
335
+ };
336
+ }
337
+ if (geometryTypes.length === 0) {
338
+ return GeoArrowBuilder.build(rows, {...options, dimension, encoding: 'geoarrow.point'});
339
+ }
340
+ if (geometryTypes.length === 1 && geometryTypes[0] !== 'GeometryCollection') {
341
+ return GeoArrowBuilder.build(rows, {
342
+ ...options,
343
+ dimension,
344
+ encoding: getGeoArrowEncodingForGeometry(geometryTypes[0]) as GeoArrowBuilderEncoding
345
+ });
346
+ }
347
+ if (geometryTypes.length === 1 && geometryTypes[0] === 'GeometryCollection') {
348
+ return {
349
+ encoding: 'geoarrow.geometrycollection',
350
+ dimension,
351
+ coordinateLayout: options.coordinateLayout || 'interleaved',
352
+ chunks: [makeGeometryCollectionArray(rows, options, dimension)]
353
+ };
354
+ }
355
+ return {
356
+ encoding: 'geoarrow.geometry',
357
+ dimension,
358
+ coordinateLayout: options.coordinateLayout || 'interleaved',
359
+ chunks: [makeDenseUnionArray(rows, options, dimension)]
360
+ };
361
+ }
362
+
363
+ function makeDenseUnionArray(
364
+ rows: readonly (GeoArrowGeometryValue | null)[],
365
+ options: GeoArrowColumnFromRowsOptions,
366
+ dimension: GeoArrowDimension
367
+ ): GeoArrowDenseUnion {
368
+ const nonNullTypes = [...new Set(rows.filter(Boolean).map(row => row!.type))];
369
+ const fallbackType = nonNullTypes[0] || 'Point';
370
+ const childRows = new Map<GeoArrowGeometryValue['type'], GeoArrowGeometryValue[]>();
371
+ for (const type of nonNullTypes.length ? nonNullTypes : [fallbackType]) childRows.set(type, []);
372
+ const typeIds = new Int8Array(rows.length);
373
+ const valueOffsets = new Int32Array(rows.length);
374
+ const validity = new Uint8Array(Math.ceil(rows.length / 8));
375
+ for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) {
376
+ const geometry = rows[rowIndex];
377
+ const type = geometry?.type || fallbackType;
378
+ const values = childRows.get(type) || [];
379
+ typeIds[rowIndex] = getGeometryTypeId(type);
380
+ valueOffsets[rowIndex] = values.length;
381
+ if (geometry) {
382
+ values.push(geometry);
383
+ childRows.set(type, values);
384
+ setValidityBit(validity, rowIndex);
385
+ }
386
+ }
387
+ const children = [...childRows.entries()]
388
+ .sort(([left], [right]) => getGeometryTypeId(left) - getGeometryTypeId(right))
389
+ .map(([type, values]) => {
390
+ const data =
391
+ type === 'GeometryCollection'
392
+ ? makeGeometryCollectionArray(values, options, dimension)
393
+ : GeoArrowBuilder.build(values, {
394
+ ...options,
395
+ dimension,
396
+ encoding: getGeoArrowEncodingForGeometry(type) as GeoArrowBuilderEncoding
397
+ }).chunks[0];
398
+ return {name: type, typeId: getGeometryTypeId(type), data};
399
+ });
400
+ return {
401
+ kind: 'dense-union',
402
+ length: rows.length,
403
+ typeIds,
404
+ valueOffsets,
405
+ children,
406
+ validity: {values: validity}
407
+ };
408
+ }
409
+
410
+ function makeGeometryCollectionArray(
411
+ rows: readonly (GeoArrowGeometryValue | null)[],
412
+ options: GeoArrowColumnFromRowsOptions,
413
+ dimension: GeoArrowDimension
414
+ ): GeoArrowArray {
415
+ const offsets = new Int32Array(rows.length + 1);
416
+ const validity = new Uint8Array(Math.ceil(rows.length / 8));
417
+ const flattened: GeoArrowGeometryValue[] = [];
418
+ for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) {
419
+ const row = rows[rowIndex];
420
+ if (row?.type === 'GeometryCollection') {
421
+ flattened.push(...row.geometries);
422
+ setValidityBit(validity, rowIndex);
423
+ }
424
+ offsets[rowIndex + 1] = flattened.length;
425
+ }
426
+ return {
427
+ kind: 'list',
428
+ length: rows.length,
429
+ offsets,
430
+ child: makeDenseUnionArray(flattened, options, dimension),
431
+ validity: {values: validity}
432
+ };
433
+ }
434
+
435
+ function makeCoordinateArray(
436
+ coordinates: GeoArrowBuilderTarget['coordinates'],
437
+ coordinateCount: number,
438
+ dimension: GeoArrowDimension,
439
+ layout: GeoArrowCoordinateLayout
440
+ ): GeoArrowArray {
441
+ const size = getGeoArrowDimensionSize(dimension);
442
+ if (layout === 'interleaved') {
443
+ const values = coordinates as Float32Array | Float64Array;
444
+ return {
445
+ kind: 'fixed-size-list',
446
+ length: coordinateCount,
447
+ size,
448
+ child: {kind: 'primitive', length: coordinateCount * size, values}
449
+ };
450
+ }
451
+ const separated = coordinates as Exclude<
452
+ GeoArrowBuilderTarget['coordinates'],
453
+ Float32Array | Float64Array
454
+ >;
455
+ const children: Record<string, GeoArrowArray> = {
456
+ x: {kind: 'primitive', length: coordinateCount, values: separated.x},
457
+ y: {kind: 'primitive', length: coordinateCount, values: separated.y}
458
+ };
459
+ if (separated.z) {
460
+ children['z'] = {kind: 'primitive', length: coordinateCount, values: separated.z};
461
+ }
462
+ if (separated.m) {
463
+ children['m'] = {kind: 'primitive', length: coordinateCount, values: separated.m};
464
+ }
465
+ return {kind: 'struct', length: coordinateCount, children} as GeoArrowStruct;
466
+ }
467
+
468
+ function makeSeparatedCoordinateTarget(
469
+ FloatArray: Float32ArrayConstructor | Float64ArrayConstructor,
470
+ count: number,
471
+ dimension: GeoArrowDimension
472
+ ): Exclude<GeoArrowBuilderTarget['coordinates'], Float32Array | Float64Array> {
473
+ return {
474
+ x: new FloatArray(count),
475
+ y: new FloatArray(count),
476
+ z: dimension === 'xyz' || dimension === 'xyzm' ? new FloatArray(count) : undefined,
477
+ m: dimension === 'xym' || dimension === 'xyzm' ? new FloatArray(count) : undefined
478
+ };
479
+ }
480
+
481
+ function writeCoordinate(
482
+ target: GeoArrowBuilderTarget['coordinates'],
483
+ coordinateIndex: number,
484
+ coordinate: readonly number[],
485
+ dimension: GeoArrowDimension
486
+ ): void {
487
+ if (target instanceof Float32Array || target instanceof Float64Array) {
488
+ target.set(coordinate, coordinateIndex * coordinate.length);
489
+ return;
490
+ }
491
+ target.x[coordinateIndex] = coordinate[0];
492
+ target.y[coordinateIndex] = coordinate[1];
493
+ if (dimension === 'xyz') target.z![coordinateIndex] = coordinate[2];
494
+ else if (dimension === 'xym') target.m![coordinateIndex] = coordinate[2];
495
+ else if (dimension === 'xyzm') {
496
+ target.z![coordinateIndex] = coordinate[2];
497
+ target.m![coordinateIndex] = coordinate[3];
498
+ }
499
+ }
500
+
501
+ function setValidityBit(validity: Uint8Array, index: number): void {
502
+ validity[index >> 3] |= 1 << (index & 7);
503
+ }
504
+
505
+ function getBuilderDepth(encoding: GeoArrowBuilderEncoding): 0 | 1 | 2 | 3 {
506
+ switch (encoding) {
507
+ case 'geoarrow.point':
508
+ return 0;
509
+ case 'geoarrow.linestring':
510
+ case 'geoarrow.multipoint':
511
+ return 1;
512
+ case 'geoarrow.polygon':
513
+ case 'geoarrow.multilinestring':
514
+ return 2;
515
+ case 'geoarrow.multipolygon':
516
+ return 3;
517
+ }
518
+ }
519
+
520
+ function getGeometryTypeId(type: GeoArrowGeometryValue['type']): number {
521
+ return (
522
+ [
523
+ 'Point',
524
+ 'LineString',
525
+ 'Polygon',
526
+ 'MultiPoint',
527
+ 'MultiLineString',
528
+ 'MultiPolygon',
529
+ 'GeometryCollection'
530
+ ].indexOf(type) + 1
531
+ );
532
+ }
533
+
534
+ function inferRowsDimension(rows: readonly (GeoArrowGeometryValue | null)[]): GeoArrowDimension {
535
+ let size = 2;
536
+ const visit = (value: unknown): void => {
537
+ if (!Array.isArray(value) || value.length === 0) return;
538
+ if (typeof value[0] === 'number') size = Math.max(size, value.length);
539
+ else for (const child of value) visit(child);
540
+ };
541
+ const visitGeometry = (geometry: GeoArrowGeometryValue): void => {
542
+ if (geometry.type === 'GeometryCollection') {
543
+ for (const child of geometry.geometries) visitGeometry(child);
544
+ } else {
545
+ visit(geometry.coordinates);
546
+ }
547
+ };
548
+ for (const row of rows) {
549
+ if (row) visitGeometry(row);
550
+ }
551
+ return size >= 4 ? 'xyzm' : size === 3 ? 'xyz' : 'xy';
552
+ }
553
+
554
+ function assertTargetCapacity(
555
+ target: GeoArrowBuilderTarget,
556
+ measurement: GeoArrowBuilderMeasurement,
557
+ size: number
558
+ ): void {
559
+ const coordinateLength =
560
+ target.coordinates instanceof Float32Array || target.coordinates instanceof Float64Array
561
+ ? target.coordinates.length / size
562
+ : target.coordinates.x.length;
563
+ if (
564
+ coordinateLength < measurement.coordinateCount ||
565
+ target.validity.length * 8 < measurement.length
566
+ ) {
567
+ throw new Error('GeoArrowBuilder target is smaller than its measured output');
568
+ }
569
+ }