@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.
- package/LICENSE +140 -0
- package/README.md +311 -0
- package/dist/builder.d.ts +90 -0
- package/dist/builder.d.ts.map +1 -0
- package/dist/builder.js +428 -0
- package/dist/builder.js.map +1 -0
- package/dist/codecs.d.ts +10 -0
- package/dist/codecs.d.ts.map +1 -0
- package/dist/codecs.js +122 -0
- package/dist/codecs.js.map +1 -0
- package/dist/index.cjs +1651 -0
- package/dist/index.cjs.map +6 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/kernels.d.ts +51 -0
- package/dist/kernels.d.ts.map +1 -0
- package/dist/kernels.js +359 -0
- package/dist/kernels.js.map +1 -0
- package/dist/layout.d.ts +52 -0
- package/dist/layout.d.ts.map +1 -0
- package/dist/layout.js +669 -0
- package/dist/layout.js.map +1 -0
- package/dist/tessellate.d.ts +31 -0
- package/dist/tessellate.d.ts.map +1 -0
- package/dist/tessellate.js +102 -0
- package/dist/tessellate.js.map +1 -0
- package/dist/types.d.ts +113 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +41 -0
- package/dist/types.js.map +1 -0
- package/dist/worker.cjs +73 -0
- package/dist/worker.cjs.map +6 -0
- package/dist/worker.d.ts +11 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +10 -0
- package/dist/worker.js.map +1 -0
- package/package.json +48 -0
- package/src/builder.ts +569 -0
- package/src/codecs.ts +155 -0
- package/src/index.ts +93 -0
- package/src/kernels.ts +463 -0
- package/src/layout.ts +833 -0
- package/src/tessellate.ts +133 -0
- package/src/types.ts +200 -0
- 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
|
+
}
|