@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.
Files changed (54) hide show
  1. package/README.md +45 -15
  2. package/dist/builder-entry.cjs +561 -0
  3. package/dist/builder-entry.cjs.map +6 -0
  4. package/dist/builder-entry.d.ts +4 -0
  5. package/dist/builder-entry.d.ts.map +1 -0
  6. package/dist/builder-entry.js +5 -0
  7. package/dist/builder-entry.js.map +1 -0
  8. package/dist/builder.d.ts +12 -1
  9. package/dist/builder.d.ts.map +1 -1
  10. package/dist/builder.js +112 -10
  11. package/dist/builder.js.map +1 -1
  12. package/dist/codecs.d.ts.map +1 -1
  13. package/dist/codecs.js +205 -7
  14. package/dist/codecs.js.map +1 -1
  15. package/dist/index.cjs +904 -120
  16. package/dist/index.cjs.map +2 -2
  17. package/dist/index.d.ts +1 -1
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +1 -1
  20. package/dist/index.js.map +1 -1
  21. package/dist/kernels.d.ts +8 -3
  22. package/dist/kernels.d.ts.map +1 -1
  23. package/dist/kernels.js +610 -107
  24. package/dist/kernels.js.map +1 -1
  25. package/dist/layout.d.ts.map +1 -1
  26. package/dist/layout.js +28 -5
  27. package/dist/layout.js.map +1 -1
  28. package/dist/tessellation-entry.cjs +427 -0
  29. package/dist/tessellation-entry.cjs.map +6 -0
  30. package/dist/tessellation-entry.d.ts +4 -0
  31. package/dist/tessellation-entry.d.ts.map +1 -0
  32. package/dist/tessellation-entry.js +5 -0
  33. package/dist/tessellation-entry.js.map +1 -0
  34. package/dist/types.d.ts +37 -3
  35. package/dist/types.d.ts.map +1 -1
  36. package/dist/types.js.map +1 -1
  37. package/dist/wkb-entry.cjs +1005 -0
  38. package/dist/wkb-entry.cjs.map +6 -0
  39. package/dist/wkb-entry.d.ts +3 -0
  40. package/dist/wkb-entry.d.ts.map +1 -0
  41. package/dist/wkb-entry.js +6 -0
  42. package/dist/wkb-entry.js.map +1 -0
  43. package/dist/worker.cjs +4 -0
  44. package/dist/worker.cjs.map +1 -1
  45. package/package.json +21 -6
  46. package/src/builder-entry.ts +18 -0
  47. package/src/builder.ts +133 -19
  48. package/src/codecs.ts +251 -9
  49. package/src/index.ts +1 -0
  50. package/src/kernels.ts +811 -133
  51. package/src/layout.ts +38 -13
  52. package/src/tessellation-entry.ts +7 -0
  53. package/src/types.ts +23 -4
  54. package/src/wkb-entry.ts +11 -0
package/src/codecs.ts CHANGED
@@ -2,7 +2,9 @@
2
2
  // SPDX-License-Identifier: MIT
3
3
  // Copyright (c) vis.gl contributors
4
4
 
