@math.gl/geoarrow 5.0.0-alpha.2 → 5.0.0-alpha.3
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/README.md +45 -15
- package/dist/builder-entry.cjs +561 -0
- package/dist/builder-entry.cjs.map +6 -0
- package/dist/builder-entry.d.ts +4 -0
- package/dist/builder-entry.d.ts.map +1 -0
- package/dist/builder-entry.js +5 -0
- package/dist/builder-entry.js.map +1 -0
- package/dist/builder.d.ts +12 -1
- package/dist/builder.d.ts.map +1 -1
- package/dist/builder.js +112 -10
- package/dist/builder.js.map +1 -1
- package/dist/codecs.d.ts.map +1 -1
- package/dist/codecs.js +205 -7
- package/dist/codecs.js.map +1 -1
- package/dist/index.cjs +904 -120
- package/dist/index.cjs.map +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/kernels.d.ts +8 -3
- package/dist/kernels.d.ts.map +1 -1
- package/dist/kernels.js +610 -107
- package/dist/kernels.js.map +1 -1
- package/dist/layout.d.ts.map +1 -1
- package/dist/layout.js +28 -5
- package/dist/layout.js.map +1 -1
- package/dist/tessellation-entry.cjs +427 -0
- package/dist/tessellation-entry.cjs.map +6 -0
- package/dist/tessellation-entry.d.ts +4 -0
- package/dist/tessellation-entry.d.ts.map +1 -0
- package/dist/tessellation-entry.js +5 -0
- package/dist/tessellation-entry.js.map +1 -0
- package/dist/types.d.ts +37 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/wkb-entry.cjs +1005 -0
- package/dist/wkb-entry.cjs.map +6 -0
- package/dist/wkb-entry.d.ts +3 -0
- package/dist/wkb-entry.d.ts.map +1 -0
- package/dist/wkb-entry.js +6 -0
- package/dist/wkb-entry.js.map +1 -0
- package/dist/worker.cjs +4 -0
- package/dist/worker.cjs.map +1 -1
- package/package.json +21 -6
- package/src/builder-entry.ts +18 -0
- package/src/builder.ts +133 -19
- package/src/codecs.ts +251 -9
- package/src/index.ts +1 -0
- package/src/kernels.ts +811 -133
- package/src/layout.ts +38 -13
- package/src/tessellation-entry.ts +7 -0
- package/src/types.ts +23 -4
- package/src/wkb-entry.ts +11 -0
package/dist/kernels.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Copyright (c) vis.gl contributors
|
|
4
4
|
import { getGeoArrowDimensionSize } from "./types.js";
|
|
5
5
|
import { makeGeoArrowColumnFromGeometryRows } from "./builder.js";
|
|
6
|
-
import { getGeoArrowRowCount, getGeoArrowVertexCount, isGeoArrowValueValid,
|
|
6
|
+
import { getGeoArrowRowCount, getGeoArrowVertexCount, isGeoArrowValueValid, visitGeoArrowCoordinates, getListRange, materializeGeometryRow } from "./layout.js";
|
|
7
7
|
/** Returns XY bounds, or null when the column contains no finite coordinates. */
|
|
8
8
|
export function getGeoArrowBounds(column) {
|
|
9
9
|
if (column.encoding === 'geoarrow.box')
|
|
@@ -25,6 +25,49 @@ export function getGeoArrowBounds(column) {
|
|
|
25
25
|
});
|
|
26
26
|
return Number.isFinite(minimumX) ? [minimumX, minimumY, maximumX, maximumY] : null;
|
|
27
27
|
}
|
|
28
|
+
/** Computes exact XY bounds for each logical row without materializing geometry values. */
|
|
29
|
+
export function getGeoArrowRowBounds(column) {
|
|
30
|
+
const rowCount = getGeoArrowRowCount(column);
|
|
31
|
+
if (column.encoding === 'geoarrow.box') {
|
|
32
|
+
const result = [];
|
|
33
|
+
for (const chunk of column.chunks) {
|
|
34
|
+
for (let rowIndex = 0; rowIndex < chunk.length; rowIndex++) {
|
|
35
|
+
if (chunk.kind !== 'struct' || !isGeoArrowValueValid(chunk.validity, rowIndex)) {
|
|
36
|
+
result.push(null);
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
const index = (chunk.offset || 0) + rowIndex;
|
|
40
|
+
const xmin = readPrimitiveNumber(chunk.children['xmin'], index);
|
|
41
|
+
const ymin = readPrimitiveNumber(chunk.children['ymin'], index);
|
|
42
|
+
const xmax = readPrimitiveNumber(chunk.children['xmax'], index);
|
|
43
|
+
const ymax = readPrimitiveNumber(chunk.children['ymax'], index);
|
|
44
|
+
result.push([xmin, ymin, xmax, ymax].every(Number.isFinite) ? [xmin, ymin, xmax, ymax] : null);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
const minimumX = new Float64Array(rowCount).fill(Number.POSITIVE_INFINITY);
|
|
50
|
+
const minimumY = new Float64Array(rowCount).fill(Number.POSITIVE_INFINITY);
|
|
51
|
+
const maximumX = new Float64Array(rowCount).fill(Number.NEGATIVE_INFINITY);
|
|
52
|
+
const maximumY = new Float64Array(rowCount).fill(Number.NEGATIVE_INFINITY);
|
|
53
|
+
visitGeoArrowCoordinates(column, (coordinate, sourceRowIndex) => {
|
|
54
|
+
const [x, y] = coordinate;
|
|
55
|
+
if (Number.isFinite(x) && Number.isFinite(y)) {
|
|
56
|
+
minimumX[sourceRowIndex] = Math.min(minimumX[sourceRowIndex], x);
|
|
57
|
+
minimumY[sourceRowIndex] = Math.min(minimumY[sourceRowIndex], y);
|
|
58
|
+
maximumX[sourceRowIndex] = Math.max(maximumX[sourceRowIndex], x);
|
|
59
|
+
maximumY[sourceRowIndex] = Math.max(maximumY[sourceRowIndex], y);
|
|
60
|
+
}
|
|
61
|
+
return coordinate;
|
|
62
|
+
});
|
|
63
|
+
const bounds = [];
|
|
64
|
+
for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) {
|
|
65
|
+
bounds.push(Number.isFinite(minimumX[rowIndex])
|
|
66
|
+
? [minimumX[rowIndex], minimumY[rowIndex], maximumX[rowIndex], maximumY[rowIndex]]
|
|
67
|
+
: null);
|
|
68
|
+
}
|
|
69
|
+
return bounds;
|
|
70
|
+
}
|
|
28
71
|
function getGeoArrowBoxBounds(column) {
|
|
29
72
|
let minimumX = Number.POSITIVE_INFINITY;
|
|
30
73
|
let minimumY = Number.POSITIVE_INFINITY;
|
|
@@ -66,22 +109,20 @@ function readPrimitiveNumber(array, index) {
|
|
|
66
109
|
return Number(array.values[valueIndex]);
|
|
67
110
|
}
|
|
68
111
|
/** Maps every coordinate into newly allocated physical buffers while preserving column semantics. */
|
|
69
|
-
export function mapGeoArrowCoordinates(column, mapper, options = {}) {
|
|
112
|
+
export function mapGeoArrowCoordinates(column, mapper, options = {}, sourceDimension) {
|
|
70
113
|
assertGeoArrowResourceLimits(column, options.limits);
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
114
|
+
if (column.encoding === 'geoarrow.wkb' || column.encoding === 'geoarrow.wkt') {
|
|
115
|
+
throw new Error('Coordinate mapping requires native GeoArrow storage');
|
|
116
|
+
}
|
|
117
|
+
const dimension = options.dimension || column.dimension;
|
|
118
|
+
const layout = options.coordinateLayout || column.coordinateLayout || 'interleaved';
|
|
119
|
+
let rowOffset = 0;
|
|
120
|
+
const chunks = column.chunks.map(chunk => {
|
|
121
|
+
const mapped = mapArrayCoordinates(chunk, column.encoding, rowOffset, dimension, layout, mapper, options.coordinateType || 'preserve', options.dimension !== undefined, options.coordinateLayout !== undefined, sourceDimension);
|
|
122
|
+
rowOffset += chunk.length;
|
|
78
123
|
return mapped;
|
|
79
124
|
});
|
|
80
|
-
|
|
81
|
-
dimension: options.dimension || column.dimension,
|
|
82
|
-
coordinateLayout: options.coordinateLayout || column.coordinateLayout || 'interleaved'
|
|
83
|
-
});
|
|
84
|
-
return copyColumnMetadata(column, result);
|
|
125
|
+
return { ...column, dimension, coordinateLayout: layout, chunks };
|
|
85
126
|
}
|
|
86
127
|
/**
|
|
87
128
|
* Maps coordinates and copies the result into caller-provided destination buffers.
|
|
@@ -137,48 +178,65 @@ export function normalizeGeoArrowUnion(column) {
|
|
|
137
178
|
}
|
|
138
179
|
/** Converts between concrete/mixed encodings, dimensions, and coordinate layouts. */
|
|
139
180
|
export function convertGeoArrowColumn(column, options = {}) {
|
|
140
|
-
const encoding = options.encoding || column.encoding;
|
|
181
|
+
const encoding = options.encoding === 'native' || !options.encoding ? column.encoding : options.encoding;
|
|
141
182
|
const dimension = options.dimension || column.dimension;
|
|
142
|
-
const coordinateLayout = options.coordinateLayout ||
|
|
183
|
+
const coordinateLayout = options.coordinateLayout === 'preserve' || !options.coordinateLayout
|
|
184
|
+
? column.coordinateLayout
|
|
185
|
+
: options.coordinateLayout;
|
|
186
|
+
const requestedOffsetType = options.offsetType || 'preserve';
|
|
187
|
+
const currentOffsetType = getColumnOffsetType(column);
|
|
143
188
|
if (encoding === column.encoding &&
|
|
144
189
|
dimension === column.dimension &&
|
|
145
|
-
coordinateLayout === column.coordinateLayout
|
|
190
|
+
coordinateLayout === column.coordinateLayout &&
|
|
191
|
+
(requestedOffsetType === 'preserve' || requestedOffsetType === currentOffsetType) &&
|
|
192
|
+
(!options.coordinateType || options.coordinateType === 'preserve')) {
|
|
146
193
|
return column;
|
|
147
194
|
}
|
|
148
195
|
if (encoding === 'geoarrow.wkb' || encoding === 'geoarrow.wkt') {
|
|
149
196
|
throw new Error('Use encodeGeoArrowWKB or encodeGeoArrowWKT for serialized output');
|
|
150
197
|
}
|
|
151
198
|
assertGeoArrowResourceLimits(column, options.limits);
|
|
152
|
-
const
|
|
153
|
-
const result = makeGeoArrowColumnFromGeometryRows(rows, {
|
|
154
|
-
encoding: encoding === 'geoarrow.geometry' ? encoding : undefined,
|
|
199
|
+
const mapped = mapGeoArrowCoordinates(column, coordinate => coordinate, {
|
|
155
200
|
dimension,
|
|
156
|
-
coordinateLayout: coordinateLayout ||
|
|
201
|
+
coordinateLayout: coordinateLayout || undefined,
|
|
202
|
+
coordinateType: options.coordinateType,
|
|
203
|
+
limits: options.limits
|
|
204
|
+
}, column.dimension);
|
|
205
|
+
const offsetConverted = requestedOffsetType === 'preserve'
|
|
206
|
+
? mapped
|
|
207
|
+
: {
|
|
208
|
+
...mapped,
|
|
209
|
+
chunks: mapped.chunks.map(chunk => convertArrayOffsets(chunk, requestedOffsetType))
|
|
210
|
+
};
|
|
211
|
+
if (encoding === column.encoding)
|
|
212
|
+
return offsetConverted;
|
|
213
|
+
if (encoding === 'geoarrow.geometry')
|
|
214
|
+
return promoteToDenseUnion(offsetConverted);
|
|
215
|
+
if (column.encoding === 'geoarrow.geometry')
|
|
216
|
+
return demoteDenseUnion(offsetConverted, encoding);
|
|
217
|
+
throw new Error(`Rows cannot be represented as ${encoding}`);
|
|
218
|
+
}
|
|
219
|
+
function resizeCoordinate(coordinate, sourceDimension, targetDimension) {
|
|
220
|
+
const sourceNames = getDimensionNames(sourceDimension);
|
|
221
|
+
return getDimensionNames(targetDimension).map(name => {
|
|
222
|
+
const sourceIndex = sourceNames.indexOf(name);
|
|
223
|
+
return sourceIndex >= 0 ? (coordinate[sourceIndex] ?? 0) : 0;
|
|
157
224
|
});
|
|
158
|
-
if (result.encoding !== encoding && encoding !== 'geoarrow.geometry') {
|
|
159
|
-
throw new Error(`Rows cannot be represented as ${encoding}`);
|
|
160
|
-
}
|
|
161
|
-
return copyColumnMetadata(column, result);
|
|
162
225
|
}
|
|
163
226
|
/** Rewinds polygon rings without changing non-polygon rows. */
|
|
164
227
|
export function rewindGeoArrow(column, options = {}) {
|
|
165
228
|
assertGeoArrowResourceLimits(column, options.limits);
|
|
229
|
+
if (column.encoding === 'geoarrow.wkb' || column.encoding === 'geoarrow.wkt') {
|
|
230
|
+
throw new Error('Rewinding requires native GeoArrow storage');
|
|
231
|
+
}
|
|
166
232
|
const outerClockwise = (options.outer || 'counter-clockwise') === 'clockwise';
|
|
167
233
|
let changed = false;
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
changed || (changed = rewound !== row);
|
|
173
|
-
return rewound;
|
|
174
|
-
});
|
|
175
|
-
if (!changed)
|
|
176
|
-
return column;
|
|
177
|
-
const result = makeGeoArrowColumnFromGeometryRows(rows, {
|
|
178
|
-
dimension: column.dimension,
|
|
179
|
-
coordinateLayout: column.coordinateLayout || 'interleaved'
|
|
234
|
+
const chunks = column.chunks.map(chunk => {
|
|
235
|
+
const clone = mapArrayCoordinates(chunk, column.encoding, 0, column.dimension, column.coordinateLayout || 'interleaved', coordinate => coordinate, 'preserve');
|
|
236
|
+
changed || (changed = rewindArrayRings(clone, column.encoding, outerClockwise));
|
|
237
|
+
return clone;
|
|
180
238
|
});
|
|
181
|
-
return
|
|
239
|
+
return changed ? { ...column, chunks } : column;
|
|
182
240
|
}
|
|
183
241
|
/** Throws when a column exceeds configured work or output limits. */
|
|
184
242
|
export function assertGeoArrowResourceLimits(column, options = {}) {
|
|
@@ -206,90 +264,218 @@ export function assertGeoArrowResourceLimits(column, options = {}) {
|
|
|
206
264
|
}
|
|
207
265
|
}
|
|
208
266
|
}
|
|
209
|
-
|
|
210
|
-
|
|
267
|
+
/** Directly clones a physical array while rewriting only coordinate leaves. */
|
|
268
|
+
function mapArrayCoordinates(array, encoding, rowOffset, targetDimension, targetLayout, mapper, coordinateType, forceDimension = false, forceLayout = false, sourceDimension, rowIndicesOverride) {
|
|
269
|
+
if (encoding === 'geoarrow.geometry' && array.kind === 'dense-union') {
|
|
270
|
+
const childRowIndices = collectUnionRowIndices(array, rowOffset);
|
|
271
|
+
return {
|
|
272
|
+
...array,
|
|
273
|
+
children: array.children.map((child, childIndex) => {
|
|
274
|
+
const childEncoding = child.encoding || getEncodingFromChildName(child.name);
|
|
275
|
+
const childDimension = forceDimension
|
|
276
|
+
? targetDimension
|
|
277
|
+
: child.dimension || targetDimension;
|
|
278
|
+
const childLayout = forceLayout ? targetLayout : child.coordinateLayout || targetLayout;
|
|
279
|
+
return {
|
|
280
|
+
...child,
|
|
281
|
+
data: mapArrayCoordinates(child.data, childEncoding, rowOffset, childDimension, childLayout, mapper, coordinateType, forceDimension, forceLayout, childDimension, childRowIndices[childIndex]),
|
|
282
|
+
dimension: childDimension,
|
|
283
|
+
coordinateLayout: childLayout,
|
|
284
|
+
encoding: childEncoding
|
|
285
|
+
};
|
|
286
|
+
})
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
if (encoding === 'geoarrow.geometrycollection' && array.kind === 'list') {
|
|
211
290
|
return {
|
|
212
|
-
...
|
|
213
|
-
|
|
291
|
+
...array,
|
|
292
|
+
child: mapArrayCoordinates(array.child, 'geoarrow.geometry', rowOffset, targetDimension, targetLayout, mapper, coordinateType, forceDimension, forceLayout, sourceDimension, rowIndicesOverride)
|
|
214
293
|
};
|
|
215
294
|
}
|
|
295
|
+
const depth = getEncodingDepth(encoding);
|
|
296
|
+
const rowIndices = rowIndicesOverride || collectLeafRowIndices(array, depth, rowOffset);
|
|
297
|
+
return mapNestedArray(array, depth, rowOffset, targetDimension, targetLayout, mapper, coordinateType, rowIndices, sourceDimension);
|
|
298
|
+
}
|
|
299
|
+
function mapNestedArray(array, depth, rowOffset, targetDimension, targetLayout, mapper, coordinateType, rowIndices, sourceDimension) {
|
|
300
|
+
if (depth === 0) {
|
|
301
|
+
return mapCoordinateLeaf(array, rowOffset, targetDimension, targetLayout, mapper, coordinateType, rowIndices, sourceDimension);
|
|
302
|
+
}
|
|
303
|
+
if (array.kind !== 'list')
|
|
304
|
+
return array;
|
|
216
305
|
return {
|
|
217
|
-
...
|
|
218
|
-
|
|
306
|
+
...array,
|
|
307
|
+
child: mapNestedArray(array.child, depth - 1, rowOffset, targetDimension, targetLayout, mapper, coordinateType, rowIndices, sourceDimension)
|
|
219
308
|
};
|
|
220
309
|
}
|
|
221
|
-
function
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
310
|
+
function mapCoordinateLeaf(array, rowOffset, targetDimension, targetLayout, mapper, coordinateType, rowIndices, sourceDimension) {
|
|
311
|
+
const count = array.length;
|
|
312
|
+
const targetSize = getGeoArrowDimensionSize(targetDimension);
|
|
313
|
+
const sourceValues = new Array(count);
|
|
314
|
+
for (let index = 0; index < count; index++) {
|
|
315
|
+
const coordinate = readCoordinateAt(array, index);
|
|
316
|
+
const mapped = coordinate
|
|
317
|
+
? mapper(sourceDimension
|
|
318
|
+
? resizeCoordinate(coordinate, sourceDimension, targetDimension)
|
|
319
|
+
: coordinate, rowIndices?.[index] ?? rowOffset)
|
|
320
|
+
: new Array(targetSize).fill(0);
|
|
321
|
+
if (mapped.length !== targetSize) {
|
|
322
|
+
throw new Error(`Coordinate mapper returned ${mapped.length} values; expected ${targetSize}`);
|
|
323
|
+
}
|
|
324
|
+
sourceValues[index] = [...mapped];
|
|
325
|
+
}
|
|
326
|
+
const FloatArray = resolveCoordinateConstructor(array, coordinateType);
|
|
327
|
+
return makeCoordinateLeaf(sourceValues, targetDimension, targetLayout, FloatArray, array.validity);
|
|
227
328
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
329
|
+
/** Fills one row-index entry per coordinate in a concrete nested array. */
|
|
330
|
+
function collectLeafRowIndices(array, depth, rowOffset) {
|
|
331
|
+
const output = new Array(getLeafLength(array, depth)).fill(rowOffset);
|
|
332
|
+
for (let rowIndex = 0; rowIndex < array.length; rowIndex++) {
|
|
333
|
+
if (isGeoArrowValueValid(array.validity, rowIndex)) {
|
|
334
|
+
assignNestedRowIndices(array, rowIndex, depth, rowOffset + rowIndex, output);
|
|
335
|
+
}
|
|
234
336
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
337
|
+
return output;
|
|
338
|
+
}
|
|
339
|
+
function assignNestedRowIndices(array, index, depth, rowIndex, output) {
|
|
340
|
+
if (depth === 0) {
|
|
341
|
+
if (index >= 0 && index < output.length)
|
|
342
|
+
output[index] = rowIndex;
|
|
343
|
+
return;
|
|
238
344
|
}
|
|
239
|
-
if (
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
345
|
+
if (array.kind !== 'list')
|
|
346
|
+
return;
|
|
347
|
+
const [first, last] = getListRange(array, index);
|
|
348
|
+
for (let childIndex = first; childIndex < last; childIndex++) {
|
|
349
|
+
assignNestedRowIndices(array.child, childIndex, depth - 1, rowIndex, output);
|
|
244
350
|
}
|
|
245
|
-
return geometry;
|
|
246
351
|
}
|
|
247
|
-
function
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
return ring;
|
|
254
|
-
changed = true;
|
|
255
|
-
return [...ring].reverse();
|
|
352
|
+
function collectUnionRowIndices(union, rowOffset) {
|
|
353
|
+
if (union.kind !== 'dense-union')
|
|
354
|
+
return [];
|
|
355
|
+
const output = union.children.map(child => {
|
|
356
|
+
const encoding = child.encoding || getEncodingFromChildName(child.name);
|
|
357
|
+
return new Array(getLeafLength(child.data, getEncodingDepth(encoding))).fill(rowOffset);
|
|
256
358
|
});
|
|
257
|
-
|
|
359
|
+
for (let rowIndex = 0; rowIndex < union.length; rowIndex++) {
|
|
360
|
+
if (!isGeoArrowValueValid(union.validity, rowIndex))
|
|
361
|
+
continue;
|
|
362
|
+
const physical = (union.offset || 0) + rowIndex;
|
|
363
|
+
const childIndex = union.children.findIndex(child => child.typeId === union.typeIds[physical]);
|
|
364
|
+
if (childIndex < 0)
|
|
365
|
+
continue;
|
|
366
|
+
const child = union.children[childIndex];
|
|
367
|
+
assignUnionRowIndices(child.data, child.encoding || getEncodingFromChildName(child.name), union.valueOffsets[physical], rowOffset + rowIndex, output[childIndex]);
|
|
368
|
+
}
|
|
369
|
+
return output;
|
|
258
370
|
}
|
|
259
|
-
function
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
|
|
371
|
+
function assignUnionRowIndices(array, encoding, index, rowIndex, output) {
|
|
372
|
+
if (encoding === 'geoarrow.geometry' && array.kind === 'dense-union') {
|
|
373
|
+
const physical = (array.offset || 0) + index;
|
|
374
|
+
const childIndex = array.children.findIndex(child => child.typeId === array.typeIds[physical]);
|
|
375
|
+
if (childIndex >= 0) {
|
|
376
|
+
const child = array.children[childIndex];
|
|
377
|
+
assignUnionRowIndices(child.data, child.encoding || getEncodingFromChildName(child.name), array.valueOffsets[physical], rowIndex, output);
|
|
378
|
+
}
|
|
379
|
+
return;
|
|
265
380
|
}
|
|
266
|
-
|
|
381
|
+
if (encoding === 'geoarrow.geometrycollection' && array.kind === 'list') {
|
|
382
|
+
const [first, last] = getListRange(array, index);
|
|
383
|
+
if (array.child.kind === 'dense-union') {
|
|
384
|
+
for (let childIndex = first; childIndex < last; childIndex++) {
|
|
385
|
+
assignUnionRowIndices(array.child, 'geoarrow.geometry', childIndex, rowIndex, output);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
assignNestedRowIndices(array, index, getEncodingDepth(encoding), rowIndex, output);
|
|
267
391
|
}
|
|
268
|
-
function
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
if (
|
|
272
|
-
return
|
|
392
|
+
function getLeafLength(array, depth) {
|
|
393
|
+
let current = array;
|
|
394
|
+
for (let level = 0; level < depth; level++) {
|
|
395
|
+
if (current.kind !== 'list')
|
|
396
|
+
return 0;
|
|
397
|
+
current = current.child;
|
|
398
|
+
}
|
|
399
|
+
return current.length;
|
|
400
|
+
}
|
|
401
|
+
function resolveCoordinateConstructor(array, coordinateType) {
|
|
402
|
+
if (coordinateType === 'float32')
|
|
403
|
+
return Float32Array;
|
|
404
|
+
if (coordinateType === 'float64')
|
|
405
|
+
return Float64Array;
|
|
406
|
+
if (array.kind === 'fixed-size-list' && array.child.kind === 'primitive') {
|
|
407
|
+
return array.child.values instanceof Float32Array ? Float32Array : Float64Array;
|
|
408
|
+
}
|
|
409
|
+
if (array.kind === 'struct') {
|
|
410
|
+
const child = array.children['x'];
|
|
411
|
+
if (child?.kind === 'primitive' && child.values instanceof Float32Array)
|
|
412
|
+
return Float32Array;
|
|
413
|
+
}
|
|
414
|
+
return Float64Array;
|
|
415
|
+
}
|
|
416
|
+
function makeCoordinateLeaf(coordinates, dimension, layout, FloatArray, validity) {
|
|
417
|
+
const size = getGeoArrowDimensionSize(dimension);
|
|
418
|
+
if (layout === 'interleaved') {
|
|
419
|
+
const values = new FloatArray(coordinates.length * size);
|
|
420
|
+
for (let index = 0; index < coordinates.length; index++) {
|
|
421
|
+
values.set(coordinates[index], index * size);
|
|
273
422
|
}
|
|
274
|
-
|
|
423
|
+
return {
|
|
424
|
+
kind: 'fixed-size-list',
|
|
425
|
+
length: coordinates.length,
|
|
426
|
+
size,
|
|
427
|
+
child: { kind: 'primitive', length: values.length, values },
|
|
428
|
+
validity
|
|
429
|
+
};
|
|
275
430
|
}
|
|
276
|
-
const
|
|
277
|
-
|
|
278
|
-
|
|
431
|
+
const children = {
|
|
432
|
+
x: { kind: 'primitive', length: coordinates.length, values: new FloatArray(coordinates.length) },
|
|
433
|
+
y: { kind: 'primitive', length: coordinates.length, values: new FloatArray(coordinates.length) }
|
|
434
|
+
};
|
|
435
|
+
const names = getDimensionNames(dimension);
|
|
436
|
+
for (let component = 2; component < names.length; component++) {
|
|
437
|
+
children[names[component]] = {
|
|
438
|
+
kind: 'primitive',
|
|
439
|
+
length: coordinates.length,
|
|
440
|
+
values: new FloatArray(coordinates.length)
|
|
441
|
+
};
|
|
279
442
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
443
|
+
for (let index = 0; index < coordinates.length; index++) {
|
|
444
|
+
const coordinate = coordinates[index];
|
|
445
|
+
for (let component = 0; component < names.length; component++) {
|
|
446
|
+
const child = children[names[component]];
|
|
447
|
+
if (child.kind === 'primitive')
|
|
448
|
+
child.values[index] = coordinate[component];
|
|
449
|
+
}
|
|
283
450
|
}
|
|
284
|
-
return {
|
|
451
|
+
return { kind: 'struct', length: coordinates.length, children, validity };
|
|
285
452
|
}
|
|
286
|
-
function
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
const
|
|
291
|
-
|
|
292
|
-
|
|
453
|
+
function readCoordinateAt(array, index) {
|
|
454
|
+
if (!isGeoArrowValueValid(array.validity, index))
|
|
455
|
+
return null;
|
|
456
|
+
if (array.kind === 'fixed-size-list') {
|
|
457
|
+
const logicalIndex = (array.offset || 0) + index;
|
|
458
|
+
if (array.child.kind !== 'primitive')
|
|
459
|
+
return null;
|
|
460
|
+
const values = [];
|
|
461
|
+
for (let component = 0; component < array.size; component++) {
|
|
462
|
+
const scalarIndex = logicalIndex * array.size + component;
|
|
463
|
+
values.push(Number(array.child.values[(array.child.offset || 0) + scalarIndex * (array.child.stride || 1)]));
|
|
464
|
+
}
|
|
465
|
+
return values;
|
|
466
|
+
}
|
|
467
|
+
if (array.kind === 'struct') {
|
|
468
|
+
const logicalIndex = (array.offset || 0) + index;
|
|
469
|
+
const values = [];
|
|
470
|
+
for (const name of ['x', 'y', 'z', 'm']) {
|
|
471
|
+
const child = array.children[name];
|
|
472
|
+
if (child?.kind === 'primitive') {
|
|
473
|
+
values.push(Number(child.values[(child.offset || 0) + logicalIndex * (child.stride || 1)]));
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
return values.length >= 2 ? values : null;
|
|
477
|
+
}
|
|
478
|
+
return null;
|
|
293
479
|
}
|
|
294
480
|
function getDimensionNames(dimension) {
|
|
295
481
|
switch (dimension) {
|
|
@@ -303,13 +489,330 @@ function getDimensionNames(dimension) {
|
|
|
303
489
|
return ['x', 'y', 'z', 'm'];
|
|
304
490
|
}
|
|
305
491
|
}
|
|
306
|
-
function
|
|
492
|
+
function getEncodingFromChildName(name) {
|
|
493
|
+
const normalized = name.replace(/[^a-z]/gi, '').toLowerCase();
|
|
494
|
+
const encoding = `geoarrow.${normalized}`;
|
|
495
|
+
if (encoding === 'geoarrow.point' ||
|
|
496
|
+
encoding === 'geoarrow.linestring' ||
|
|
497
|
+
encoding === 'geoarrow.polygon' ||
|
|
498
|
+
encoding === 'geoarrow.multipoint' ||
|
|
499
|
+
encoding === 'geoarrow.multilinestring' ||
|
|
500
|
+
encoding === 'geoarrow.multipolygon' ||
|
|
501
|
+
encoding === 'geoarrow.geometrycollection') {
|
|
502
|
+
return encoding;
|
|
503
|
+
}
|
|
504
|
+
throw new Error(`Unknown GeoArrow dense-union child ${name}`);
|
|
505
|
+
}
|
|
506
|
+
function getEncodingDepth(encoding) {
|
|
507
|
+
switch (encoding) {
|
|
508
|
+
case 'geoarrow.point':
|
|
509
|
+
return 0;
|
|
510
|
+
case 'geoarrow.linestring':
|
|
511
|
+
case 'geoarrow.multipoint':
|
|
512
|
+
return 1;
|
|
513
|
+
case 'geoarrow.polygon':
|
|
514
|
+
case 'geoarrow.multilinestring':
|
|
515
|
+
return 2;
|
|
516
|
+
case 'geoarrow.multipolygon':
|
|
517
|
+
return 3;
|
|
518
|
+
default:
|
|
519
|
+
return 0;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
function promoteToDenseUnion(column) {
|
|
523
|
+
if (column.encoding === 'geoarrow.geometry')
|
|
524
|
+
return column;
|
|
525
|
+
const chunks = column.chunks.map(chunk => {
|
|
526
|
+
const typeId = getCanonicalTypeId(column.encoding, column.dimension);
|
|
527
|
+
const typeIds = new Int8Array(chunk.length);
|
|
528
|
+
typeIds.fill(typeId);
|
|
529
|
+
const valueOffsets = new Int32Array(chunk.length);
|
|
530
|
+
for (let index = 0; index < chunk.length; index++)
|
|
531
|
+
valueOffsets[index] = index;
|
|
532
|
+
return {
|
|
533
|
+
kind: 'dense-union',
|
|
534
|
+
length: chunk.length,
|
|
535
|
+
typeIds,
|
|
536
|
+
valueOffsets,
|
|
537
|
+
validity: chunk.validity,
|
|
538
|
+
children: [
|
|
539
|
+
{
|
|
540
|
+
name: getGeoArrowGeometryTypeName(column.encoding),
|
|
541
|
+
typeId,
|
|
542
|
+
encoding: column.encoding,
|
|
543
|
+
dimension: column.dimension,
|
|
544
|
+
coordinateLayout: column.coordinateLayout,
|
|
545
|
+
data: chunk
|
|
546
|
+
}
|
|
547
|
+
]
|
|
548
|
+
};
|
|
549
|
+
});
|
|
550
|
+
return { ...column, encoding: 'geoarrow.geometry', chunks };
|
|
551
|
+
}
|
|
552
|
+
function demoteDenseUnion(column, encoding) {
|
|
553
|
+
if (encoding === 'geoarrow.geometry' || encoding === 'geoarrow.geometrycollection')
|
|
554
|
+
return column;
|
|
555
|
+
const rows = [];
|
|
556
|
+
for (const chunk of column.chunks) {
|
|
557
|
+
if (chunk.kind !== 'dense-union')
|
|
558
|
+
throw new Error('Expected dense-union storage');
|
|
559
|
+
for (let index = 0; index < chunk.length; index++) {
|
|
560
|
+
if (!isGeoArrowValueValid(chunk.validity, index)) {
|
|
561
|
+
rows.push(null);
|
|
562
|
+
continue;
|
|
563
|
+
}
|
|
564
|
+
const physical = (chunk.offset || 0) + index;
|
|
565
|
+
const child = chunk.children.find(candidate => candidate.typeId === chunk.typeIds[physical]);
|
|
566
|
+
const childEncoding = child && (child.encoding || getEncodingFromChildName(child.name));
|
|
567
|
+
if (!child || childEncoding !== encoding) {
|
|
568
|
+
throw new Error(`Rows cannot be represented as ${encoding}`);
|
|
569
|
+
}
|
|
570
|
+
const geometry = materializeGeometryRow(child.data, chunk.valueOffsets[physical], childEncoding);
|
|
571
|
+
rows.push(geometry
|
|
572
|
+
? resizeGeometryValue(geometry, child.dimension || column.dimension, column.dimension)
|
|
573
|
+
: null);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
const built = makeGeoArrowColumnFromGeometryRows(rows, {
|
|
577
|
+
dimension: column.dimension,
|
|
578
|
+
coordinateLayout: column.coordinateLayout || 'interleaved',
|
|
579
|
+
offsetType: getColumnOffsetType(column)
|
|
580
|
+
});
|
|
581
|
+
return {
|
|
582
|
+
...built,
|
|
583
|
+
encoding: encoding,
|
|
584
|
+
spatialReference: column.spatialReference,
|
|
585
|
+
edges: column.edges,
|
|
586
|
+
metadata: column.metadata
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
function resizeGeometryValue(geometry, sourceDimension, targetDimension) {
|
|
590
|
+
const map = (value) => {
|
|
591
|
+
if (Array.isArray(value) && (value.length === 0 || typeof value[0] === 'number')) {
|
|
592
|
+
return resizeCoordinate(value, sourceDimension, targetDimension);
|
|
593
|
+
}
|
|
594
|
+
return Array.isArray(value) ? value.map(map) : value;
|
|
595
|
+
};
|
|
596
|
+
if (geometry.type === 'GeometryCollection') {
|
|
597
|
+
return {
|
|
598
|
+
type: geometry.type,
|
|
599
|
+
geometries: geometry.geometries.map(child => resizeGeometryValue(child, sourceDimension, targetDimension))
|
|
600
|
+
};
|
|
601
|
+
}
|
|
307
602
|
return {
|
|
308
|
-
...
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
603
|
+
...geometry,
|
|
604
|
+
coordinates: map(geometry.coordinates)
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
function getCanonicalTypeId(encoding, dimension) {
|
|
608
|
+
const families = [
|
|
609
|
+
'point',
|
|
610
|
+
'linestring',
|
|
611
|
+
'polygon',
|
|
612
|
+
'multipoint',
|
|
613
|
+
'multilinestring',
|
|
614
|
+
'multipolygon',
|
|
615
|
+
'geometrycollection'
|
|
616
|
+
];
|
|
617
|
+
const dimensions = ['xy', 'xyz', 'xym', 'xyzm'];
|
|
618
|
+
const family = encoding.replace('geoarrow.', '');
|
|
619
|
+
const familyIndex = families.indexOf(family);
|
|
620
|
+
const dimensionIndex = dimensions.indexOf(dimension);
|
|
621
|
+
return familyIndex < 0 || dimensionIndex < 0 ? 1 : familyIndex * 4 + dimensionIndex + 1;
|
|
622
|
+
}
|
|
623
|
+
function getColumnOffsetType(column) {
|
|
624
|
+
for (const chunk of column.chunks) {
|
|
625
|
+
const found = findOffsetType(chunk);
|
|
626
|
+
if (found)
|
|
627
|
+
return found;
|
|
628
|
+
}
|
|
629
|
+
return 'int32';
|
|
630
|
+
}
|
|
631
|
+
function findOffsetType(array) {
|
|
632
|
+
switch (array.kind) {
|
|
633
|
+
case 'list':
|
|
634
|
+
case 'serialized':
|
|
635
|
+
return array.offsets instanceof BigInt64Array ? 'int64' : 'int32';
|
|
636
|
+
case 'fixed-size-list':
|
|
637
|
+
return findOffsetType(array.child);
|
|
638
|
+
case 'struct': {
|
|
639
|
+
for (const child of Object.values(array.children)) {
|
|
640
|
+
const found = findOffsetType(child);
|
|
641
|
+
if (found)
|
|
642
|
+
return found;
|
|
643
|
+
}
|
|
644
|
+
return null;
|
|
645
|
+
}
|
|
646
|
+
case 'dense-union':
|
|
647
|
+
for (const child of array.children) {
|
|
648
|
+
const found = findOffsetType(child.data);
|
|
649
|
+
if (found)
|
|
650
|
+
return found;
|
|
651
|
+
}
|
|
652
|
+
return null;
|
|
653
|
+
default:
|
|
654
|
+
return null;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
function convertArrayOffsets(array, offsetType) {
|
|
658
|
+
const toOffsets = (offsets, offsetBase) => {
|
|
659
|
+
if (offsetType === 'int64') {
|
|
660
|
+
const result = new BigInt64Array(offsets.length);
|
|
661
|
+
for (let index = 0; index < offsets.length; index++)
|
|
662
|
+
result[index] = BigInt(offsets[index]);
|
|
663
|
+
return { offsets: result, ...(offsetBase === undefined ? {} : { offsetBase }) };
|
|
664
|
+
}
|
|
665
|
+
const result = new Int32Array(offsets.length);
|
|
666
|
+
const base = BigInt(offsetBase ?? 0);
|
|
667
|
+
for (let index = 0; index < offsets.length; index++) {
|
|
668
|
+
const value = BigInt(offsets[index]) - base;
|
|
669
|
+
if (value < -2147483648n || value > 2147483647n) {
|
|
670
|
+
throw new Error('GeoArrow offset cannot be represented as Int32');
|
|
671
|
+
}
|
|
672
|
+
result[index] = Number(value);
|
|
673
|
+
}
|
|
674
|
+
return { offsets: result, offsetBase: 0 };
|
|
312
675
|
};
|
|
676
|
+
switch (array.kind) {
|
|
677
|
+
case 'list':
|
|
678
|
+
if (array.offsets instanceof (offsetType === 'int64' ? BigInt64Array : Int32Array)) {
|
|
679
|
+
return { ...array, child: convertArrayOffsets(array.child, offsetType) };
|
|
680
|
+
}
|
|
681
|
+
return {
|
|
682
|
+
...array,
|
|
683
|
+
...toOffsets(array.offsets, array.offsetBase),
|
|
684
|
+
child: convertArrayOffsets(array.child, offsetType)
|
|
685
|
+
};
|
|
686
|
+
case 'serialized':
|
|
687
|
+
if (array.offsets instanceof (offsetType === 'int64' ? BigInt64Array : Int32Array))
|
|
688
|
+
return array;
|
|
689
|
+
return { ...array, ...toOffsets(array.offsets, array.offsetBase) };
|
|
690
|
+
case 'fixed-size-list':
|
|
691
|
+
return { ...array, child: convertArrayOffsets(array.child, offsetType) };
|
|
692
|
+
case 'struct':
|
|
693
|
+
return {
|
|
694
|
+
...array,
|
|
695
|
+
children: Object.fromEntries(Object.entries(array.children).map(([name, child]) => [
|
|
696
|
+
name,
|
|
697
|
+
convertArrayOffsets(child, offsetType)
|
|
698
|
+
]))
|
|
699
|
+
};
|
|
700
|
+
case 'dense-union':
|
|
701
|
+
return {
|
|
702
|
+
...array,
|
|
703
|
+
children: array.children.map(child => ({
|
|
704
|
+
...child,
|
|
705
|
+
data: convertArrayOffsets(child.data, offsetType)
|
|
706
|
+
}))
|
|
707
|
+
};
|
|
708
|
+
default:
|
|
709
|
+
return array;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
function getGeoArrowGeometryTypeName(encoding) {
|
|
713
|
+
return encoding.replace('geoarrow.', '').replace(/^./, character => character.toUpperCase());
|
|
714
|
+
}
|
|
715
|
+
function rewindArrayRings(array, encoding, outerClockwise) {
|
|
716
|
+
if (encoding === 'geoarrow.geometry' && array.kind === 'dense-union') {
|
|
717
|
+
let changed = false;
|
|
718
|
+
for (const child of array.children) {
|
|
719
|
+
if (rewindArrayRings(child.data, child.encoding || getEncodingFromChildName(child.name), outerClockwise))
|
|
720
|
+
changed = true;
|
|
721
|
+
}
|
|
722
|
+
return changed;
|
|
723
|
+
}
|
|
724
|
+
if (encoding === 'geoarrow.geometrycollection' && array.kind === 'list') {
|
|
725
|
+
if (array.child.kind !== 'dense-union')
|
|
726
|
+
return false;
|
|
727
|
+
let changed = false;
|
|
728
|
+
for (const child of array.child.children) {
|
|
729
|
+
if (rewindArrayRings(child.data, child.encoding || getEncodingFromChildName(child.name), outerClockwise))
|
|
730
|
+
changed = true;
|
|
731
|
+
}
|
|
732
|
+
return changed;
|
|
733
|
+
}
|
|
734
|
+
if (encoding !== 'geoarrow.polygon' && encoding !== 'geoarrow.multipolygon')
|
|
735
|
+
return false;
|
|
736
|
+
if (array.kind !== 'list' || array.child.kind !== 'list')
|
|
737
|
+
return false;
|
|
738
|
+
let changed = false;
|
|
739
|
+
for (let rowIndex = 0; rowIndex < array.length; rowIndex++) {
|
|
740
|
+
if (!isGeoArrowValueValid(array.validity, rowIndex))
|
|
741
|
+
continue;
|
|
742
|
+
const [, rowEnd] = getListRange(array, rowIndex);
|
|
743
|
+
const rowStart = getListRange(array, rowIndex)[0];
|
|
744
|
+
if (encoding === 'geoarrow.polygon') {
|
|
745
|
+
const rings = array.child;
|
|
746
|
+
if (rewindRingGroup(rings, rowStart, rowEnd, outerClockwise))
|
|
747
|
+
changed = true;
|
|
748
|
+
}
|
|
749
|
+
else {
|
|
750
|
+
const polygons = array.child;
|
|
751
|
+
for (let polygonIndex = rowStart; polygonIndex < rowEnd; polygonIndex++) {
|
|
752
|
+
const [ringStart, ringEnd] = getListRange(polygons, polygonIndex);
|
|
753
|
+
if (rewindRingGroup(polygons.child, ringStart, ringEnd, outerClockwise))
|
|
754
|
+
changed = true;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
return changed;
|
|
759
|
+
}
|
|
760
|
+
function rewindRingGroup(rings, firstRing, lastRing, outerClockwise) {
|
|
761
|
+
if (rings.kind !== 'list')
|
|
762
|
+
return false;
|
|
763
|
+
const leaf = rings.child;
|
|
764
|
+
if (leaf.kind !== 'fixed-size-list' && leaf.kind !== 'struct')
|
|
765
|
+
return false;
|
|
766
|
+
let changed = false;
|
|
767
|
+
for (let ringIndex = firstRing; ringIndex < lastRing; ringIndex++) {
|
|
768
|
+
const [first, last] = getListRange(rings, ringIndex);
|
|
769
|
+
const area = getRingArea(leaf, first, last);
|
|
770
|
+
if (!Number.isFinite(area) || area === 0)
|
|
771
|
+
continue;
|
|
772
|
+
const shouldClockwise = ringIndex === firstRing ? outerClockwise : !outerClockwise;
|
|
773
|
+
if (area < 0 !== shouldClockwise) {
|
|
774
|
+
reverseCoordinateRange(leaf, first, last);
|
|
775
|
+
changed = true;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
return changed;
|
|
779
|
+
}
|
|
780
|
+
function getRingArea(leaf, first, last) {
|
|
781
|
+
let area = 0;
|
|
782
|
+
for (let index = first; index < last; index++) {
|
|
783
|
+
const current = readCoordinateAt(leaf, index);
|
|
784
|
+
const next = readCoordinateAt(leaf, index + 1 < last ? index + 1 : first);
|
|
785
|
+
if (!current || !next)
|
|
786
|
+
return Number.NaN;
|
|
787
|
+
area += current[0] * next[1] - next[0] * current[1];
|
|
788
|
+
}
|
|
789
|
+
return area / 2;
|
|
790
|
+
}
|
|
791
|
+
function reverseCoordinateRange(leaf, first, last) {
|
|
792
|
+
const values = [];
|
|
793
|
+
for (let index = first; index < last; index++)
|
|
794
|
+
values.push(readCoordinateAt(leaf, index));
|
|
795
|
+
values.reverse();
|
|
796
|
+
for (let index = first; index < last; index++)
|
|
797
|
+
writeCoordinateAt(leaf, index, values[index - first]);
|
|
798
|
+
}
|
|
799
|
+
function writeCoordinateAt(array, index, coordinate) {
|
|
800
|
+
if (array.kind === 'fixed-size-list' && array.child.kind === 'primitive') {
|
|
801
|
+
const logical = (array.offset || 0) + index;
|
|
802
|
+
for (let component = 0; component < array.size; component++) {
|
|
803
|
+
const scalar = (array.child.offset || 0) + logical * array.size + component;
|
|
804
|
+
array.child.values[scalar] = coordinate[component];
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
else if (array.kind === 'struct') {
|
|
808
|
+
const logical = (array.offset || 0) + index;
|
|
809
|
+
const names = ['x', 'y', 'z', 'm'];
|
|
810
|
+
for (let component = 0; component < names.length; component++) {
|
|
811
|
+
const child = array.children[names[component]];
|
|
812
|
+
if (child?.kind === 'primitive')
|
|
813
|
+
child.values[(child.offset || 0) + logical * (child.stride || 1)] = coordinate[component];
|
|
814
|
+
}
|
|
815
|
+
}
|
|
313
816
|
}
|
|
314
817
|
function collectCoordinateLeaves(column) {
|
|
315
818
|
const leaves = [];
|