@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/dist/index.cjs CHANGED
@@ -31,6 +31,7 @@ __export(index_exports, {
31
31
  getGeoArrowDimensionSize: () => getGeoArrowDimensionSize,
32
32
  getGeoArrowEncodingForGeometry: () => getGeoArrowEncodingForGeometry,
33
33
  getGeoArrowGeometryType: () => getGeoArrowGeometryType,
34
+ getGeoArrowRowBounds: () => getGeoArrowRowBounds,
34
35
  getGeoArrowRowCount: () => getGeoArrowRowCount,
35
36
  getGeoArrowTransferList: () => getGeoArrowTransferList,
36
37
  getGeoArrowVertexCount: () => getGeoArrowVertexCount,
@@ -285,7 +286,7 @@ function visitUnionRowCoordinates(union, rowIndex, sourceRowIndex, visitor) {
285
286
  const child = union.children.find((candidate) => candidate.typeId === typeId);
286
287
  if (!child)
287
288
  return;
288
- const encoding = getEncodingFromChildName(child.name);
289
+ const encoding = child.encoding || getEncodingFromChildName(child.name);
289
290
  if (encoding === "geoarrow.geometrycollection" && child.data.kind === "list") {
290
291
  const [first, last] = getListRange(child.data, valueOffset);
291
292
  if (child.data.child.kind === "dense-union") {
@@ -363,7 +364,7 @@ function materializeUnionRow(union, rowIndex) {
363
364
  const child = union.children.find((candidate) => candidate.typeId === typeId);
364
365
  if (!child || valueOffset < 0 || valueOffset >= child.data.length)
365
366
  return null;
366
- return materializeGeometryRow(child.data, valueOffset, getEncodingFromChildName(child.name));
367
+ return materializeGeometryRow(child.data, valueOffset, child.encoding || getEncodingFromChildName(child.name));
367
368
  }
368
369
  function getEncodingFromChildName(name) {
369
370
  const normalized = name.replace(/[^a-z]/gi, "").toLowerCase();
@@ -509,7 +510,18 @@ function validateArray(array, path, issues) {
509
510
  break;
510
511
  }
511
512
  case "serialized": {
512
- validateOffsets(array.offsets, array.offset || 0, array.length, array.values.length, array.offsetBase, path, issues);
513
+ if (array.views) {
514
+ const requiredWords = ((array.offset || 0) + array.length) * 4;
515
+ if (requiredWords > array.views.length) {
516
+ issues.push({
517
+ code: "invalid-length",
518
+ path,
519
+ message: "Serialized view records are too short."
520
+ });
521
+ }
522
+ } else {
523
+ validateOffsets(array.offsets, array.offset || 0, array.length, array.values.length, array.offsetBase, path, issues);
524
+ }
513
525
  break;
514
526
  }
515
527
  }
@@ -627,12 +639,17 @@ function validateUnionLayouts(union, column, path, issues) {
627
639
  for (const child of union.children) {
628
640
  let encoding;
629
641
  try {
630
- encoding = getEncodingFromChildName(child.name);
642
+ encoding = child.encoding || getEncodingFromChildName(child.name);
631
643
  } catch (error) {
632
644
  addInvalidLayout(`${path}.${child.name}`, error.message, issues);
633
645
  continue;
634
646
  }
635
- validateGeometryLayout(child.data, encoding, column, `${path}.${child.name}`, issues);
647
+ const childColumn = child.dimension || child.coordinateLayout ? {
648
+ ...column,
649
+ dimension: child.dimension || column.dimension,
650
+ coordinateLayout: child.coordinateLayout || column.coordinateLayout
651
+ } : column;
652
+ validateGeometryLayout(child.data, encoding, childColumn, `${path}.${child.name}`, issues);
636
653
  }
637
654
  }
638
655
  function validateCoordinateLayout(array, column, path, issues) {
@@ -704,6 +721,10 @@ function collectArrayBuffers(array, buffers) {
704
721
  case "serialized":
705
722
  addBuffer(array.offsets.buffer, buffers);
706
723
  addBuffer(array.values.buffer, buffers);
724
+ if (array.views)
725
+ addBuffer(array.views.buffer, buffers);
726
+ for (const data of array.dataBuffers || [])
727
+ addBuffer(data.buffer, buffers);
707
728
  break;
708
729
  }
709
730
  }
@@ -724,6 +745,7 @@ var GeoArrowBuilder = class _GeoArrowBuilder {
724
745
  this.coordinateCount = 0;
725
746
  this.partCount = 0;
726
747
  this.ringCount = 0;
748
+ this.eventStack = [];
727
749
  this.mode = options.mode;
728
750
  this.encoding = options.encoding;
729
751
  this.dimension = options.dimension || "xy";
@@ -755,7 +777,7 @@ var GeoArrowBuilder = class _GeoArrowBuilder {
755
777
  const depth = getBuilderDepth(this.encoding);
756
778
  const coordinates = geometry.coordinates;
757
779
  if (depth === 0) {
758
- this.writeCoordinate(coordinates);
780
+ this.writeCoordinateValues(coordinates);
759
781
  } else if (depth === 1) {
760
782
  this.writeCoordinateList(coordinates);
761
783
  this.writeOffset((_a = this.target) == null ? void 0 : _a.geometryOffsets, rowIndex + 1, this.coordinateCount);
@@ -865,16 +887,16 @@ var GeoArrowBuilder = class _GeoArrowBuilder {
865
887
  var _a;
866
888
  const depth = getBuilderDepth(this.encoding);
867
889
  if (depth === 0) {
868
- this.writeCoordinate(new Array(getGeoArrowDimensionSize(this.dimension)).fill(0));
890
+ this.writeCoordinateValues(new Array(getGeoArrowDimensionSize(this.dimension)).fill(0));
869
891
  } else {
870
892
  this.writeOffset((_a = this.target) == null ? void 0 : _a.geometryOffsets, rowIndex + 1, depth === 1 ? this.coordinateCount : this.partCount);
871
893
  }
872
894
  }
873
895
  writeCoordinateList(coordinates) {
874
896
  for (const coordinate of coordinates)
875
- this.writeCoordinate(coordinate);
897
+ this.writeCoordinateValues(coordinate);
876
898
  }
877
- writeCoordinate(coordinate) {
899
+ writeCoordinateValues(coordinate) {
878
900
  const size = getGeoArrowDimensionSize(this.dimension);
879
901
  if (coordinate.length !== size) {
880
902
  throw new Error(`Expected ${size} coordinate values for ${this.dimension}`);
@@ -883,6 +905,96 @@ var GeoArrowBuilder = class _GeoArrowBuilder {
883
905
  writeCoordinate(this.target.coordinates, this.coordinateCount, coordinate, this.dimension);
884
906
  this.coordinateCount++;
885
907
  }
908
+ /** Begins an event-driven geometry. Events are accepted by both measure and write builders. */
909
+ beginGeometry(type, dimension = this.dimension, _count) {
910
+ this.eventStack.push({ type, dimension, rings: [], polygons: [], children: [] });
911
+ return this;
912
+ }
913
+ /** Begins a polygon part inside an event-built MultiPolygon. */
914
+ beginPolygon() {
915
+ const state = this.eventStack[this.eventStack.length - 1];
916
+ if (!state || state.type !== "MultiPolygon") {
917
+ throw new Error("beginPolygon must follow beginGeometry('MultiPolygon')");
918
+ }
919
+ state.currentPolygon = [];
920
+ state.polygons.push(state.currentPolygon);
921
+ state.currentRing = void 0;
922
+ return this;
923
+ }
924
+ /** Begins a ring/part in the current event geometry. */
925
+ beginRing(_count) {
926
+ const state = this.eventStack[this.eventStack.length - 1];
927
+ if (!state)
928
+ throw new Error("beginRing must follow beginGeometry");
929
+ state.currentRing = [];
930
+ if (state.type === "MultiPolygon") {
931
+ if (!state.currentPolygon)
932
+ this.beginPolygon();
933
+ state.currentPolygon.push(state.currentRing);
934
+ } else {
935
+ state.rings.push(state.currentRing);
936
+ }
937
+ return this;
938
+ }
939
+ /** Writes one coordinate into the current event ring or point. */
940
+ writeCoordinate(x, y, z, m) {
941
+ const state = this.eventStack[this.eventStack.length - 1];
942
+ if (!state)
943
+ throw new Error("writeCoordinate must follow beginGeometry");
944
+ const coordinate = Array.isArray(x) ? [...x] : [x, y, ...z === void 0 ? [] : [z], ...m === void 0 ? [] : [m]];
945
+ const size = getGeoArrowDimensionSize(state.dimension);
946
+ if (coordinate.length !== size)
947
+ throw new Error(`Expected ${size} coordinate values for ${state.dimension}`);
948
+ if (state.type === "Point") {
949
+ state.rings = [[coordinate]];
950
+ return this;
951
+ }
952
+ if (!state.currentRing)
953
+ this.beginRing();
954
+ state.currentRing.push(coordinate);
955
+ return this;
956
+ }
957
+ /** Ends the current event geometry and appends it to the builder/parent. */
958
+ endGeometry() {
959
+ var _a;
960
+ const state = this.eventStack.pop();
961
+ if (!state)
962
+ throw new Error("endGeometry without beginGeometry");
963
+ const coordinates = state.rings;
964
+ let geometry;
965
+ switch (state.type) {
966
+ case "Point":
967
+ geometry = { type: "Point", coordinates: ((_a = coordinates[0]) == null ? void 0 : _a[0]) || [] };
968
+ break;
969
+ case "LineString":
970
+ geometry = { type: "LineString", coordinates: coordinates[0] || [] };
971
+ break;
972
+ case "MultiPoint":
973
+ geometry = { type: "MultiPoint", coordinates: coordinates.flat() };
974
+ break;
975
+ case "Polygon":
976
+ geometry = { type: "Polygon", coordinates };
977
+ break;
978
+ case "MultiLineString":
979
+ geometry = { type: "MultiLineString", coordinates };
980
+ break;
981
+ case "MultiPolygon":
982
+ geometry = {
983
+ type: "MultiPolygon",
984
+ coordinates: state.polygons.length ? state.polygons : [coordinates]
985
+ };
986
+ break;
987
+ case "GeometryCollection":
988
+ geometry = { type: "GeometryCollection", geometries: state.children };
989
+ break;
990
+ }
991
+ const parent = this.eventStack[this.eventStack.length - 1];
992
+ if ((parent == null ? void 0 : parent.type) === "GeometryCollection")
993
+ parent.children.push(geometry);
994
+ else
995
+ this.append(geometry);
996
+ return this;
997
+ }
886
998
  writeOffset(target, index, value) {
887
999
  if (!target)
888
1000
  return;
@@ -961,7 +1073,7 @@ function makeDenseUnionArray(rows, options, dimension) {
961
1073
  const geometry = rows[rowIndex];
962
1074
  const type = (geometry == null ? void 0 : geometry.type) || fallbackType;
963
1075
  const values = childRows.get(type) || [];
964
- typeIds[rowIndex] = getGeometryTypeId(type);
1076
+ typeIds[rowIndex] = getGeometryTypeId(type, dimension);
965
1077
  valueOffsets[rowIndex] = values.length;
966
1078
  if (geometry) {
967
1079
  values.push(geometry);
@@ -969,13 +1081,20 @@ function makeDenseUnionArray(rows, options, dimension) {
969
1081
  setValidityBit(validity, rowIndex);
970
1082
  }
971
1083
  }
972
- const children = [...childRows.entries()].sort(([left], [right]) => getGeometryTypeId(left) - getGeometryTypeId(right)).map(([type, values]) => {
1084
+ const children = [...childRows.entries()].sort(([left], [right]) => getGeometryTypeId(left, dimension) - getGeometryTypeId(right, dimension)).map(([type, values]) => {
973
1085
  const data = type === "GeometryCollection" ? makeGeometryCollectionArray(values, options, dimension) : GeoArrowBuilder.build(values, {
974
1086
  ...options,
975
1087
  dimension,
976
1088
  encoding: getGeoArrowEncodingForGeometry(type)
977
1089
  }).chunks[0];
978
- return { name: type, typeId: getGeometryTypeId(type), data };
1090
+ return {
1091
+ name: type,
1092
+ typeId: getGeometryTypeId(type, dimension),
1093
+ encoding: getGeoArrowEncodingForGeometry(type),
1094
+ dimension,
1095
+ coordinateLayout: options.coordinateLayout || "interleaved",
1096
+ data
1097
+ };
979
1098
  });
980
1099
  return {
981
1100
  kind: "dense-union",
@@ -1071,8 +1190,8 @@ function getBuilderDepth(encoding) {
1071
1190
  return 3;
1072
1191
  }
1073
1192
  }
1074
- function getGeometryTypeId(type) {
1075
- return [
1193
+ function getGeometryTypeId(type, dimension = "xy") {
1194
+ const family = [
1076
1195
  "Point",
1077
1196
  "LineString",
1078
1197
  "Polygon",
@@ -1080,7 +1199,9 @@ function getGeometryTypeId(type) {
1080
1199
  "MultiLineString",
1081
1200
  "MultiPolygon",
1082
1201
  "GeometryCollection"
1083
- ].indexOf(type) + 1;
1202
+ ].indexOf(type);
1203
+ const dimensionIndex = ["xy", "xyz", "xym", "xyzm"].indexOf(dimension);
1204
+ return family < 0 || dimensionIndex < 0 ? 1 : family * 4 + dimensionIndex + 1;
1084
1205
  }
1085
1206
  function inferRowsDimension(rows) {
1086
1207
  let size = 2;
@@ -1135,6 +1256,46 @@ function getGeoArrowBounds(column) {
1135
1256
  });
1136
1257
  return Number.isFinite(minimumX) ? [minimumX, minimumY, maximumX, maximumY] : null;
1137
1258
  }
1259
+ function getGeoArrowRowBounds(column) {
1260
+ const rowCount = getGeoArrowRowCount(column);
1261
+ if (column.encoding === "geoarrow.box") {
1262
+ const result = [];
1263
+ for (const chunk of column.chunks) {
1264
+ for (let rowIndex = 0; rowIndex < chunk.length; rowIndex++) {
1265
+ if (chunk.kind !== "struct" || !isGeoArrowValueValid(chunk.validity, rowIndex)) {
1266
+ result.push(null);
1267
+ continue;
1268
+ }
1269
+ const index = (chunk.offset || 0) + rowIndex;
1270
+ const xmin = readPrimitiveNumber(chunk.children["xmin"], index);
1271
+ const ymin = readPrimitiveNumber(chunk.children["ymin"], index);
1272
+ const xmax = readPrimitiveNumber(chunk.children["xmax"], index);
1273
+ const ymax = readPrimitiveNumber(chunk.children["ymax"], index);
1274
+ result.push([xmin, ymin, xmax, ymax].every(Number.isFinite) ? [xmin, ymin, xmax, ymax] : null);
1275
+ }
1276
+ }
1277
+ return result;
1278
+ }
1279
+ const minimumX = new Float64Array(rowCount).fill(Number.POSITIVE_INFINITY);
1280
+ const minimumY = new Float64Array(rowCount).fill(Number.POSITIVE_INFINITY);
1281
+ const maximumX = new Float64Array(rowCount).fill(Number.NEGATIVE_INFINITY);
1282
+ const maximumY = new Float64Array(rowCount).fill(Number.NEGATIVE_INFINITY);
1283
+ visitGeoArrowCoordinates(column, (coordinate, sourceRowIndex) => {
1284
+ const [x, y] = coordinate;
1285
+ if (Number.isFinite(x) && Number.isFinite(y)) {
1286
+ minimumX[sourceRowIndex] = Math.min(minimumX[sourceRowIndex], x);
1287
+ minimumY[sourceRowIndex] = Math.min(minimumY[sourceRowIndex], y);
1288
+ maximumX[sourceRowIndex] = Math.max(maximumX[sourceRowIndex], x);
1289
+ maximumY[sourceRowIndex] = Math.max(maximumY[sourceRowIndex], y);
1290
+ }
1291
+ return coordinate;
1292
+ });
1293
+ const bounds = [];
1294
+ for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) {
1295
+ bounds.push(Number.isFinite(minimumX[rowIndex]) ? [minimumX[rowIndex], minimumY[rowIndex], maximumX[rowIndex], maximumY[rowIndex]] : null);
1296
+ }
1297
+ return bounds;
1298
+ }
1138
1299
  function getGeoArrowBoxBounds(column) {
1139
1300
  let minimumX = Number.POSITIVE_INFINITY;
1140
1301
  let minimumY = Number.POSITIVE_INFINITY;
@@ -1175,20 +1336,20 @@ function readPrimitiveNumber(array, index) {
1175
1336
  const valueIndex = (array.offset || 0) + index * (array.stride || 1);
1176
1337
  return Number(array.values[valueIndex]);
1177
1338
  }
1178
- function mapGeoArrowCoordinates(column, mapper, options = {}) {
1339
+ function mapGeoArrowCoordinates(column, mapper, options = {}, sourceDimension) {
1179
1340
  assertGeoArrowResourceLimits(column, options.limits);
1180
- const rows = materializeGeoArrowRows(column);
1181
- let rowIndex = 0;
1182
- const mappedRows = rows.map((row) => {
1183
- const mapped = row ? mapGeometryCoordinates(row, (coordinate) => mapper(coordinate, rowIndex)) : null;
1184
- rowIndex++;
1341
+ if (column.encoding === "geoarrow.wkb" || column.encoding === "geoarrow.wkt") {
1342
+ throw new Error("Coordinate mapping requires native GeoArrow storage");
1343
+ }
1344
+ const dimension = options.dimension || column.dimension;
1345
+ const layout = options.coordinateLayout || column.coordinateLayout || "interleaved";
1346
+ let rowOffset = 0;
1347
+ const chunks = column.chunks.map((chunk) => {
1348
+ const mapped = mapArrayCoordinates(chunk, column.encoding, rowOffset, dimension, layout, mapper, options.coordinateType || "preserve", options.dimension !== void 0, options.coordinateLayout !== void 0, sourceDimension);
1349
+ rowOffset += chunk.length;
1185
1350
  return mapped;
1186
1351
  });
1187
- const result = makeGeoArrowColumnFromGeometryRows(mappedRows, {
1188
- dimension: options.dimension || column.dimension,
1189
- coordinateLayout: options.coordinateLayout || column.coordinateLayout || "interleaved"
1190
- });
1191
- return copyColumnMetadata(column, result);
1352
+ return { ...column, dimension, coordinateLayout: layout, chunks };
1192
1353
  }
1193
1354
  function mapGeoArrowCoordinatesInto(target, source, mapper, options = {}) {
1194
1355
  const mapped = mapGeoArrowCoordinates(source, mapper, {
@@ -1233,45 +1394,56 @@ function normalizeGeoArrowUnion(column) {
1233
1394
  return changed ? { ...column, chunks } : column;
1234
1395
  }
1235
1396
  function convertGeoArrowColumn(column, options = {}) {
1236
- const encoding = options.encoding || column.encoding;
1397
+ const encoding = options.encoding === "native" || !options.encoding ? column.encoding : options.encoding;
1237
1398
  const dimension = options.dimension || column.dimension;
1238
- const coordinateLayout = options.coordinateLayout || column.coordinateLayout;
1239
- if (encoding === column.encoding && dimension === column.dimension && coordinateLayout === column.coordinateLayout) {
1399
+ const coordinateLayout = options.coordinateLayout === "preserve" || !options.coordinateLayout ? column.coordinateLayout : options.coordinateLayout;
1400
+ const requestedOffsetType = options.offsetType || "preserve";
1401
+ const currentOffsetType = getColumnOffsetType(column);
1402
+ if (encoding === column.encoding && dimension === column.dimension && coordinateLayout === column.coordinateLayout && (requestedOffsetType === "preserve" || requestedOffsetType === currentOffsetType) && (!options.coordinateType || options.coordinateType === "preserve")) {
1240
1403
  return column;
1241
1404
  }
1242
1405
  if (encoding === "geoarrow.wkb" || encoding === "geoarrow.wkt") {
1243
1406
  throw new Error("Use encodeGeoArrowWKB or encodeGeoArrowWKT for serialized output");
1244
1407
  }
1245
1408
  assertGeoArrowResourceLimits(column, options.limits);
1246
- const rows = materializeGeoArrowRows(column).map((row) => row ? convertGeometryFamily(row, encoding, column.dimension, dimension) : null);
1247
- const result = makeGeoArrowColumnFromGeometryRows(rows, {
1248
- encoding: encoding === "geoarrow.geometry" ? encoding : void 0,
1409
+ const mapped = mapGeoArrowCoordinates(column, (coordinate) => coordinate, {
1249
1410
  dimension,
1250
- coordinateLayout: coordinateLayout || "interleaved"
1411
+ coordinateLayout: coordinateLayout || void 0,
1412
+ coordinateType: options.coordinateType,
1413
+ limits: options.limits
1414
+ }, column.dimension);
1415
+ const offsetConverted = requestedOffsetType === "preserve" ? mapped : {
1416
+ ...mapped,
1417
+ chunks: mapped.chunks.map((chunk) => convertArrayOffsets(chunk, requestedOffsetType))
1418
+ };
1419
+ if (encoding === column.encoding)
1420
+ return offsetConverted;
1421
+ if (encoding === "geoarrow.geometry")
1422
+ return promoteToDenseUnion(offsetConverted);
1423
+ if (column.encoding === "geoarrow.geometry")
1424
+ return demoteDenseUnion(offsetConverted, encoding);
1425
+ throw new Error(`Rows cannot be represented as ${encoding}`);
1426
+ }
1427
+ function resizeCoordinate(coordinate, sourceDimension, targetDimension) {
1428
+ const sourceNames = getDimensionNames(sourceDimension);
1429
+ return getDimensionNames(targetDimension).map((name) => {
1430
+ const sourceIndex = sourceNames.indexOf(name);
1431
+ return sourceIndex >= 0 ? coordinate[sourceIndex] ?? 0 : 0;
1251
1432
  });
1252
- if (result.encoding !== encoding && encoding !== "geoarrow.geometry") {
1253
- throw new Error(`Rows cannot be represented as ${encoding}`);
1254
- }
1255
- return copyColumnMetadata(column, result);
1256
1433
  }
1257
1434
  function rewindGeoArrow(column, options = {}) {
1258
1435
  assertGeoArrowResourceLimits(column, options.limits);
1436
+ if (column.encoding === "geoarrow.wkb" || column.encoding === "geoarrow.wkt") {
1437
+ throw new Error("Rewinding requires native GeoArrow storage");
1438
+ }
1259
1439
  const outerClockwise = (options.outer || "counter-clockwise") === "clockwise";
1260
1440
  let changed = false;
1261
- const rows = materializeGeoArrowRows(column).map((row) => {
1262
- if (!row)
1263
- return null;
1264
- const rewound = rewindGeometry(row, outerClockwise);
1265
- changed || (changed = rewound !== row);
1266
- return rewound;
1267
- });
1268
- if (!changed)
1269
- return column;
1270
- const result = makeGeoArrowColumnFromGeometryRows(rows, {
1271
- dimension: column.dimension,
1272
- coordinateLayout: column.coordinateLayout || "interleaved"
1441
+ const chunks = column.chunks.map((chunk) => {
1442
+ const clone = mapArrayCoordinates(chunk, column.encoding, 0, column.dimension, column.coordinateLayout || "interleaved", (coordinate) => coordinate, "preserve");
1443
+ changed || (changed = rewindArrayRings(clone, column.encoding, outerClockwise));
1444
+ return clone;
1273
1445
  });
1274
- return copyColumnMetadata(column, result);
1446
+ return changed ? { ...column, chunks } : column;
1275
1447
  }
1276
1448
  function assertGeoArrowResourceLimits(column, options = {}) {
1277
1449
  const rows = getGeoArrowRowCount(column);
@@ -1298,86 +1470,210 @@ function assertGeoArrowResourceLimits(column, options = {}) {
1298
1470
  }
1299
1471
  }
1300
1472
  }
1301
- function mapGeometryCoordinates(geometry, mapper) {
1302
- if (geometry.type === "GeometryCollection") {
1473
+ function mapArrayCoordinates(array, encoding, rowOffset, targetDimension, targetLayout, mapper, coordinateType, forceDimension = false, forceLayout = false, sourceDimension, rowIndicesOverride) {
1474
+ if (encoding === "geoarrow.geometry" && array.kind === "dense-union") {
1475
+ const childRowIndices = collectUnionRowIndices(array, rowOffset);
1303
1476
  return {
1304
- ...geometry,
1305
- geometries: geometry.geometries.map((child) => mapGeometryCoordinates(child, mapper))
1477
+ ...array,
1478
+ children: array.children.map((child, childIndex) => {
1479
+ const childEncoding = child.encoding || getEncodingFromChildName2(child.name);
1480
+ const childDimension = forceDimension ? targetDimension : child.dimension || targetDimension;
1481
+ const childLayout = forceLayout ? targetLayout : child.coordinateLayout || targetLayout;
1482
+ return {
1483
+ ...child,
1484
+ data: mapArrayCoordinates(child.data, childEncoding, rowOffset, childDimension, childLayout, mapper, coordinateType, forceDimension, forceLayout, childDimension, childRowIndices[childIndex]),
1485
+ dimension: childDimension,
1486
+ coordinateLayout: childLayout,
1487
+ encoding: childEncoding
1488
+ };
1489
+ })
1306
1490
  };
1307
1491
  }
1492
+ if (encoding === "geoarrow.geometrycollection" && array.kind === "list") {
1493
+ return {
1494
+ ...array,
1495
+ child: mapArrayCoordinates(array.child, "geoarrow.geometry", rowOffset, targetDimension, targetLayout, mapper, coordinateType, forceDimension, forceLayout, sourceDimension, rowIndicesOverride)
1496
+ };
1497
+ }
1498
+ const depth = getEncodingDepth2(encoding);
1499
+ const rowIndices = rowIndicesOverride || collectLeafRowIndices(array, depth, rowOffset);
1500
+ return mapNestedArray(array, depth, rowOffset, targetDimension, targetLayout, mapper, coordinateType, rowIndices, sourceDimension);
1501
+ }
1502
+ function mapNestedArray(array, depth, rowOffset, targetDimension, targetLayout, mapper, coordinateType, rowIndices, sourceDimension) {
1503
+ if (depth === 0) {
1504
+ return mapCoordinateLeaf(array, rowOffset, targetDimension, targetLayout, mapper, coordinateType, rowIndices, sourceDimension);
1505
+ }
1506
+ if (array.kind !== "list")
1507
+ return array;
1308
1508
  return {
1309
- ...geometry,
1310
- coordinates: mapCoordinateNesting(geometry.coordinates, mapper)
1509
+ ...array,
1510
+ child: mapNestedArray(array.child, depth - 1, rowOffset, targetDimension, targetLayout, mapper, coordinateType, rowIndices, sourceDimension)
1311
1511
  };
1312
1512
  }
1313
- function mapCoordinateNesting(value, mapper) {
1314
- if (value.length === 0)
1315
- return [];
1316
- if (typeof value[0] === "number")
1317
- return [...mapper(value)];
1318
- return value.map((child) => mapCoordinateNesting(child, mapper));
1513
+ function mapCoordinateLeaf(array, rowOffset, targetDimension, targetLayout, mapper, coordinateType, rowIndices, sourceDimension) {
1514
+ const count = array.length;
1515
+ const targetSize = getGeoArrowDimensionSize(targetDimension);
1516
+ const sourceValues = new Array(count);
1517
+ for (let index = 0; index < count; index++) {
1518
+ const coordinate = readCoordinateAt(array, index);
1519
+ const mapped = coordinate ? mapper(sourceDimension ? resizeCoordinate(coordinate, sourceDimension, targetDimension) : coordinate, (rowIndices == null ? void 0 : rowIndices[index]) ?? rowOffset) : new Array(targetSize).fill(0);
1520
+ if (mapped.length !== targetSize) {
1521
+ throw new Error(`Coordinate mapper returned ${mapped.length} values; expected ${targetSize}`);
1522
+ }
1523
+ sourceValues[index] = [...mapped];
1524
+ }
1525
+ const FloatArray = resolveCoordinateConstructor(array, coordinateType);
1526
+ return makeCoordinateLeaf(sourceValues, targetDimension, targetLayout, FloatArray, array.validity);
1319
1527
  }
1320
- function rewindGeometry(geometry, outerClockwise) {
1321
- if (geometry.type === "GeometryCollection") {
1322
- const geometries = geometry.geometries.map((child) => rewindGeometry(child, outerClockwise));
1323
- return geometries.some((child, index) => child !== geometry.geometries[index]) ? { ...geometry, geometries } : geometry;
1528
+ function collectLeafRowIndices(array, depth, rowOffset) {
1529
+ const output = new Array(getLeafLength(array, depth)).fill(rowOffset);
1530
+ for (let rowIndex = 0; rowIndex < array.length; rowIndex++) {
1531
+ if (isGeoArrowValueValid(array.validity, rowIndex)) {
1532
+ assignNestedRowIndices(array, rowIndex, depth, rowOffset + rowIndex, output);
1533
+ }
1324
1534
  }
1325
- if (geometry.type === "Polygon") {
1326
- const coordinates = rewindPolygon(geometry.coordinates, outerClockwise);
1327
- return coordinates === geometry.coordinates ? geometry : { ...geometry, coordinates };
1535
+ return output;
1536
+ }
1537
+ function assignNestedRowIndices(array, index, depth, rowIndex, output) {
1538
+ if (depth === 0) {
1539
+ if (index >= 0 && index < output.length)
1540
+ output[index] = rowIndex;
1541
+ return;
1328
1542
  }
1329
- if (geometry.type === "MultiPolygon") {
1330
- const coordinates = geometry.coordinates.map((polygon) => rewindPolygon(polygon, outerClockwise));
1331
- return coordinates.every((polygon, index) => polygon === geometry.coordinates[index]) ? geometry : { ...geometry, coordinates };
1543
+ if (array.kind !== "list")
1544
+ return;
1545
+ const [first, last] = getListRange(array, index);
1546
+ for (let childIndex = first; childIndex < last; childIndex++) {
1547
+ assignNestedRowIndices(array.child, childIndex, depth - 1, rowIndex, output);
1332
1548
  }
1333
- return geometry;
1334
1549
  }
1335
- function rewindPolygon(rings, outerClockwise) {
1336
- let changed = false;
1337
- const result = rings.map((ring, ringIndex) => {
1338
- const shouldBeClockwise = ringIndex === 0 ? outerClockwise : !outerClockwise;
1339
- const isClockwise = getRingSignedArea(ring) < 0;
1340
- if (isClockwise === shouldBeClockwise)
1341
- return ring;
1342
- changed = true;
1343
- return [...ring].reverse();
1550
+ function collectUnionRowIndices(union, rowOffset) {
1551
+ if (union.kind !== "dense-union")
1552
+ return [];
1553
+ const output = union.children.map((child) => {
1554
+ const encoding = child.encoding || getEncodingFromChildName2(child.name);
1555
+ return new Array(getLeafLength(child.data, getEncodingDepth2(encoding))).fill(rowOffset);
1344
1556
  });
1345
- return changed ? result : rings;
1557
+ for (let rowIndex = 0; rowIndex < union.length; rowIndex++) {
1558
+ if (!isGeoArrowValueValid(union.validity, rowIndex))
1559
+ continue;
1560
+ const physical = (union.offset || 0) + rowIndex;
1561
+ const childIndex = union.children.findIndex((child2) => child2.typeId === union.typeIds[physical]);
1562
+ if (childIndex < 0)
1563
+ continue;
1564
+ const child = union.children[childIndex];
1565
+ assignUnionRowIndices(child.data, child.encoding || getEncodingFromChildName2(child.name), union.valueOffsets[physical], rowOffset + rowIndex, output[childIndex]);
1566
+ }
1567
+ return output;
1568
+ }
1569
+ function assignUnionRowIndices(array, encoding, index, rowIndex, output) {
1570
+ if (encoding === "geoarrow.geometry" && array.kind === "dense-union") {
1571
+ const physical = (array.offset || 0) + index;
1572
+ const childIndex = array.children.findIndex((child) => child.typeId === array.typeIds[physical]);
1573
+ if (childIndex >= 0) {
1574
+ const child = array.children[childIndex];
1575
+ assignUnionRowIndices(child.data, child.encoding || getEncodingFromChildName2(child.name), array.valueOffsets[physical], rowIndex, output);
1576
+ }
1577
+ return;
1578
+ }
1579
+ if (encoding === "geoarrow.geometrycollection" && array.kind === "list") {
1580
+ const [first, last] = getListRange(array, index);
1581
+ if (array.child.kind === "dense-union") {
1582
+ for (let childIndex = first; childIndex < last; childIndex++) {
1583
+ assignUnionRowIndices(array.child, "geoarrow.geometry", childIndex, rowIndex, output);
1584
+ }
1585
+ }
1586
+ return;
1587
+ }
1588
+ assignNestedRowIndices(array, index, getEncodingDepth2(encoding), rowIndex, output);
1346
1589
  }
1347
- function getRingSignedArea(ring) {
1348
- let area = 0;
1349
- for (let index = 0; index < ring.length; index++) {
1350
- const current = ring[index];
1351
- const next = ring[(index + 1) % ring.length];
1352
- area += current[0] * next[1] - next[0] * current[1];
1590
+ function getLeafLength(array, depth) {
1591
+ let current = array;
1592
+ for (let level = 0; level < depth; level++) {
1593
+ if (current.kind !== "list")
1594
+ return 0;
1595
+ current = current.child;
1353
1596
  }
1354
- return area / 2;
1597
+ return current.length;
1355
1598
  }
1356
- function convertGeometryFamily(geometry, encoding, sourceDimension, targetDimension) {
1357
- if (geometry.type === "GeometryCollection") {
1358
- const geometries = geometry.geometries.map((child) => convertGeometryFamily(child, "geoarrow.geometry", sourceDimension, targetDimension));
1359
- if (encoding === "geoarrow.geometry" || encoding === "geoarrow.geometrycollection") {
1360
- return { ...geometry, geometries };
1599
+ function resolveCoordinateConstructor(array, coordinateType) {
1600
+ if (coordinateType === "float32")
1601
+ return Float32Array;
1602
+ if (coordinateType === "float64")
1603
+ return Float64Array;
1604
+ if (array.kind === "fixed-size-list" && array.child.kind === "primitive") {
1605
+ return array.child.values instanceof Float32Array ? Float32Array : Float64Array;
1606
+ }
1607
+ if (array.kind === "struct") {
1608
+ const child = array.children["x"];
1609
+ if ((child == null ? void 0 : child.kind) === "primitive" && child.values instanceof Float32Array)
1610
+ return Float32Array;
1611
+ }
1612
+ return Float64Array;
1613
+ }
1614
+ function makeCoordinateLeaf(coordinates, dimension, layout, FloatArray, validity) {
1615
+ const size = getGeoArrowDimensionSize(dimension);
1616
+ if (layout === "interleaved") {
1617
+ const values = new FloatArray(coordinates.length * size);
1618
+ for (let index = 0; index < coordinates.length; index++) {
1619
+ values.set(coordinates[index], index * size);
1361
1620
  }
1362
- throw new Error(`${geometry.type} cannot be represented as ${encoding}`);
1621
+ return {
1622
+ kind: "fixed-size-list",
1623
+ length: coordinates.length,
1624
+ size,
1625
+ child: { kind: "primitive", length: values.length, values },
1626
+ validity
1627
+ };
1363
1628
  }
1364
- const coordinates = mapCoordinateNesting(geometry.coordinates, (coordinate) => resizeCoordinate(coordinate, sourceDimension, targetDimension));
1365
- if (encoding === "geoarrow.geometry") {
1366
- return { ...geometry, coordinates };
1629
+ const children = {
1630
+ x: { kind: "primitive", length: coordinates.length, values: new FloatArray(coordinates.length) },
1631
+ y: { kind: "primitive", length: coordinates.length, values: new FloatArray(coordinates.length) }
1632
+ };
1633
+ const names = getDimensionNames(dimension);
1634
+ for (let component = 2; component < names.length; component++) {
1635
+ children[names[component]] = {
1636
+ kind: "primitive",
1637
+ length: coordinates.length,
1638
+ values: new FloatArray(coordinates.length)
1639
+ };
1367
1640
  }
1368
- const target = encoding.replace("geoarrow.", "");
1369
- if (geometry.type.toLowerCase() !== target) {
1370
- throw new Error(`${geometry.type} cannot be represented as ${encoding}`);
1641
+ for (let index = 0; index < coordinates.length; index++) {
1642
+ const coordinate = coordinates[index];
1643
+ for (let component = 0; component < names.length; component++) {
1644
+ const child = children[names[component]];
1645
+ if (child.kind === "primitive")
1646
+ child.values[index] = coordinate[component];
1647
+ }
1371
1648
  }
1372
- return { ...geometry, coordinates };
1649
+ return { kind: "struct", length: coordinates.length, children, validity };
1373
1650
  }
1374
- function resizeCoordinate(coordinate, sourceDimension, targetDimension) {
1375
- const sourceNames = getDimensionNames(sourceDimension);
1376
- const targetNames = getDimensionNames(targetDimension);
1377
- return targetNames.map((name) => {
1378
- const sourceIndex = sourceNames.indexOf(name);
1379
- return sourceIndex >= 0 ? coordinate[sourceIndex] ?? 0 : 0;
1380
- });
1651
+ function readCoordinateAt(array, index) {
1652
+ if (!isGeoArrowValueValid(array.validity, index))
1653
+ return null;
1654
+ if (array.kind === "fixed-size-list") {
1655
+ const logicalIndex = (array.offset || 0) + index;
1656
+ if (array.child.kind !== "primitive")
1657
+ return null;
1658
+ const values = [];
1659
+ for (let component = 0; component < array.size; component++) {
1660
+ const scalarIndex = logicalIndex * array.size + component;
1661
+ values.push(Number(array.child.values[(array.child.offset || 0) + scalarIndex * (array.child.stride || 1)]));
1662
+ }
1663
+ return values;
1664
+ }
1665
+ if (array.kind === "struct") {
1666
+ const logicalIndex = (array.offset || 0) + index;
1667
+ const values = [];
1668
+ for (const name of ["x", "y", "z", "m"]) {
1669
+ const child = array.children[name];
1670
+ if ((child == null ? void 0 : child.kind) === "primitive") {
1671
+ values.push(Number(child.values[(child.offset || 0) + logicalIndex * (child.stride || 1)]));
1672
+ }
1673
+ }
1674
+ return values.length >= 2 ? values : null;
1675
+ }
1676
+ return null;
1381
1677
  }
1382
1678
  function getDimensionNames(dimension) {
1383
1679
  switch (dimension) {
@@ -1391,14 +1687,321 @@ function getDimensionNames(dimension) {
1391
1687
  return ["x", "y", "z", "m"];
1392
1688
  }
1393
1689
  }
1394
- function copyColumnMetadata(source, target) {
1690
+ function getEncodingFromChildName2(name) {
1691
+ const normalized = name.replace(/[^a-z]/gi, "").toLowerCase();
1692
+ const encoding = `geoarrow.${normalized}`;
1693
+ if (encoding === "geoarrow.point" || encoding === "geoarrow.linestring" || encoding === "geoarrow.polygon" || encoding === "geoarrow.multipoint" || encoding === "geoarrow.multilinestring" || encoding === "geoarrow.multipolygon" || encoding === "geoarrow.geometrycollection") {
1694
+ return encoding;
1695
+ }
1696
+ throw new Error(`Unknown GeoArrow dense-union child ${name}`);
1697
+ }
1698
+ function getEncodingDepth2(encoding) {
1699
+ switch (encoding) {
1700
+ case "geoarrow.point":
1701
+ return 0;
1702
+ case "geoarrow.linestring":
1703
+ case "geoarrow.multipoint":
1704
+ return 1;
1705
+ case "geoarrow.polygon":
1706
+ case "geoarrow.multilinestring":
1707
+ return 2;
1708
+ case "geoarrow.multipolygon":
1709
+ return 3;
1710
+ default:
1711
+ return 0;
1712
+ }
1713
+ }
1714
+ function promoteToDenseUnion(column) {
1715
+ if (column.encoding === "geoarrow.geometry")
1716
+ return column;
1717
+ const chunks = column.chunks.map((chunk) => {
1718
+ const typeId = getCanonicalTypeId(column.encoding, column.dimension);
1719
+ const typeIds = new Int8Array(chunk.length);
1720
+ typeIds.fill(typeId);
1721
+ const valueOffsets = new Int32Array(chunk.length);
1722
+ for (let index = 0; index < chunk.length; index++)
1723
+ valueOffsets[index] = index;
1724
+ return {
1725
+ kind: "dense-union",
1726
+ length: chunk.length,
1727
+ typeIds,
1728
+ valueOffsets,
1729
+ validity: chunk.validity,
1730
+ children: [
1731
+ {
1732
+ name: getGeoArrowGeometryTypeName(column.encoding),
1733
+ typeId,
1734
+ encoding: column.encoding,
1735
+ dimension: column.dimension,
1736
+ coordinateLayout: column.coordinateLayout,
1737
+ data: chunk
1738
+ }
1739
+ ]
1740
+ };
1741
+ });
1742
+ return { ...column, encoding: "geoarrow.geometry", chunks };
1743
+ }
1744
+ function demoteDenseUnion(column, encoding) {
1745
+ if (encoding === "geoarrow.geometry" || encoding === "geoarrow.geometrycollection")
1746
+ return column;
1747
+ const rows = [];
1748
+ for (const chunk of column.chunks) {
1749
+ if (chunk.kind !== "dense-union")
1750
+ throw new Error("Expected dense-union storage");
1751
+ for (let index = 0; index < chunk.length; index++) {
1752
+ if (!isGeoArrowValueValid(chunk.validity, index)) {
1753
+ rows.push(null);
1754
+ continue;
1755
+ }
1756
+ const physical = (chunk.offset || 0) + index;
1757
+ const child = chunk.children.find((candidate) => candidate.typeId === chunk.typeIds[physical]);
1758
+ const childEncoding = child && (child.encoding || getEncodingFromChildName2(child.name));
1759
+ if (!child || childEncoding !== encoding) {
1760
+ throw new Error(`Rows cannot be represented as ${encoding}`);
1761
+ }
1762
+ const geometry = materializeGeometryRow(child.data, chunk.valueOffsets[physical], childEncoding);
1763
+ rows.push(geometry ? resizeGeometryValue(geometry, child.dimension || column.dimension, column.dimension) : null);
1764
+ }
1765
+ }
1766
+ const built = makeGeoArrowColumnFromGeometryRows(rows, {
1767
+ dimension: column.dimension,
1768
+ coordinateLayout: column.coordinateLayout || "interleaved",
1769
+ offsetType: getColumnOffsetType(column)
1770
+ });
1771
+ return {
1772
+ ...built,
1773
+ encoding,
1774
+ spatialReference: column.spatialReference,
1775
+ edges: column.edges,
1776
+ metadata: column.metadata
1777
+ };
1778
+ }
1779
+ function resizeGeometryValue(geometry, sourceDimension, targetDimension) {
1780
+ const map = (value) => {
1781
+ if (Array.isArray(value) && (value.length === 0 || typeof value[0] === "number")) {
1782
+ return resizeCoordinate(value, sourceDimension, targetDimension);
1783
+ }
1784
+ return Array.isArray(value) ? value.map(map) : value;
1785
+ };
1786
+ if (geometry.type === "GeometryCollection") {
1787
+ return {
1788
+ type: geometry.type,
1789
+ geometries: geometry.geometries.map((child) => resizeGeometryValue(child, sourceDimension, targetDimension))
1790
+ };
1791
+ }
1395
1792
  return {
1396
- ...target,
1397
- spatialReference: source.spatialReference,
1398
- edges: source.edges,
1399
- metadata: source.metadata
1793
+ ...geometry,
1794
+ coordinates: map(geometry.coordinates)
1400
1795
  };
1401
1796
  }
1797
+ function getCanonicalTypeId(encoding, dimension) {
1798
+ const families = [
1799
+ "point",
1800
+ "linestring",
1801
+ "polygon",
1802
+ "multipoint",
1803
+ "multilinestring",
1804
+ "multipolygon",
1805
+ "geometrycollection"
1806
+ ];
1807
+ const dimensions = ["xy", "xyz", "xym", "xyzm"];
1808
+ const family = encoding.replace("geoarrow.", "");
1809
+ const familyIndex = families.indexOf(family);
1810
+ const dimensionIndex = dimensions.indexOf(dimension);
1811
+ return familyIndex < 0 || dimensionIndex < 0 ? 1 : familyIndex * 4 + dimensionIndex + 1;
1812
+ }
1813
+ function getColumnOffsetType(column) {
1814
+ for (const chunk of column.chunks) {
1815
+ const found = findOffsetType(chunk);
1816
+ if (found)
1817
+ return found;
1818
+ }
1819
+ return "int32";
1820
+ }
1821
+ function findOffsetType(array) {
1822
+ switch (array.kind) {
1823
+ case "list":
1824
+ case "serialized":
1825
+ return array.offsets instanceof BigInt64Array ? "int64" : "int32";
1826
+ case "fixed-size-list":
1827
+ return findOffsetType(array.child);
1828
+ case "struct": {
1829
+ for (const child of Object.values(array.children)) {
1830
+ const found = findOffsetType(child);
1831
+ if (found)
1832
+ return found;
1833
+ }
1834
+ return null;
1835
+ }
1836
+ case "dense-union":
1837
+ for (const child of array.children) {
1838
+ const found = findOffsetType(child.data);
1839
+ if (found)
1840
+ return found;
1841
+ }
1842
+ return null;
1843
+ default:
1844
+ return null;
1845
+ }
1846
+ }
1847
+ function convertArrayOffsets(array, offsetType) {
1848
+ const toOffsets = (offsets, offsetBase) => {
1849
+ if (offsetType === "int64") {
1850
+ const result2 = new BigInt64Array(offsets.length);
1851
+ for (let index = 0; index < offsets.length; index++)
1852
+ result2[index] = BigInt(offsets[index]);
1853
+ return { offsets: result2, ...offsetBase === void 0 ? {} : { offsetBase } };
1854
+ }
1855
+ const result = new Int32Array(offsets.length);
1856
+ const base = BigInt(offsetBase ?? 0);
1857
+ for (let index = 0; index < offsets.length; index++) {
1858
+ const value = BigInt(offsets[index]) - base;
1859
+ if (value < -2147483648n || value > 2147483647n) {
1860
+ throw new Error("GeoArrow offset cannot be represented as Int32");
1861
+ }
1862
+ result[index] = Number(value);
1863
+ }
1864
+ return { offsets: result, offsetBase: 0 };
1865
+ };
1866
+ switch (array.kind) {
1867
+ case "list":
1868
+ if (array.offsets instanceof (offsetType === "int64" ? BigInt64Array : Int32Array)) {
1869
+ return { ...array, child: convertArrayOffsets(array.child, offsetType) };
1870
+ }
1871
+ return {
1872
+ ...array,
1873
+ ...toOffsets(array.offsets, array.offsetBase),
1874
+ child: convertArrayOffsets(array.child, offsetType)
1875
+ };
1876
+ case "serialized":
1877
+ if (array.offsets instanceof (offsetType === "int64" ? BigInt64Array : Int32Array))
1878
+ return array;
1879
+ return { ...array, ...toOffsets(array.offsets, array.offsetBase) };
1880
+ case "fixed-size-list":
1881
+ return { ...array, child: convertArrayOffsets(array.child, offsetType) };
1882
+ case "struct":
1883
+ return {
1884
+ ...array,
1885
+ children: Object.fromEntries(Object.entries(array.children).map(([name, child]) => [
1886
+ name,
1887
+ convertArrayOffsets(child, offsetType)
1888
+ ]))
1889
+ };
1890
+ case "dense-union":
1891
+ return {
1892
+ ...array,
1893
+ children: array.children.map((child) => ({
1894
+ ...child,
1895
+ data: convertArrayOffsets(child.data, offsetType)
1896
+ }))
1897
+ };
1898
+ default:
1899
+ return array;
1900
+ }
1901
+ }
1902
+ function getGeoArrowGeometryTypeName(encoding) {
1903
+ return encoding.replace("geoarrow.", "").replace(/^./, (character) => character.toUpperCase());
1904
+ }
1905
+ function rewindArrayRings(array, encoding, outerClockwise) {
1906
+ if (encoding === "geoarrow.geometry" && array.kind === "dense-union") {
1907
+ let changed2 = false;
1908
+ for (const child of array.children) {
1909
+ if (rewindArrayRings(child.data, child.encoding || getEncodingFromChildName2(child.name), outerClockwise))
1910
+ changed2 = true;
1911
+ }
1912
+ return changed2;
1913
+ }
1914
+ if (encoding === "geoarrow.geometrycollection" && array.kind === "list") {
1915
+ if (array.child.kind !== "dense-union")
1916
+ return false;
1917
+ let changed2 = false;
1918
+ for (const child of array.child.children) {
1919
+ if (rewindArrayRings(child.data, child.encoding || getEncodingFromChildName2(child.name), outerClockwise))
1920
+ changed2 = true;
1921
+ }
1922
+ return changed2;
1923
+ }
1924
+ if (encoding !== "geoarrow.polygon" && encoding !== "geoarrow.multipolygon")
1925
+ return false;
1926
+ if (array.kind !== "list" || array.child.kind !== "list")
1927
+ return false;
1928
+ let changed = false;
1929
+ for (let rowIndex = 0; rowIndex < array.length; rowIndex++) {
1930
+ if (!isGeoArrowValueValid(array.validity, rowIndex))
1931
+ continue;
1932
+ const [, rowEnd] = getListRange(array, rowIndex);
1933
+ const rowStart = getListRange(array, rowIndex)[0];
1934
+ if (encoding === "geoarrow.polygon") {
1935
+ const rings = array.child;
1936
+ if (rewindRingGroup(rings, rowStart, rowEnd, outerClockwise))
1937
+ changed = true;
1938
+ } else {
1939
+ const polygons = array.child;
1940
+ for (let polygonIndex = rowStart; polygonIndex < rowEnd; polygonIndex++) {
1941
+ const [ringStart, ringEnd] = getListRange(polygons, polygonIndex);
1942
+ if (rewindRingGroup(polygons.child, ringStart, ringEnd, outerClockwise))
1943
+ changed = true;
1944
+ }
1945
+ }
1946
+ }
1947
+ return changed;
1948
+ }
1949
+ function rewindRingGroup(rings, firstRing, lastRing, outerClockwise) {
1950
+ if (rings.kind !== "list")
1951
+ return false;
1952
+ const leaf = rings.child;
1953
+ if (leaf.kind !== "fixed-size-list" && leaf.kind !== "struct")
1954
+ return false;
1955
+ let changed = false;
1956
+ for (let ringIndex = firstRing; ringIndex < lastRing; ringIndex++) {
1957
+ const [first, last] = getListRange(rings, ringIndex);
1958
+ const area = getRingArea(leaf, first, last);
1959
+ if (!Number.isFinite(area) || area === 0)
1960
+ continue;
1961
+ const shouldClockwise = ringIndex === firstRing ? outerClockwise : !outerClockwise;
1962
+ if (area < 0 !== shouldClockwise) {
1963
+ reverseCoordinateRange(leaf, first, last);
1964
+ changed = true;
1965
+ }
1966
+ }
1967
+ return changed;
1968
+ }
1969
+ function getRingArea(leaf, first, last) {
1970
+ let area = 0;
1971
+ for (let index = first; index < last; index++) {
1972
+ const current = readCoordinateAt(leaf, index);
1973
+ const next = readCoordinateAt(leaf, index + 1 < last ? index + 1 : first);
1974
+ if (!current || !next)
1975
+ return Number.NaN;
1976
+ area += current[0] * next[1] - next[0] * current[1];
1977
+ }
1978
+ return area / 2;
1979
+ }
1980
+ function reverseCoordinateRange(leaf, first, last) {
1981
+ const values = [];
1982
+ for (let index = first; index < last; index++)
1983
+ values.push(readCoordinateAt(leaf, index));
1984
+ values.reverse();
1985
+ for (let index = first; index < last; index++)
1986
+ writeCoordinateAt(leaf, index, values[index - first]);
1987
+ }
1988
+ function writeCoordinateAt(array, index, coordinate) {
1989
+ if (array.kind === "fixed-size-list" && array.child.kind === "primitive") {
1990
+ const logical = (array.offset || 0) + index;
1991
+ for (let component = 0; component < array.size; component++) {
1992
+ const scalar = (array.child.offset || 0) + logical * array.size + component;
1993
+ array.child.values[scalar] = coordinate[component];
1994
+ }
1995
+ } else if (array.kind === "struct") {
1996
+ const logical = (array.offset || 0) + index;
1997
+ const names = ["x", "y", "z", "m"];
1998
+ for (let component = 0; component < names.length; component++) {
1999
+ const child = array.children[names[component]];
2000
+ if ((child == null ? void 0 : child.kind) === "primitive")
2001
+ child.values[(child.offset || 0) + logical * (child.stride || 1)] = coordinate[component];
2002
+ }
2003
+ }
2004
+ }
1402
2005
  function collectCoordinateLeaves(column) {
1403
2006
  const leaves = [];
1404
2007
  for (const chunk of column.chunks)
@@ -1447,6 +2050,7 @@ function getArrayDepth(array) {
1447
2050
 
1448
2051
  // dist/codecs.js
1449
2052
  var import_wkb = require("@math.gl/wkb");
2053
+ var import_wkb2 = require("@math.gl/wkb");
1450
2054
  var textEncoder = new TextEncoder();
1451
2055
  var textDecoder = new TextDecoder();
1452
2056
  function decodeGeoArrowWKB(column) {
@@ -1458,9 +2062,38 @@ function decodeGeoArrowWKB(column) {
1458
2062
  function encodeGeoArrowWKB(column) {
1459
2063
  if (column.encoding === "geoarrow.wkb")
1460
2064
  return column;
1461
- const rows = materializeGeoArrowRows(column);
1462
- const values = rows.map((row) => row ? (0, import_wkb.writeWKB)(row, column.dimension) : null);
1463
- return copyMetadata(column, makeSerializedColumn(values, "geoarrow.wkb", column.dimension));
2065
+ const writers = [];
2066
+ const dimensions = [];
2067
+ for (const chunk of column.chunks) {
2068
+ for (let rowIndex = 0; rowIndex < chunk.length; rowIndex++) {
2069
+ if (!isGeoArrowValueValid(chunk.validity, rowIndex)) {
2070
+ writers.push(null);
2071
+ dimensions.push(column.dimension);
2072
+ continue;
2073
+ }
2074
+ dimensions.push(getPhysicalGeometryDimension(chunk, rowIndex, column.encoding, column.dimension));
2075
+ writers.push((builder) => writePhysicalGeometry(builder, chunk, rowIndex, column.encoding, column.dimension));
2076
+ }
2077
+ }
2078
+ const built = import_wkb2.WKBBuilder.buildGeometryArray(writers, {
2079
+ dimension: column.dimension,
2080
+ dimensionResolver: (index) => dimensions[index] || column.dimension
2081
+ });
2082
+ return copyMetadata(column, {
2083
+ encoding: "geoarrow.wkb",
2084
+ dimension: column.dimension,
2085
+ coordinateLayout: null,
2086
+ chunks: [
2087
+ {
2088
+ kind: "serialized",
2089
+ encoding: "binary",
2090
+ length: writers.length,
2091
+ offsets: built.valueOffsets,
2092
+ values: built.values,
2093
+ ...built.nullBitmap ? { validity: { values: built.nullBitmap } } : {}
2094
+ }
2095
+ ]
2096
+ });
1464
2097
  }
1465
2098
  function decodeGeoArrowWKT(column) {
1466
2099
  if (column.encoding !== "geoarrow.wkt")
@@ -1471,10 +2104,146 @@ function decodeGeoArrowWKT(column) {
1471
2104
  function encodeGeoArrowWKT(column) {
1472
2105
  if (column.encoding === "geoarrow.wkt")
1473
2106
  return column;
1474
- const rows = materializeGeoArrowRows(column);
1475
- const values = rows.map((row) => row ? textEncoder.encode((0, import_wkb.formatWKT)(row, column.dimension)) : null);
2107
+ const values = [];
2108
+ for (const chunk of column.chunks) {
2109
+ for (let rowIndex = 0; rowIndex < chunk.length; rowIndex++) {
2110
+ const row = isGeoArrowValueValid(chunk.validity, rowIndex) ? materializeGeometryRow(chunk, rowIndex, column.encoding) : null;
2111
+ values.push(row ? textEncoder.encode((0, import_wkb.formatWKT)(row, column.dimension)) : null);
2112
+ }
2113
+ }
1476
2114
  return copyMetadata(column, makeSerializedColumn(values, "geoarrow.wkt", column.dimension));
1477
2115
  }
2116
+ function writePhysicalGeometry(builder, array, rowIndex, encoding, dimension) {
2117
+ const depth = getEncodingDepth3(encoding);
2118
+ if (encoding === "geoarrow.geometry" && array.kind === "dense-union") {
2119
+ const physical = (array.offset || 0) + rowIndex;
2120
+ const typeId = array.typeIds[physical];
2121
+ const child = array.children.find((candidate) => candidate.typeId === typeId);
2122
+ if (!child)
2123
+ throw new Error("Dense-union row references an unknown child");
2124
+ const childDimension = child.dimension || dimension;
2125
+ builder.withDimension(childDimension, () => writePhysicalGeometry(builder, child.data, array.valueOffsets[physical], child.encoding || encodingFromName(child.name), childDimension));
2126
+ return;
2127
+ }
2128
+ if (encoding === "geoarrow.geometrycollection" && array.kind === "list") {
2129
+ const [first, last] = getListRange2(array, rowIndex);
2130
+ builder.beginGeometry("GeometryCollection", last - first);
2131
+ if (array.child.kind !== "dense-union")
2132
+ throw new Error("GeometryCollection requires dense union child");
2133
+ const union = array.child;
2134
+ for (let index = first; index < last; index++) {
2135
+ const physical = (union.offset || 0) + index;
2136
+ const child = union.children.find((candidate) => candidate.typeId === union.typeIds[physical]);
2137
+ if (child) {
2138
+ const childDimension = child.dimension || dimension;
2139
+ builder.withDimension(childDimension, () => writePhysicalGeometry(builder, child.data, union.valueOffsets[physical], child.encoding || encodingFromName(child.name), childDimension));
2140
+ }
2141
+ }
2142
+ return;
2143
+ }
2144
+ if (array.kind !== "list" && depth > 0)
2145
+ throw new Error(`Invalid ${encoding} storage`);
2146
+ const writeCoordinate2 = (leaf, index) => {
2147
+ const coordinate = readPhysicalCoordinate(leaf, index);
2148
+ if (!coordinate)
2149
+ throw new Error("Invalid GeoArrow coordinate storage");
2150
+ if (dimension === "xym")
2151
+ builder.writeCoordinate(coordinate[0], coordinate[1], void 0, coordinate[2]);
2152
+ else if (dimension === "xyzm")
2153
+ builder.writeCoordinate(coordinate[0], coordinate[1], coordinate[2], coordinate[3]);
2154
+ else
2155
+ builder.writeCoordinate(coordinate[0], coordinate[1], coordinate[2]);
2156
+ };
2157
+ const writeSequence = (leaf, first, last) => {
2158
+ for (let index = first; index < last; index++)
2159
+ writeCoordinate2(leaf, index);
2160
+ };
2161
+ if (encoding === "geoarrow.point") {
2162
+ builder.beginPoint();
2163
+ writeCoordinate2(array, rowIndex);
2164
+ } else if (encoding === "geoarrow.linestring" || encoding === "geoarrow.multipoint") {
2165
+ const [first, last] = getListRange2(array, rowIndex);
2166
+ if (encoding === "geoarrow.linestring") {
2167
+ builder.beginLineString(last - first);
2168
+ writeSequence(array.child, first, last);
2169
+ } else {
2170
+ builder.beginMultiPoint(last - first);
2171
+ const leaf = array.child;
2172
+ for (let index = first; index < last; index++) {
2173
+ builder.beginPoint();
2174
+ writeCoordinate2(leaf, index);
2175
+ }
2176
+ }
2177
+ } else if (encoding === "geoarrow.polygon" || encoding === "geoarrow.multilinestring") {
2178
+ const [first, last] = getListRange2(array, rowIndex);
2179
+ const parts = array.child;
2180
+ if (encoding === "geoarrow.polygon")
2181
+ builder.beginPolygon(last - first);
2182
+ else
2183
+ builder.beginMultiLineString(last - first);
2184
+ for (let partIndex = first; partIndex < last; partIndex++) {
2185
+ const [ringFirst, ringLast] = getListRange2(parts, partIndex);
2186
+ const leaf = parts.child;
2187
+ if (encoding === "geoarrow.polygon")
2188
+ builder.beginLinearRing(ringLast - ringFirst);
2189
+ else
2190
+ builder.beginLineString(ringLast - ringFirst);
2191
+ writeSequence(leaf, ringFirst, ringLast);
2192
+ }
2193
+ } else if (encoding === "geoarrow.multipolygon") {
2194
+ const [first, last] = getListRange2(array, rowIndex);
2195
+ const polygons = array.child;
2196
+ builder.beginMultiPolygon(last - first);
2197
+ for (let polygonIndex = first; polygonIndex < last; polygonIndex++) {
2198
+ const [partFirst, partLast] = getListRange2(polygons, polygonIndex);
2199
+ const parts = polygons.child;
2200
+ builder.beginPolygon(partLast - partFirst);
2201
+ for (let partIndex = partFirst; partIndex < partLast; partIndex++) {
2202
+ const [ringFirst, ringLast] = getListRange2(parts, partIndex);
2203
+ builder.beginLinearRing(ringLast - ringFirst);
2204
+ writeSequence(parts.child, ringFirst, ringLast);
2205
+ }
2206
+ }
2207
+ }
2208
+ }
2209
+ function getPhysicalGeometryDimension(array, rowIndex, encoding, dimension) {
2210
+ if (encoding !== "geoarrow.geometry" || array.kind !== "dense-union")
2211
+ return dimension;
2212
+ const physical = (array.offset || 0) + rowIndex;
2213
+ const child = array.children.find((candidate) => candidate.typeId === array.typeIds[physical]);
2214
+ return (child == null ? void 0 : child.dimension) || dimension;
2215
+ }
2216
+ function readPhysicalCoordinate(array, index) {
2217
+ if (array.kind === "fixed-size-list" && array.child.kind === "primitive") {
2218
+ const logical = (array.offset || 0) + index;
2219
+ const values = [];
2220
+ for (let component = 0; component < array.size; component++)
2221
+ values.push(Number(array.child.values[(array.child.offset || 0) + (logical * array.size + component) * (array.child.stride || 1)]));
2222
+ return values;
2223
+ }
2224
+ if (array.kind === "struct") {
2225
+ const logical = (array.offset || 0) + index;
2226
+ return ["x", "y", "z", "m"].flatMap((name) => {
2227
+ const child = array.children[name];
2228
+ return (child == null ? void 0 : child.kind) === "primitive" ? [Number(child.values[(child.offset || 0) + logical * (child.stride || 1)])] : [];
2229
+ });
2230
+ }
2231
+ return null;
2232
+ }
2233
+ function getListRange2(list, index) {
2234
+ const offset = (list.offset || 0) + index;
2235
+ const base = typeof (list.offsetBase ?? 0) === "bigint" ? Number(list.offsetBase) : Number(list.offsetBase ?? 0);
2236
+ return [
2237
+ getGeoArrowOffset(list.offsets, offset) - base,
2238
+ getGeoArrowOffset(list.offsets, offset + 1) - base
2239
+ ];
2240
+ }
2241
+ function getEncodingDepth3(encoding) {
2242
+ return encoding === "geoarrow.point" ? 0 : encoding === "geoarrow.linestring" || encoding === "geoarrow.multipoint" ? 1 : encoding === "geoarrow.polygon" || encoding === "geoarrow.multilinestring" ? 2 : 3;
2243
+ }
2244
+ function encodingFromName(name) {
2245
+ return `geoarrow.${name.toLowerCase()}`;
2246
+ }
1478
2247
  function normalizeRowsToDimension(rows, dimension) {
1479
2248
  const size = getGeoArrowDimensionSize(dimension);
1480
2249
  const normalizeGeometry = (geometry) => {
@@ -1515,6 +2284,21 @@ function decodeSerializedRows(column, decoder) {
1515
2284
  return rows;
1516
2285
  }
1517
2286
  function getSerializedBytes(array, rowIndex) {
2287
+ var _a;
2288
+ if (array.views) {
2289
+ const viewIndex = ((array.offset || 0) + rowIndex) * 4;
2290
+ const length = array.views[viewIndex];
2291
+ if (length <= 12) {
2292
+ const byteOffset2 = array.views.byteOffset + viewIndex * 4 + 4;
2293
+ return new Uint8Array(array.views.buffer, byteOffset2, length);
2294
+ }
2295
+ const bufferIndex = array.views[viewIndex + 2];
2296
+ const byteOffset = array.views[viewIndex + 3];
2297
+ const data = (_a = array.dataBuffers) == null ? void 0 : _a[bufferIndex];
2298
+ if (!data)
2299
+ throw new Error(`Serialized view references missing data buffer ${bufferIndex}`);
2300
+ return data.subarray(byteOffset, byteOffset + length);
2301
+ }
1518
2302
  const offsetIndex = (array.offset || 0) + rowIndex;
1519
2303
  const baseValue = array.offsetBase ?? 0;
1520
2304
  const base = typeof baseValue === "bigint" ? Number(baseValue) : baseValue;