@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/kernels.ts
ADDED
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
// math.gl
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
|
|
5
|
+
import type {
|
|
6
|
+
GeoArrowArray,
|
|
7
|
+
GeoArrowBounds,
|
|
8
|
+
GeoArrowColumn,
|
|
9
|
+
GeoArrowCoordinateLayout,
|
|
10
|
+
GeoArrowCoordinateMapper,
|
|
11
|
+
GeoArrowDimension,
|
|
12
|
+
GeoArrowEncoding,
|
|
13
|
+
GeoArrowGeometryValue
|
|
14
|
+
} from './types';
|
|
15
|
+
import {getGeoArrowDimensionSize} from './types';
|
|
16
|
+
import {makeGeoArrowColumnFromGeometryRows} from './builder';
|
|
17
|
+
import {
|
|
18
|
+
getGeoArrowRowCount,
|
|
19
|
+
getGeoArrowVertexCount,
|
|
20
|
+
isGeoArrowValueValid,
|
|
21
|
+
materializeGeoArrowRows,
|
|
22
|
+
visitGeoArrowCoordinates
|
|
23
|
+
} from './layout';
|
|
24
|
+
|
|
25
|
+
/** Resource limits applied before potentially expensive materialization or conversion. */
|
|
26
|
+
export type GeoArrowResourceLimitOptions = Readonly<{
|
|
27
|
+
maximumRows?: number;
|
|
28
|
+
maximumCoordinates?: number;
|
|
29
|
+
maximumChunks?: number;
|
|
30
|
+
maximumNestingDepth?: number;
|
|
31
|
+
maximumOutputBytes?: number;
|
|
32
|
+
}>;
|
|
33
|
+
|
|
34
|
+
/** Coordinate-map options. */
|
|
35
|
+
export type MapGeoArrowCoordinatesOptions = Readonly<{
|
|
36
|
+
dimension?: GeoArrowDimension;
|
|
37
|
+
coordinateLayout?: GeoArrowCoordinateLayout;
|
|
38
|
+
limits?: GeoArrowResourceLimitOptions;
|
|
39
|
+
}>;
|
|
40
|
+
|
|
41
|
+
/** Conversion options for native physical layouts. */
|
|
42
|
+
export type ConvertGeoArrowColumnOptions = Readonly<{
|
|
43
|
+
encoding?: GeoArrowEncoding;
|
|
44
|
+
dimension?: GeoArrowDimension;
|
|
45
|
+
coordinateLayout?: GeoArrowCoordinateLayout;
|
|
46
|
+
limits?: GeoArrowResourceLimitOptions;
|
|
47
|
+
}>;
|
|
48
|
+
|
|
49
|
+
/** Ring orientation requested by {@link rewindGeoArrow}. */
|
|
50
|
+
export type GeoArrowRingOrientation = 'clockwise' | 'counter-clockwise';
|
|
51
|
+
|
|
52
|
+
/** Rewind options. */
|
|
53
|
+
export type RewindGeoArrowOptions = Readonly<{
|
|
54
|
+
outer?: GeoArrowRingOrientation;
|
|
55
|
+
limits?: GeoArrowResourceLimitOptions;
|
|
56
|
+
}>;
|
|
57
|
+
|
|
58
|
+
/** Returns XY bounds, or null when the column contains no finite coordinates. */
|
|
59
|
+
export function getGeoArrowBounds(column: GeoArrowColumn): GeoArrowBounds | null {
|
|
60
|
+
if (column.encoding === 'geoarrow.box') return getGeoArrowBoxBounds(column);
|
|
61
|
+
let minimumX = Number.POSITIVE_INFINITY;
|
|
62
|
+
let minimumY = Number.POSITIVE_INFINITY;
|
|
63
|
+
let maximumX = Number.NEGATIVE_INFINITY;
|
|
64
|
+
let maximumY = Number.NEGATIVE_INFINITY;
|
|
65
|
+
visitGeoArrowCoordinates(column, coordinate => {
|
|
66
|
+
const x = coordinate[0];
|
|
67
|
+
const y = coordinate[1];
|
|
68
|
+
if (Number.isFinite(x) && Number.isFinite(y)) {
|
|
69
|
+
minimumX = Math.min(minimumX, x);
|
|
70
|
+
minimumY = Math.min(minimumY, y);
|
|
71
|
+
maximumX = Math.max(maximumX, x);
|
|
72
|
+
maximumY = Math.max(maximumY, y);
|
|
73
|
+
}
|
|
74
|
+
return coordinate;
|
|
75
|
+
});
|
|
76
|
+
return Number.isFinite(minimumX) ? [minimumX, minimumY, maximumX, maximumY] : null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function getGeoArrowBoxBounds(column: GeoArrowColumn): GeoArrowBounds | null {
|
|
80
|
+
let minimumX = Number.POSITIVE_INFINITY;
|
|
81
|
+
let minimumY = Number.POSITIVE_INFINITY;
|
|
82
|
+
let maximumX = Number.NEGATIVE_INFINITY;
|
|
83
|
+
let maximumY = Number.NEGATIVE_INFINITY;
|
|
84
|
+
for (const chunk of column.chunks) {
|
|
85
|
+
if (chunk.kind !== 'struct') continue;
|
|
86
|
+
const xmin = chunk.children['xmin'];
|
|
87
|
+
const ymin = chunk.children['ymin'];
|
|
88
|
+
const xmax = chunk.children['xmax'];
|
|
89
|
+
const ymax = chunk.children['ymax'];
|
|
90
|
+
if (!xmin || !ymin || !xmax || !ymax) continue;
|
|
91
|
+
for (let rowIndex = 0; rowIndex < chunk.length; rowIndex++) {
|
|
92
|
+
if (!isGeoArrowValueValid(chunk.validity, rowIndex)) continue;
|
|
93
|
+
const structIndex = (chunk.offset || 0) + rowIndex;
|
|
94
|
+
const bounds = [
|
|
95
|
+
readPrimitiveNumber(xmin, structIndex),
|
|
96
|
+
readPrimitiveNumber(ymin, structIndex),
|
|
97
|
+
readPrimitiveNumber(xmax, structIndex),
|
|
98
|
+
readPrimitiveNumber(ymax, structIndex)
|
|
99
|
+
];
|
|
100
|
+
if (bounds.every(Number.isFinite)) {
|
|
101
|
+
minimumX = Math.min(minimumX, bounds[0]);
|
|
102
|
+
minimumY = Math.min(minimumY, bounds[1]);
|
|
103
|
+
maximumX = Math.max(maximumX, bounds[2]);
|
|
104
|
+
maximumY = Math.max(maximumY, bounds[3]);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return Number.isFinite(minimumX) ? [minimumX, minimumY, maximumX, maximumY] : null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function readPrimitiveNumber(array: GeoArrowArray, index: number): number {
|
|
112
|
+
if (array.kind !== 'primitive' || !isGeoArrowValueValid(array.validity, index)) return Number.NaN;
|
|
113
|
+
const valueIndex = (array.offset || 0) + index * (array.stride || 1);
|
|
114
|
+
return Number(array.values[valueIndex]);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Maps every coordinate into newly allocated physical buffers while preserving column semantics. */
|
|
118
|
+
export function mapGeoArrowCoordinates(
|
|
119
|
+
column: GeoArrowColumn,
|
|
120
|
+
mapper: GeoArrowCoordinateMapper,
|
|
121
|
+
options: MapGeoArrowCoordinatesOptions = {}
|
|
122
|
+
): GeoArrowColumn {
|
|
123
|
+
assertGeoArrowResourceLimits(column, options.limits);
|
|
124
|
+
const rows = materializeGeoArrowRows(column);
|
|
125
|
+
let rowIndex = 0;
|
|
126
|
+
const mappedRows = rows.map(row => {
|
|
127
|
+
const mapped = row
|
|
128
|
+
? mapGeometryCoordinates(row, coordinate => mapper(coordinate, rowIndex))
|
|
129
|
+
: null;
|
|
130
|
+
rowIndex++;
|
|
131
|
+
return mapped;
|
|
132
|
+
});
|
|
133
|
+
const result = makeGeoArrowColumnFromGeometryRows(mappedRows, {
|
|
134
|
+
dimension: options.dimension || column.dimension,
|
|
135
|
+
coordinateLayout: options.coordinateLayout || column.coordinateLayout || 'interleaved'
|
|
136
|
+
});
|
|
137
|
+
return copyColumnMetadata(column, result);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Maps coordinates and copies the result into caller-provided destination buffers.
|
|
142
|
+
*
|
|
143
|
+
* The destination must have the same physical topology as the mapped result. Topology, validity,
|
|
144
|
+
* offsets, and metadata in the destination are never replaced.
|
|
145
|
+
*/
|
|
146
|
+
export function mapGeoArrowCoordinatesInto(
|
|
147
|
+
target: GeoArrowColumn,
|
|
148
|
+
source: GeoArrowColumn,
|
|
149
|
+
mapper: GeoArrowCoordinateMapper,
|
|
150
|
+
options: Omit<MapGeoArrowCoordinatesOptions, 'coordinateLayout'> = {}
|
|
151
|
+
): GeoArrowColumn {
|
|
152
|
+
const mapped = mapGeoArrowCoordinates(source, mapper, {
|
|
153
|
+
...options,
|
|
154
|
+
coordinateLayout: target.coordinateLayout || undefined
|
|
155
|
+
});
|
|
156
|
+
const sourceLeaves = collectCoordinateLeaves(mapped);
|
|
157
|
+
const targetLeaves = collectCoordinateLeaves(target);
|
|
158
|
+
if (sourceLeaves.length !== targetLeaves.length) {
|
|
159
|
+
throw new Error('GeoArrow destination has different coordinate topology');
|
|
160
|
+
}
|
|
161
|
+
for (let index = 0; index < sourceLeaves.length; index++) {
|
|
162
|
+
const sourceLeaf = sourceLeaves[index];
|
|
163
|
+
const targetLeaf = targetLeaves[index];
|
|
164
|
+
if (sourceLeaf.length !== targetLeaf.length) {
|
|
165
|
+
throw new Error('GeoArrow destination coordinate buffer has a different length');
|
|
166
|
+
}
|
|
167
|
+
targetLeaf.set(sourceLeaf as never);
|
|
168
|
+
}
|
|
169
|
+
return target;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** Converts separated native coordinates to interleaved tuples. Identity is returned unchanged. */
|
|
173
|
+
export function interleaveGeoArrowCoordinates(column: GeoArrowColumn): GeoArrowColumn {
|
|
174
|
+
if (column.coordinateLayout === 'interleaved') return column;
|
|
175
|
+
if (!column.coordinateLayout) return column;
|
|
176
|
+
return mapGeoArrowCoordinates(column, coordinate => coordinate, {
|
|
177
|
+
coordinateLayout: 'interleaved'
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** Normalizes dense-union children by ascending type ID without changing dispatch buffers. */
|
|
182
|
+
export function normalizeGeoArrowUnion(column: GeoArrowColumn): GeoArrowColumn {
|
|
183
|
+
if (column.encoding !== 'geoarrow.geometry') return column;
|
|
184
|
+
let changed = false;
|
|
185
|
+
const chunks = column.chunks.map(chunk => {
|
|
186
|
+
if (chunk.kind !== 'dense-union') return chunk;
|
|
187
|
+
const children = [...chunk.children].sort((left, right) => left.typeId - right.typeId);
|
|
188
|
+
changed ||= children.some((child, index) => child !== chunk.children[index]);
|
|
189
|
+
return children.every((child, index) => child === chunk.children[index])
|
|
190
|
+
? chunk
|
|
191
|
+
: {...chunk, children};
|
|
192
|
+
});
|
|
193
|
+
return changed ? {...column, chunks} : column;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** Converts between concrete/mixed encodings, dimensions, and coordinate layouts. */
|
|
197
|
+
export function convertGeoArrowColumn(
|
|
198
|
+
column: GeoArrowColumn,
|
|
199
|
+
options: ConvertGeoArrowColumnOptions = {}
|
|
200
|
+
): GeoArrowColumn {
|
|
201
|
+
const encoding = options.encoding || column.encoding;
|
|
202
|
+
const dimension = options.dimension || column.dimension;
|
|
203
|
+
const coordinateLayout = options.coordinateLayout || column.coordinateLayout;
|
|
204
|
+
if (
|
|
205
|
+
encoding === column.encoding &&
|
|
206
|
+
dimension === column.dimension &&
|
|
207
|
+
coordinateLayout === column.coordinateLayout
|
|
208
|
+
) {
|
|
209
|
+
return column;
|
|
210
|
+
}
|
|
211
|
+
if (encoding === 'geoarrow.wkb' || encoding === 'geoarrow.wkt') {
|
|
212
|
+
throw new Error('Use encodeGeoArrowWKB or encodeGeoArrowWKT for serialized output');
|
|
213
|
+
}
|
|
214
|
+
assertGeoArrowResourceLimits(column, options.limits);
|
|
215
|
+
const rows = materializeGeoArrowRows(column).map(row =>
|
|
216
|
+
row ? convertGeometryFamily(row, encoding, column.dimension, dimension) : null
|
|
217
|
+
);
|
|
218
|
+
const result = makeGeoArrowColumnFromGeometryRows(rows, {
|
|
219
|
+
encoding: encoding === 'geoarrow.geometry' ? encoding : undefined,
|
|
220
|
+
dimension,
|
|
221
|
+
coordinateLayout: coordinateLayout || 'interleaved'
|
|
222
|
+
});
|
|
223
|
+
if (result.encoding !== encoding && encoding !== 'geoarrow.geometry') {
|
|
224
|
+
throw new Error(`Rows cannot be represented as ${encoding}`);
|
|
225
|
+
}
|
|
226
|
+
return copyColumnMetadata(column, result);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** Rewinds polygon rings without changing non-polygon rows. */
|
|
230
|
+
export function rewindGeoArrow(
|
|
231
|
+
column: GeoArrowColumn,
|
|
232
|
+
options: RewindGeoArrowOptions = {}
|
|
233
|
+
): GeoArrowColumn {
|
|
234
|
+
assertGeoArrowResourceLimits(column, options.limits);
|
|
235
|
+
const outerClockwise = (options.outer || 'counter-clockwise') === 'clockwise';
|
|
236
|
+
let changed = false;
|
|
237
|
+
const rows = materializeGeoArrowRows(column).map(row => {
|
|
238
|
+
if (!row) return null;
|
|
239
|
+
const rewound = rewindGeometry(row, outerClockwise);
|
|
240
|
+
changed ||= rewound !== row;
|
|
241
|
+
return rewound;
|
|
242
|
+
});
|
|
243
|
+
if (!changed) return column;
|
|
244
|
+
const result = makeGeoArrowColumnFromGeometryRows(rows, {
|
|
245
|
+
dimension: column.dimension,
|
|
246
|
+
coordinateLayout: column.coordinateLayout || 'interleaved'
|
|
247
|
+
});
|
|
248
|
+
return copyColumnMetadata(column, result);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/** Throws when a column exceeds configured work or output limits. */
|
|
252
|
+
export function assertGeoArrowResourceLimits(
|
|
253
|
+
column: GeoArrowColumn,
|
|
254
|
+
options: GeoArrowResourceLimitOptions = {}
|
|
255
|
+
): void {
|
|
256
|
+
const rows = getGeoArrowRowCount(column);
|
|
257
|
+
if (rows > (options.maximumRows ?? Number.POSITIVE_INFINITY)) {
|
|
258
|
+
throw new Error(`GeoArrow row count ${rows} exceeds maximumRows`);
|
|
259
|
+
}
|
|
260
|
+
if (column.chunks.length > (options.maximumChunks ?? Number.POSITIVE_INFINITY)) {
|
|
261
|
+
throw new Error(`GeoArrow chunk count ${column.chunks.length} exceeds maximumChunks`);
|
|
262
|
+
}
|
|
263
|
+
const coordinates = getGeoArrowVertexCount(column);
|
|
264
|
+
if (coordinates > (options.maximumCoordinates ?? Number.POSITIVE_INFINITY)) {
|
|
265
|
+
throw new Error(`GeoArrow coordinate count ${coordinates} exceeds maximumCoordinates`);
|
|
266
|
+
}
|
|
267
|
+
if (options.maximumNestingDepth !== undefined) {
|
|
268
|
+
const depth = Math.max(0, ...column.chunks.map(getArrayDepth));
|
|
269
|
+
if (depth > options.maximumNestingDepth) {
|
|
270
|
+
throw new Error(`GeoArrow nesting depth ${depth} exceeds maximumNestingDepth`);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
if (options.maximumOutputBytes !== undefined) {
|
|
274
|
+
const estimatedBytes = coordinates * getGeoArrowDimensionSize(column.dimension) * 8;
|
|
275
|
+
if (estimatedBytes > options.maximumOutputBytes) {
|
|
276
|
+
throw new Error(`GeoArrow estimated output ${estimatedBytes} exceeds maximumOutputBytes`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function mapGeometryCoordinates(
|
|
282
|
+
geometry: GeoArrowGeometryValue,
|
|
283
|
+
mapper: (coordinate: readonly number[]) => readonly number[]
|
|
284
|
+
): GeoArrowGeometryValue {
|
|
285
|
+
if (geometry.type === 'GeometryCollection') {
|
|
286
|
+
return {
|
|
287
|
+
...geometry,
|
|
288
|
+
geometries: geometry.geometries.map(child => mapGeometryCoordinates(child, mapper))
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
return {
|
|
292
|
+
...geometry,
|
|
293
|
+
coordinates: mapCoordinateNesting(geometry.coordinates, mapper)
|
|
294
|
+
} as GeoArrowGeometryValue;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function mapCoordinateNesting(
|
|
298
|
+
value: readonly unknown[],
|
|
299
|
+
mapper: (coordinate: readonly number[]) => readonly number[]
|
|
300
|
+
): unknown {
|
|
301
|
+
if (value.length === 0) return [];
|
|
302
|
+
if (typeof value[0] === 'number') return [...mapper(value as readonly number[])];
|
|
303
|
+
return value.map(child => mapCoordinateNesting(child as readonly unknown[], mapper));
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function rewindGeometry(
|
|
307
|
+
geometry: GeoArrowGeometryValue,
|
|
308
|
+
outerClockwise: boolean
|
|
309
|
+
): GeoArrowGeometryValue {
|
|
310
|
+
if (geometry.type === 'GeometryCollection') {
|
|
311
|
+
const geometries = geometry.geometries.map(child => rewindGeometry(child, outerClockwise));
|
|
312
|
+
return geometries.some((child, index) => child !== geometry.geometries[index])
|
|
313
|
+
? {...geometry, geometries}
|
|
314
|
+
: geometry;
|
|
315
|
+
}
|
|
316
|
+
if (geometry.type === 'Polygon') {
|
|
317
|
+
const coordinates = rewindPolygon(geometry.coordinates, outerClockwise);
|
|
318
|
+
return coordinates === geometry.coordinates ? geometry : {...geometry, coordinates};
|
|
319
|
+
}
|
|
320
|
+
if (geometry.type === 'MultiPolygon') {
|
|
321
|
+
const coordinates = geometry.coordinates.map(polygon => rewindPolygon(polygon, outerClockwise));
|
|
322
|
+
return coordinates.every((polygon, index) => polygon === geometry.coordinates[index])
|
|
323
|
+
? geometry
|
|
324
|
+
: {...geometry, coordinates};
|
|
325
|
+
}
|
|
326
|
+
return geometry;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function rewindPolygon(
|
|
330
|
+
rings: readonly (readonly (readonly number[])[])[],
|
|
331
|
+
outerClockwise: boolean
|
|
332
|
+
): readonly (readonly (readonly number[])[])[] {
|
|
333
|
+
let changed = false;
|
|
334
|
+
const result = rings.map((ring, ringIndex) => {
|
|
335
|
+
const shouldBeClockwise = ringIndex === 0 ? outerClockwise : !outerClockwise;
|
|
336
|
+
const isClockwise = getRingSignedArea(ring) < 0;
|
|
337
|
+
if (isClockwise === shouldBeClockwise) return ring;
|
|
338
|
+
changed = true;
|
|
339
|
+
return [...ring].reverse();
|
|
340
|
+
});
|
|
341
|
+
return changed ? result : rings;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function getRingSignedArea(ring: readonly (readonly number[])[]): number {
|
|
345
|
+
let area = 0;
|
|
346
|
+
for (let index = 0; index < ring.length; index++) {
|
|
347
|
+
const current = ring[index];
|
|
348
|
+
const next = ring[(index + 1) % ring.length];
|
|
349
|
+
area += current[0] * next[1] - next[0] * current[1];
|
|
350
|
+
}
|
|
351
|
+
return area / 2;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function convertGeometryFamily(
|
|
355
|
+
geometry: GeoArrowGeometryValue,
|
|
356
|
+
encoding: GeoArrowEncoding,
|
|
357
|
+
sourceDimension: GeoArrowDimension,
|
|
358
|
+
targetDimension: GeoArrowDimension
|
|
359
|
+
): GeoArrowGeometryValue {
|
|
360
|
+
if (geometry.type === 'GeometryCollection') {
|
|
361
|
+
const geometries = geometry.geometries.map(child =>
|
|
362
|
+
convertGeometryFamily(child, 'geoarrow.geometry', sourceDimension, targetDimension)
|
|
363
|
+
);
|
|
364
|
+
if (encoding === 'geoarrow.geometry' || encoding === 'geoarrow.geometrycollection') {
|
|
365
|
+
return {...geometry, geometries};
|
|
366
|
+
}
|
|
367
|
+
throw new Error(`${geometry.type} cannot be represented as ${encoding}`);
|
|
368
|
+
}
|
|
369
|
+
const coordinates = mapCoordinateNesting(geometry.coordinates, coordinate =>
|
|
370
|
+
resizeCoordinate(coordinate, sourceDimension, targetDimension)
|
|
371
|
+
);
|
|
372
|
+
if (encoding === 'geoarrow.geometry') {
|
|
373
|
+
return {...geometry, coordinates} as GeoArrowGeometryValue;
|
|
374
|
+
}
|
|
375
|
+
const target = encoding.replace('geoarrow.', '');
|
|
376
|
+
if (geometry.type.toLowerCase() !== target) {
|
|
377
|
+
throw new Error(`${geometry.type} cannot be represented as ${encoding}`);
|
|
378
|
+
}
|
|
379
|
+
return {...geometry, coordinates} as GeoArrowGeometryValue;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function resizeCoordinate(
|
|
383
|
+
coordinate: readonly number[],
|
|
384
|
+
sourceDimension: GeoArrowDimension,
|
|
385
|
+
targetDimension: GeoArrowDimension
|
|
386
|
+
): number[] {
|
|
387
|
+
const sourceNames = getDimensionNames(sourceDimension);
|
|
388
|
+
const targetNames = getDimensionNames(targetDimension);
|
|
389
|
+
return targetNames.map(name => {
|
|
390
|
+
const sourceIndex = sourceNames.indexOf(name);
|
|
391
|
+
return sourceIndex >= 0 ? (coordinate[sourceIndex] ?? 0) : 0;
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function getDimensionNames(dimension: GeoArrowDimension): Array<'x' | 'y' | 'z' | 'm'> {
|
|
396
|
+
switch (dimension) {
|
|
397
|
+
case 'xy':
|
|
398
|
+
return ['x', 'y'];
|
|
399
|
+
case 'xyz':
|
|
400
|
+
return ['x', 'y', 'z'];
|
|
401
|
+
case 'xym':
|
|
402
|
+
return ['x', 'y', 'm'];
|
|
403
|
+
case 'xyzm':
|
|
404
|
+
return ['x', 'y', 'z', 'm'];
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function copyColumnMetadata(source: GeoArrowColumn, target: GeoArrowColumn): GeoArrowColumn {
|
|
409
|
+
return {
|
|
410
|
+
...target,
|
|
411
|
+
spatialReference: source.spatialReference,
|
|
412
|
+
edges: source.edges,
|
|
413
|
+
metadata: source.metadata
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function collectCoordinateLeaves(column: GeoArrowColumn): Array<Float32Array | Float64Array> {
|
|
418
|
+
const leaves: Array<Float32Array | Float64Array> = [];
|
|
419
|
+
for (const chunk of column.chunks) collectArrayCoordinateLeaves(chunk, leaves);
|
|
420
|
+
return leaves;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function collectArrayCoordinateLeaves(
|
|
424
|
+
array: GeoArrowArray,
|
|
425
|
+
leaves: Array<Float32Array | Float64Array>
|
|
426
|
+
): void {
|
|
427
|
+
switch (array.kind) {
|
|
428
|
+
case 'primitive':
|
|
429
|
+
if (array.values instanceof Float32Array || array.values instanceof Float64Array) {
|
|
430
|
+
leaves.push(array.values);
|
|
431
|
+
}
|
|
432
|
+
break;
|
|
433
|
+
case 'fixed-size-list':
|
|
434
|
+
case 'list':
|
|
435
|
+
collectArrayCoordinateLeaves(array.child, leaves);
|
|
436
|
+
break;
|
|
437
|
+
case 'struct':
|
|
438
|
+
for (const name of ['x', 'y', 'z', 'm']) {
|
|
439
|
+
const child = array.children[name];
|
|
440
|
+
if (child) collectArrayCoordinateLeaves(child, leaves);
|
|
441
|
+
}
|
|
442
|
+
break;
|
|
443
|
+
case 'dense-union':
|
|
444
|
+
for (const child of array.children) collectArrayCoordinateLeaves(child.data, leaves);
|
|
445
|
+
break;
|
|
446
|
+
default:
|
|
447
|
+
break;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
function getArrayDepth(array: GeoArrowArray): number {
|
|
452
|
+
switch (array.kind) {
|
|
453
|
+
case 'list':
|
|
454
|
+
case 'fixed-size-list':
|
|
455
|
+
return 1 + getArrayDepth(array.child);
|
|
456
|
+
case 'struct':
|
|
457
|
+
return 1 + Math.max(0, ...Object.values(array.children).map(getArrayDepth));
|
|
458
|
+
case 'dense-union':
|
|
459
|
+
return 1 + Math.max(0, ...array.children.map(child => getArrayDepth(child.data)));
|
|
460
|
+
default:
|
|
461
|
+
return 1;
|
|
462
|
+
}
|
|
463
|
+
}
|