5
- import {formatWKT, parseWKB, parseWKT, writeWKB} from '@math.gl/wkb';
5
+ import {formatWKT, parseWKB, parseWKT} from '@math.gl/wkb';
6
+ import type {WKBGeometryWriter} from '@math.gl/wkb';
7
+ import {WKBBuilder} from '@math.gl/wkb';
6
8
  import type {
7
9
  GeoArrowColumn,
8
10
  GeoArrowDimension,
@@ -11,7 +13,7 @@ import type {
11
13
  } from './types';
12
14
  import {getGeoArrowDimensionSize} from './types';
13
15
  import {makeGeoArrowColumnFromGeometryRows} from './builder';
14
- import {getGeoArrowOffset, isGeoArrowValueValid, materializeGeoArrowRows} from './layout';
16
+ import {getGeoArrowOffset, isGeoArrowValueValid, materializeGeometryRow} from './layout';
15
17
 
16
18
  const textEncoder = new TextEncoder();
17
19
  const textDecoder = new TextDecoder();
@@ -32,9 +34,42 @@ export function decodeGeoArrowWKB(column: GeoArrowColumn): GeoArrowColumn {
32
34
  /** Encodes a native GeoArrow column as variable-width WKB bytes. */
33
35
  export function encodeGeoArrowWKB(column: GeoArrowColumn): GeoArrowColumn {
34
36
  if (column.encoding === 'geoarrow.wkb') return column;
35
- const rows = materializeGeoArrowRows(column);
36
- const values = rows.map(row => (row ? writeWKB(row, column.dimension) : null));
37
- return copyMetadata(column, makeSerializedColumn(values, 'geoarrow.wkb', column.dimension));
37
+ const writers: Array<WKBGeometryWriter | null> = [];
38
+ const dimensions: GeoArrowDimension[] = [];
39
+ for (const chunk of column.chunks) {
40
+ for (let rowIndex = 0; rowIndex < chunk.length; rowIndex++) {
41
+ if (!isGeoArrowValueValid(chunk.validity, rowIndex)) {
42
+ writers.push(null);
43
+ dimensions.push(column.dimension);
44
+ continue;
45
+ }
46
+ dimensions.push(
47
+ getPhysicalGeometryDimension(chunk, rowIndex, column.encoding, column.dimension)
48
+ );
49
+ writers.push(builder =>
50
+ writePhysicalGeometry(builder, chunk, rowIndex, column.encoding, column.dimension)
51
+ );
52
+ }
53
+ }
54
+ const built = WKBBuilder.buildGeometryArray(writers, {
55
+ dimension: column.dimension,
56
+ dimensionResolver: index => dimensions[index] || column.dimension
57
+ });
58
+ return copyMetadata(column, {
59
+ encoding: 'geoarrow.wkb',
60
+ dimension: column.dimension,
61
+ coordinateLayout: null,
62
+ chunks: [
63
+ {
64
+ kind: 'serialized',
65
+ encoding: 'binary',
66
+ length: writers.length,
67
+ offsets: built.valueOffsets,
68
+ values: built.values,
69
+ ...(built.nullBitmap ? {validity: {values: built.nullBitmap}} : {})
70
+ }
71
+ ]
72
+ });
38
73
  }
39
74
 
40
75
  /** Decodes a GeoArrow WKT column into native physical geometry buffers. */
@@ -53,13 +88,206 @@ export function decodeGeoArrowWKT(column: GeoArrowColumn): GeoArrowColumn {
53
88
  /** Encodes a native GeoArrow column as variable-width UTF-8 WKT. */
54
89
  export function encodeGeoArrowWKT(column: GeoArrowColumn): GeoArrowColumn {
55
90
  if (column.encoding === 'geoarrow.wkt') return column;
56
- const rows = materializeGeoArrowRows(column);
57
- const values = rows.map(row =>
58
- row ? textEncoder.encode(formatWKT(row, column.dimension)) : null
59
- );
91
+ const values: Array<Uint8Array | null> = [];
92
+ for (const chunk of column.chunks) {
93
+ for (let rowIndex = 0; rowIndex < chunk.length; rowIndex++) {
94
+ const row = isGeoArrowValueValid(chunk.validity, rowIndex)
95
+ ? materializeGeometryRow(chunk, rowIndex, column.encoding)
96
+ : null;
97
+ values.push(row ? textEncoder.encode(formatWKT(row, column.dimension)) : null);
98
+ }
99
+ }
60
100
  return copyMetadata(column, makeSerializedColumn(values, 'geoarrow.wkt', column.dimension));
61
101
  }
62
102
 
103
+ function writePhysicalGeometry(
104
+ builder: WKBBuilder,
105
+ array: import('./types').GeoArrowArray,
106
+ rowIndex: number,
107
+ encoding: import('./types').GeoArrowEncoding,
108
+ dimension: GeoArrowDimension
109
+ ): void {
110
+ const depth = getEncodingDepth(encoding);
111
+ if (encoding === 'geoarrow.geometry' && array.kind === 'dense-union') {
112
+ const physical = (array.offset || 0) + rowIndex;
113
+ const typeId = array.typeIds[physical];
114
+ const child = array.children.find(candidate => candidate.typeId === typeId);
115
+ if (!child) throw new Error('Dense-union row references an unknown child');
116
+ const childDimension = child.dimension || dimension;
117
+ builder.withDimension(childDimension, () =>
118
+ writePhysicalGeometry(
119
+ builder,
120
+ child.data,
121
+ array.valueOffsets[physical],
122
+ child.encoding || encodingFromName(child.name),
123
+ childDimension
124
+ )
125
+ );
126
+ return;
127
+ }
128
+ if (encoding === 'geoarrow.geometrycollection' && array.kind === 'list') {
129
+ const [first, last] = getListRange(array, rowIndex);
130
+ builder.beginGeometry('GeometryCollection', last - first);
131
+ if (array.child.kind !== 'dense-union')
132
+ throw new Error('GeometryCollection requires dense union child');
133
+ const union = array.child;
134
+ for (let index = first; index < last; index++) {
135
+ const physical = (union.offset || 0) + index;
136
+ const child = union.children.find(candidate => candidate.typeId === union.typeIds[physical]);
137
+ if (child) {
138
+ const childDimension = child.dimension || dimension;
139
+ builder.withDimension(childDimension, () =>
140
+ writePhysicalGeometry(
141
+ builder,
142
+ child.data,
143
+ union.valueOffsets[physical],
144
+ child.encoding || encodingFromName(child.name),
145
+ childDimension
146
+ )
147
+ );
148
+ }
149
+ }
150
+ return;
151
+ }
152
+ if (array.kind !== 'list' && depth > 0) throw new Error(`Invalid ${encoding} storage`);
153
+ const writeCoordinate = (leaf: import('./types').GeoArrowArray, index: number): void => {
154
+ const coordinate = readPhysicalCoordinate(leaf, index);
155
+ if (!coordinate) throw new Error('Invalid GeoArrow coordinate storage');
156
+ if (dimension === 'xym')
157
+ builder.writeCoordinate(coordinate[0], coordinate[1], undefined, coordinate[2]);
158
+ else if (dimension === 'xyzm')
159
+ builder.writeCoordinate(coordinate[0], coordinate[1], coordinate[2], coordinate[3]);
160
+ else builder.writeCoordinate(coordinate[0], coordinate[1], coordinate[2]);
161
+ };
162
+ const writeSequence = (
163
+ leaf: import('./types').GeoArrowArray,
164
+ first: number,
165
+ last: number
166
+ ): void => {
167
+ for (let index = first; index < last; index++) writeCoordinate(leaf, index);
168
+ };
169
+ if (encoding === 'geoarrow.point') {
170
+ builder.beginPoint();
171
+ writeCoordinate(array, rowIndex);
172
+ } else if (encoding === 'geoarrow.linestring' || encoding === 'geoarrow.multipoint') {
173
+ const [first, last] = getListRange(array as import('./types').GeoArrowList, rowIndex);
174
+ if (encoding === 'geoarrow.linestring') {
175
+ builder.beginLineString(last - first);
176
+ writeSequence((array as import('./types').GeoArrowList).child, first, last);
177
+ } else {
178
+ builder.beginMultiPoint(last - first);
179
+ const leaf = (array as import('./types').GeoArrowList).child;
180
+ for (let index = first; index < last; index++) {
181
+ builder.beginPoint();
182
+ writeCoordinate(leaf, index);
183
+ }
184
+ }
185
+ } else if (encoding === 'geoarrow.polygon' || encoding === 'geoarrow.multilinestring') {
186
+ const [first, last] = getListRange(array as import('./types').GeoArrowList, rowIndex);
187
+ const parts = (array as import('./types').GeoArrowList).child;
188
+ if (encoding === 'geoarrow.polygon') builder.beginPolygon(last - first);
189
+ else builder.beginMultiLineString(last - first);
190
+ for (let partIndex = first; partIndex < last; partIndex++) {
191
+ const [ringFirst, ringLast] = getListRange(
192
+ parts as import('./types').GeoArrowList,
193
+ partIndex
194
+ );
195
+ const leaf = (parts as import('./types').GeoArrowList).child;
196
+ if (encoding === 'geoarrow.polygon') builder.beginLinearRing(ringLast - ringFirst);
197
+ else builder.beginLineString(ringLast - ringFirst);
198
+ writeSequence(leaf, ringFirst, ringLast);
199
+ }
200
+ } else if (encoding === 'geoarrow.multipolygon') {
201
+ const [first, last] = getListRange(array as import('./types').GeoArrowList, rowIndex);
202
+ const polygons = (array as import('./types').GeoArrowList).child;
203
+ builder.beginMultiPolygon(last - first);
204
+ for (let polygonIndex = first; polygonIndex < last; polygonIndex++) {
205
+ const [partFirst, partLast] = getListRange(
206
+ polygons as import('./types').GeoArrowList,
207
+ polygonIndex
208
+ );
209
+ const parts = (polygons as import('./types').GeoArrowList).child;
210
+ builder.beginPolygon(partLast - partFirst);
211
+ for (let partIndex = partFirst; partIndex < partLast; partIndex++) {
212
+ const [ringFirst, ringLast] = getListRange(
213
+ parts as import('./types').GeoArrowList,
214
+ partIndex
215
+ );
216
+ builder.beginLinearRing(ringLast - ringFirst);
217
+ writeSequence((parts as import('./types').GeoArrowList).child, ringFirst, ringLast);
218
+ }
219
+ }
220
+ }
221
+ }
222
+
223
+ function getPhysicalGeometryDimension(
224
+ array: import('./types').GeoArrowArray,
225
+ rowIndex: number,
226
+ encoding: import('./types').GeoArrowEncoding,
227
+ dimension: GeoArrowDimension
228
+ ): GeoArrowDimension {
229
+ if (encoding !== 'geoarrow.geometry' || array.kind !== 'dense-union') return dimension;
230
+ const physical = (array.offset || 0) + rowIndex;
231
+ const child = array.children.find(candidate => candidate.typeId === array.typeIds[physical]);
232
+ return child?.dimension || dimension;
233
+ }
234
+
235
+ function readPhysicalCoordinate(
236
+ array: import('./types').GeoArrowArray,
237
+ index: number
238
+ ): number[] | null {
239
+ if (array.kind === 'fixed-size-list' && array.child.kind === 'primitive') {
240
+ const logical = (array.offset || 0) + index;
241
+ const values: number[] = [];
242
+ for (let component = 0; component < array.size; component++)
243
+ values.push(
244
+ Number(
245
+ array.child.values[
246
+ (array.child.offset || 0) +
247
+ (logical * array.size + component) * (array.child.stride || 1)
248
+ ]
249
+ )
250
+ );
251
+ return values;
252
+ }
253
+ if (array.kind === 'struct') {
254
+ const logical = (array.offset || 0) + index;
255
+ return ['x', 'y', 'z', 'm'].flatMap(name => {
256
+ const child = array.children[name];
257
+ return child?.kind === 'primitive'
258
+ ? [Number(child.values[(child.offset || 0) + logical * (child.stride || 1)])]
259
+ : [];
260
+ });
261
+ }
262
+ return null;
263
+ }
264
+
265
+ function getListRange(list: import('./types').GeoArrowList, index: number): [number, number] {
266
+ const offset = (list.offset || 0) + index;
267
+ const base =
268
+ typeof (list.offsetBase ?? 0) === 'bigint'
269
+ ? Number(list.offsetBase)
270
+ : Number(list.offsetBase ?? 0);
271
+ return [
272
+ getGeoArrowOffset(list.offsets, offset) - base,
273
+ getGeoArrowOffset(list.offsets, offset + 1) - base
274
+ ];
275
+ }
276
+
277
+ function getEncodingDepth(encoding: import('./types').GeoArrowEncoding): number {
278
+ return encoding === 'geoarrow.point'
279
+ ? 0
280
+ : encoding === 'geoarrow.linestring' || encoding === 'geoarrow.multipoint'
281
+ ? 1
282
+ : encoding === 'geoarrow.polygon' || encoding === 'geoarrow.multilinestring'
283
+ ? 2
284
+ : 3;
285
+ }
286
+
287
+ function encodingFromName(name: string): import('./types').GeoArrowEncoding {
288
+ return `geoarrow.${name.toLowerCase()}` as import('./types').GeoArrowEncoding;
289
+ }
290
+
63
291
  function normalizeRowsToDimension(
64
292
  rows: readonly (GeoArrowGeometryValue | null)[],
65
293
  dimension: GeoArrowDimension
@@ -106,6 +334,20 @@ function decodeSerializedRows(
106
334
  }
107
335
 
108
336
  function getSerializedBytes(array: GeoArrowSerialized, rowIndex: number): Uint8Array {
337
+ if (array.views) {
338
+ const viewIndex = ((array.offset || 0) + rowIndex) * 4;
339
+ const length = array.views[viewIndex];
340
+ // Arrow BinaryView stores short values inline in the 16-byte view record.
341
+ if (length <= 12) {
342
+ const byteOffset = array.views.byteOffset + viewIndex * 4 + 4;
343
+ return new Uint8Array(array.views.buffer, byteOffset, length);
344
+ }
345
+ const bufferIndex = array.views[viewIndex + 2];
346
+ const byteOffset = array.views[viewIndex + 3];
347
+ const data = array.dataBuffers?.[bufferIndex];
348
+ if (!data) throw new Error(`Serialized view references missing data buffer ${bufferIndex}`);
349
+ return data.subarray(byteOffset, byteOffset + length);
350
+ }
109
351
  const offsetIndex = (array.offset || 0) + rowIndex;
110
352
  const baseValue = array.offsetBase ?? 0;
111
353
  const base = typeof baseValue === 'bigint' ? Number(baseValue) : baseValue;
package/src/index.ts CHANGED
@@ -72,6 +72,7 @@ export {
72
72
  assertGeoArrowResourceLimits,
73
73
  convertGeoArrowColumn,
74
74
  getGeoArrowBounds,
75
+ getGeoArrowRowBounds,
75
76
  interleaveGeoArrowCoordinates,
76
77
  mapGeoArrowCoordinates,
77
78
  mapGeoArrowCoordinatesInto,