@honua/react 0.1.12 → 0.1.13

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 (190) hide show
  1. package/columnar/apache-arrow.d.ts +17 -0
  2. package/columnar/apache-arrow.d.ts.map +1 -0
  3. package/columnar/apache-arrow.js +1036 -0
  4. package/columnar/batch-cache-conformance.d.ts +66 -0
  5. package/columnar/batch-cache-conformance.d.ts.map +1 -0
  6. package/columnar/batch-cache-conformance.js +317 -0
  7. package/columnar/batch-cache-indexeddb.d.ts +31 -0
  8. package/columnar/batch-cache-indexeddb.d.ts.map +1 -0
  9. package/columnar/batch-cache-indexeddb.js +148 -0
  10. package/columnar/batch-cache.d.ts +298 -0
  11. package/columnar/batch-cache.d.ts.map +1 -0
  12. package/columnar/batch-cache.js +757 -0
  13. package/columnar/geoarrow-aggregate.d.ts +178 -0
  14. package/columnar/geoarrow-aggregate.d.ts.map +1 -0
  15. package/columnar/geoarrow-aggregate.js +976 -0
  16. package/columnar/geoarrow-patch.d.ts +382 -0
  17. package/columnar/geoarrow-patch.d.ts.map +1 -0
  18. package/columnar/geoarrow-patch.js +1656 -0
  19. package/columnar/geoarrow-reproject.d.ts +22 -0
  20. package/columnar/geoarrow-reproject.d.ts.map +1 -0
  21. package/columnar/geoarrow-reproject.js +119 -0
  22. package/columnar/geoarrow-serialization.d.ts +68 -0
  23. package/columnar/geoarrow-serialization.d.ts.map +1 -0
  24. package/columnar/geoarrow-serialization.js +331 -0
  25. package/columnar/geoarrow-types.d.ts +271 -0
  26. package/columnar/geoarrow-types.d.ts.map +1 -0
  27. package/columnar/geoarrow-types.js +15 -0
  28. package/columnar/geoarrow.d.ts +52 -0
  29. package/columnar/geoarrow.d.ts.map +1 -0
  30. package/columnar/geoarrow.js +1469 -0
  31. package/columnar/index.d.ts +43 -0
  32. package/columnar/index.d.ts.map +1 -0
  33. package/columnar/index.js +29 -0
  34. package/columnar/operators.d.ts +70 -0
  35. package/columnar/operators.d.ts.map +1 -0
  36. package/columnar/operators.js +307 -0
  37. package/columnar/result.d.ts +369 -0
  38. package/columnar/result.d.ts.map +1 -0
  39. package/columnar/result.js +815 -0
  40. package/columnar/telemetry.d.ts +152 -0
  41. package/columnar/telemetry.d.ts.map +1 -0
  42. package/columnar/telemetry.js +265 -0
  43. package/columnar/transfer.d.ts +28 -0
  44. package/columnar/transfer.d.ts.map +1 -0
  45. package/columnar/transfer.js +669 -0
  46. package/columnar/types.d.ts +175 -0
  47. package/columnar/types.d.ts.map +1 -0
  48. package/columnar/types.js +22 -0
  49. package/columnar/worker.d.ts +157 -0
  50. package/columnar/worker.d.ts.map +1 -0
  51. package/columnar/worker.js +1059 -0
  52. package/connect-url-safety.d.ts +53 -0
  53. package/connect-url-safety.js +189 -0
  54. package/contract/pmtiles.d.ts +6 -5
  55. package/contract/pmtiles.d.ts.map +1 -1
  56. package/contract/pmtiles.js +30 -5
  57. package/core/client.d.ts.map +1 -1
  58. package/core/client.js +87 -19
  59. package/core/deprecation-notice.d.ts +28 -0
  60. package/core/deprecation-notice.d.ts.map +1 -0
  61. package/core/deprecation-notice.js +22 -0
  62. package/core/esri-geojson.d.ts.map +1 -1
  63. package/core/esri-geojson.js +10 -6
  64. package/core/optional-grpc-peer.d.ts +15 -0
  65. package/core/optional-grpc-peer.d.ts.map +1 -0
  66. package/core/optional-grpc-peer.js +60 -0
  67. package/esri-compat/basemap-gallery.d.ts +2 -0
  68. package/esri-compat/basemap-gallery.d.ts.map +1 -1
  69. package/esri-compat/basemap-gallery.js +12 -0
  70. package/esri-compat/basemap.d.ts +4 -0
  71. package/esri-compat/basemap.d.ts.map +1 -1
  72. package/esri-compat/basemap.js +70 -0
  73. package/esri-compat/bookmarks.d.ts +2 -0
  74. package/esri-compat/bookmarks.d.ts.map +1 -1
  75. package/esri-compat/bookmarks.js +12 -0
  76. package/esri-compat/controls.d.ts +18 -0
  77. package/esri-compat/controls.d.ts.map +1 -1
  78. package/esri-compat/controls.js +120 -57
  79. package/esri-compat/directions.d.ts +4 -0
  80. package/esri-compat/directions.d.ts.map +1 -1
  81. package/esri-compat/directions.js +30 -20
  82. package/esri-compat/editor.d.ts +4 -0
  83. package/esri-compat/editor.d.ts.map +1 -1
  84. package/esri-compat/editor.js +16 -0
  85. package/esri-compat/esri-config.d.ts +2 -1
  86. package/esri-compat/esri-config.d.ts.map +1 -1
  87. package/esri-compat/feature-effect.d.ts +23 -0
  88. package/esri-compat/feature-effect.d.ts.map +1 -0
  89. package/esri-compat/feature-effect.js +12 -0
  90. package/esri-compat/feature-form.d.ts +6 -0
  91. package/esri-compat/feature-form.d.ts.map +1 -1
  92. package/esri-compat/feature-form.js +63 -0
  93. package/esri-compat/feature-layer.d.ts +21 -2
  94. package/esri-compat/feature-layer.d.ts.map +1 -1
  95. package/esri-compat/feature-layer.js +172 -1
  96. package/esri-compat/feature-table.d.ts +5 -0
  97. package/esri-compat/feature-table.d.ts.map +1 -1
  98. package/esri-compat/feature-table.js +23 -0
  99. package/esri-compat/identity-manager.d.ts +1 -1
  100. package/esri-compat/identity-manager.js +1 -1
  101. package/esri-compat/legend.d.ts +2 -0
  102. package/esri-compat/legend.d.ts.map +1 -1
  103. package/esri-compat/legend.js +98 -0
  104. package/esri-compat/locator.d.ts +1 -1
  105. package/esri-compat/locator.js +1 -1
  106. package/esri-compat/map-view-mount.d.ts +1 -0
  107. package/esri-compat/map-view-mount.d.ts.map +1 -1
  108. package/esri-compat/map-view-mount.js +46 -25
  109. package/esri-compat/map-view.d.ts +13 -0
  110. package/esri-compat/map-view.d.ts.map +1 -1
  111. package/esri-compat/map-view.js +94 -8
  112. package/esri-compat/map.d.ts +5 -1
  113. package/esri-compat/map.d.ts.map +1 -1
  114. package/esri-compat/map.js +10 -0
  115. package/esri-compat/measurement.d.ts +2 -0
  116. package/esri-compat/measurement.d.ts.map +1 -1
  117. package/esri-compat/measurement.js +14 -0
  118. package/esri-compat/point.d.ts +7 -0
  119. package/esri-compat/point.d.ts.map +1 -1
  120. package/esri-compat/point.js +33 -3
  121. package/esri-compat/popup.d.ts +2 -0
  122. package/esri-compat/popup.d.ts.map +1 -1
  123. package/esri-compat/popup.js +12 -0
  124. package/esri-compat/portal.d.ts +1 -1
  125. package/esri-compat/portal.js +1 -1
  126. package/esri-compat/print.d.ts +3 -0
  127. package/esri-compat/print.d.ts.map +1 -1
  128. package/esri-compat/print.js +15 -0
  129. package/esri-compat/search.d.ts +4 -0
  130. package/esri-compat/search.d.ts.map +1 -1
  131. package/esri-compat/search.js +25 -28
  132. package/esri-compat/sketch.d.ts +2 -0
  133. package/esri-compat/sketch.d.ts.map +1 -1
  134. package/esri-compat/sketch.js +12 -0
  135. package/esri-compat/widget-host.d.ts +46 -17
  136. package/esri-compat/widget-host.d.ts.map +1 -1
  137. package/esri-compat/widget-host.js +84 -20
  138. package/esri-compat/widget-shell-hosts.d.ts +61 -0
  139. package/esri-compat/widget-shell-hosts.d.ts.map +1 -0
  140. package/esri-compat/widget-shell-hosts.js +76 -0
  141. package/geocoding/index.d.ts +114 -0
  142. package/geocoding/index.d.ts.map +1 -0
  143. package/geocoding/index.js +293 -0
  144. package/geocoding/provider-http.d.ts +37 -0
  145. package/geocoding/provider-http.d.ts.map +1 -0
  146. package/geocoding/provider-http.js +93 -0
  147. package/geocoding/provider.d.ts +145 -0
  148. package/geocoding/provider.d.ts.map +1 -0
  149. package/geocoding/provider.js +41 -0
  150. package/geocoding/providers/honua.d.ts +28 -0
  151. package/geocoding/providers/honua.d.ts.map +1 -0
  152. package/geocoding/providers/honua.js +68 -0
  153. package/geocoding/providers/nominatim.d.ts +47 -0
  154. package/geocoding/providers/nominatim.d.ts.map +1 -0
  155. package/geocoding/providers/nominatim.js +105 -0
  156. package/geocoding/providers/pelias.d.ts +40 -0
  157. package/geocoding/providers/pelias.d.ts.map +1 -0
  158. package/geocoding/providers/pelias.js +115 -0
  159. package/geocoding/providers/photon.d.ts +39 -0
  160. package/geocoding/providers/photon.d.ts.map +1 -0
  161. package/geocoding/providers/photon.js +107 -0
  162. package/geometry/convert.d.ts +13 -0
  163. package/geometry/convert.d.ts.map +1 -0
  164. package/geometry/convert.js +12 -0
  165. package/geometry/index.d.ts +17 -0
  166. package/geometry/index.d.ts.map +1 -0
  167. package/geometry/index.js +15 -0
  168. package/geometry/internal.d.ts +25 -0
  169. package/geometry/internal.d.ts.map +1 -0
  170. package/geometry/internal.js +40 -0
  171. package/geometry/ops.d.ts +71 -0
  172. package/geometry/ops.d.ts.map +1 -0
  173. package/geometry/ops.js +147 -0
  174. package/geometry/project.d.ts +40 -0
  175. package/geometry/project.d.ts.map +1 -0
  176. package/geometry/project.js +88 -0
  177. package/geometry/types.d.ts +36 -0
  178. package/geometry/types.d.ts.map +1 -0
  179. package/geometry/types.js +12 -0
  180. package/kernel/renderer.d.ts +82 -0
  181. package/kernel/renderer.js +1 -0
  182. package/offline/digest.d.ts +15 -0
  183. package/offline/digest.js +58 -0
  184. package/offline/quota.d.ts +161 -0
  185. package/offline/quota.js +370 -0
  186. package/offline/types.d.ts +349 -0
  187. package/offline/types.js +75 -0
  188. package/package.json +14 -2
  189. package/widget-capabilities.d.ts +67 -0
  190. package/widget-capabilities.js +152 -0
@@ -0,0 +1,1036 @@
1
+ import { GEOARROW_SPEC_VERSION, HONUA_GEOARROW_LAYOUT_VERSION, HonuaGeoArrowError, } from "./geoarrow-types.js";
2
+ import { inspectGeoArrowBatch } from "./geoarrow.js";
3
+ import { createColumnarBatch, inspectColumnarBatch } from "./transfer.js";
4
+ import { DEFAULT_COLUMNAR_BATCH_MAX_BACKING_BYTES, DEFAULT_COLUMNAR_BATCH_MAX_METADATA_ENTRIES, DEFAULT_COLUMNAR_BATCH_MAX_SCHEMA_NODES, DEFAULT_COLUMNAR_BATCH_MAX_STRING_BYTES, } from "./types.js";
5
+ const APACHE_ARROW_PACKAGE = "apache-arrow";
6
+ const MAX_ARROW_METADATA_BYTES = 16 * 1024 * 1024;
7
+ const MAX_ARROW_METADATA_DEPTH = 64;
8
+ const MAX_ARROW_METADATA_NODES = 65_536;
9
+ const REQUIRED_EXPORTS = Object.freeze([
10
+ "Dictionary",
11
+ "Field",
12
+ "FixedSizeList",
13
+ "Float64",
14
+ "Int32",
15
+ "List",
16
+ "RecordBatch",
17
+ "Schema",
18
+ "Struct",
19
+ "TimestampMicrosecond",
20
+ "TimestampMillisecond",
21
+ "TimestampNanosecond",
22
+ "TimestampSecond",
23
+ "Uint32",
24
+ "Utf8",
25
+ "makeData",
26
+ "makeVector",
27
+ ]);
28
+ const TRANSPORT_META = Object.freeze({
29
+ batchId: "honua.columnar.batch.id",
30
+ sequence: "honua.columnar.batch.sequence",
31
+ rowOffset: "honua.columnar.batch.row-offset",
32
+ schema: "honua.columnar.schema.json",
33
+ identity: "honua.columnar.identity.json",
34
+ });
35
+ const LAYOUT_META = Object.freeze({
36
+ version: "honua.geoarrow.layout.version",
37
+ specVersion: "honua.geoarrow.spec.version",
38
+ geometryField: "honua.geoarrow.geometry.field",
39
+ geometryKind: "honua.geoarrow.geometry.kind",
40
+ dimensions: "honua.geoarrow.geometry.dimensions",
41
+ coordinateLayout: "honua.geoarrow.geometry.coordinate-layout",
42
+ temporalField: "honua.geoarrow.temporal.field",
43
+ temporalUnit: "honua.geoarrow.temporal.unit",
44
+ temporalTimezone: "honua.geoarrow.temporal.timezone",
45
+ dictionaryField: "honua.geoarrow.dictionary.field",
46
+ dictionaryOrdered: "honua.geoarrow.dictionary.ordered",
47
+ featureIdField: "honua.geoarrow.feature-id.field",
48
+ });
49
+ const defaultImportModule = (specifier) => import(specifier);
50
+ function fail(code, message, detail, cause) {
51
+ throw new HonuaGeoArrowError(code, message, detail, cause === undefined ? undefined : { cause });
52
+ }
53
+ function isRecord(value) {
54
+ return (typeof value === "object" && value !== null) || typeof value === "function";
55
+ }
56
+ function validateModule(module) {
57
+ if (!isRecord(module)) {
58
+ fail("missing-peer", `The optional peer "${APACHE_ARROW_PACKAGE}" did not expose a module namespace.`, {
59
+ package: APACHE_ARROW_PACKAGE,
60
+ });
61
+ }
62
+ for (const name of REQUIRED_EXPORTS) {
63
+ if (typeof module[name] !== "function") {
64
+ fail("missing-peer", `The loaded "${APACHE_ARROW_PACKAGE}" module does not export ${name}.`, {
65
+ package: APACHE_ARROW_PACKAGE,
66
+ export: name,
67
+ });
68
+ }
69
+ }
70
+ return module;
71
+ }
72
+ /** Load Apache Arrow only when its adapter is explicitly requested. */
73
+ export async function loadApacheArrow(options = {}) {
74
+ let module;
75
+ try {
76
+ module = await (options.importModule ?? defaultImportModule)(APACHE_ARROW_PACKAGE);
77
+ }
78
+ catch (cause) {
79
+ fail("missing-peer", `The GeoArrow adapter requires the optional peer "${APACHE_ARROW_PACKAGE}". Install it or inject a module.`, { package: APACHE_ARROW_PACKAGE }, cause);
80
+ }
81
+ return validateModule(module);
82
+ }
83
+ function construct(module, name, args) {
84
+ const ctor = module[name];
85
+ if (typeof ctor !== "function")
86
+ fail("missing-peer", `Apache Arrow export ${name} is unavailable.`);
87
+ try {
88
+ return Reflect.construct(ctor, args);
89
+ }
90
+ catch (cause) {
91
+ fail("unsupported-layout", `Apache Arrow failed to construct ${name}.`, { export: name }, cause);
92
+ }
93
+ }
94
+ function invoke(module, name, args) {
95
+ const operation = module[name];
96
+ if (typeof operation !== "function")
97
+ fail("missing-peer", `Apache Arrow export ${name} is unavailable.`);
98
+ try {
99
+ return Reflect.apply(operation, module, args);
100
+ }
101
+ catch (cause) {
102
+ fail("unsupported-layout", `Apache Arrow ${name} rejected the normative layout.`, { export: name }, cause);
103
+ }
104
+ }
105
+ function mapOf(record) {
106
+ return new Map(Object.entries(record ?? {}));
107
+ }
108
+ function data(module, props) {
109
+ return invoke(module, "makeData", [props]);
110
+ }
111
+ function field(module, name, type, nullable, metadata) {
112
+ return construct(module, "Field", [name, type, nullable, mapOf(metadata)]);
113
+ }
114
+ function dimensionNames(dimensions) {
115
+ switch (dimensions) {
116
+ case "xy":
117
+ return ["x", "y"];
118
+ case "xyz":
119
+ return ["x", "y", "z"];
120
+ case "xym":
121
+ return ["x", "y", "m"];
122
+ case "xyzm":
123
+ return ["x", "y", "z", "m"];
124
+ default:
125
+ fail("unsupported-layout", `Unsupported GeoArrow dimensions "${String(dimensions)}".`);
126
+ }
127
+ }
128
+ function coordinateNode(module, inspection, nullBitmap) {
129
+ const geometry = inspection.geometry;
130
+ const names = dimensionNames(geometry.dimensions);
131
+ const floatType = construct(module, "Float64", []);
132
+ if (geometry.coordinateLayout === "interleaved") {
133
+ const child = data(module, {
134
+ type: floatType,
135
+ length: geometry.coordinates.interleaved.length,
136
+ data: geometry.coordinates.interleaved,
137
+ });
138
+ const type = construct(module, "FixedSizeList", [names.length, field(module, names.join(""), floatType, false)]);
139
+ return Object.freeze({
140
+ type,
141
+ data: data(module, {
142
+ type,
143
+ length: inspection.metrics.vertices,
144
+ child,
145
+ ...(nullBitmap === undefined ? {} : { nullBitmap }),
146
+ }),
147
+ });
148
+ }
149
+ const fields = names.map((name) => field(module, name, floatType, false));
150
+ const type = construct(module, "Struct", [fields]);
151
+ return Object.freeze({
152
+ type,
153
+ data: data(module, {
154
+ type,
155
+ length: inspection.metrics.vertices,
156
+ children: names.map((name) => data(module, { type: floatType, length: inspection.metrics.vertices, data: geometry.coordinates[name] })),
157
+ ...(nullBitmap === undefined ? {} : { nullBitmap }),
158
+ }),
159
+ });
160
+ }
161
+ function geometryNode(module, inspection) {
162
+ const geometry = inspection.geometry;
163
+ if (geometry.kind === "point")
164
+ return coordinateNode(module, inspection, geometry.validity);
165
+ const coordinate = coordinateNode(module, inspection);
166
+ const verticesType = construct(module, "List", [field(module, "vertices", coordinate.type, false)]);
167
+ if (geometry.kind === "linestring") {
168
+ return Object.freeze({
169
+ type: verticesType,
170
+ data: data(module, {
171
+ type: verticesType,
172
+ length: inspection.batch.rowCount,
173
+ valueOffsets: geometry.offsets,
174
+ child: coordinate.data,
175
+ ...(geometry.validity === undefined ? {} : { nullBitmap: geometry.validity }),
176
+ }),
177
+ });
178
+ }
179
+ const rings = data(module, {
180
+ type: verticesType,
181
+ length: inspection.metrics.rings,
182
+ valueOffsets: geometry.ringOffsets,
183
+ child: coordinate.data,
184
+ });
185
+ const polygonType = construct(module, "List", [field(module, "rings", verticesType, false)]);
186
+ return Object.freeze({
187
+ type: polygonType,
188
+ data: data(module, {
189
+ type: polygonType,
190
+ length: inspection.batch.rowCount,
191
+ valueOffsets: geometry.offsets,
192
+ child: rings,
193
+ ...(geometry.validity === undefined ? {} : { nullBitmap: geometry.validity }),
194
+ }),
195
+ });
196
+ }
197
+ function timestampType(module, unit, timezone) {
198
+ const name = unit === "second"
199
+ ? "TimestampSecond"
200
+ : unit === "millisecond"
201
+ ? "TimestampMillisecond"
202
+ : unit === "microsecond"
203
+ ? "TimestampMicrosecond"
204
+ : "TimestampNanosecond";
205
+ return construct(module, name, [timezone ?? null]);
206
+ }
207
+ function sourceField(batch, name) {
208
+ const result = batch.schema.fields.find((candidate) => candidate.name === name);
209
+ if (!result)
210
+ fail("invalid-batch", `Columnar schema field "${name}" is missing.`);
211
+ return result;
212
+ }
213
+ function transportMetadata(batch) {
214
+ const metadata = mapOf(batch.schema.metadata);
215
+ metadata.set(TRANSPORT_META.batchId, batch.id);
216
+ metadata.set(TRANSPORT_META.sequence, String(batch.sequence));
217
+ if (batch.rowOffset !== undefined)
218
+ metadata.set(TRANSPORT_META.rowOffset, String(batch.rowOffset));
219
+ metadata.set(TRANSPORT_META.schema, JSON.stringify(batch.schema));
220
+ metadata.set(TRANSPORT_META.identity, JSON.stringify(batch.identity));
221
+ return metadata;
222
+ }
223
+ /**
224
+ * Wrap a validated Honua batch as a real Apache Arrow RecordBatch. Every Arrow
225
+ * data node aliases the Honua buffers; no geometry, attribute, or validity
226
+ * payload is copied.
227
+ */
228
+ export async function toApacheArrowRecordBatch(batch, options = {}) {
229
+ const module = options.module ? validateModule(options.module) : await loadApacheArrow(options);
230
+ const inspection = inspectGeoArrowBatch(batch);
231
+ const arrowFields = [];
232
+ const arrowData = [];
233
+ const geometry = geometryNode(module, inspection);
234
+ const geometrySchemaField = sourceField(batch, inspection.geometry.field);
235
+ arrowFields.push(field(module, inspection.geometry.field, geometry.type, geometrySchemaField.nullable, geometrySchemaField.metadata));
236
+ arrowData.push(geometry.data);
237
+ if (inspection.temporal) {
238
+ const source = sourceField(batch, inspection.temporal.field);
239
+ const type = timestampType(module, inspection.temporal.unit, inspection.temporal.timezone);
240
+ arrowFields.push(field(module, inspection.temporal.field, type, source.nullable, source.metadata));
241
+ arrowData.push(data(module, {
242
+ type,
243
+ length: batch.rowCount,
244
+ data: inspection.temporal.values,
245
+ ...(inspection.temporal.validity === undefined ? {} : { nullBitmap: inspection.temporal.validity }),
246
+ }));
247
+ }
248
+ if (inspection.dictionary) {
249
+ const source = sourceField(batch, inspection.dictionary.field);
250
+ const utf8 = construct(module, "Utf8", []);
251
+ const dictionaryData = data(module, {
252
+ type: utf8,
253
+ length: inspection.dictionary.offsets.length - 1,
254
+ valueOffsets: inspection.dictionary.offsets,
255
+ data: inspection.dictionary.values,
256
+ });
257
+ const dictionaryVector = invoke(module, "makeVector", [dictionaryData]);
258
+ const type = construct(module, "Dictionary", [
259
+ utf8,
260
+ construct(module, "Int32", []),
261
+ 0,
262
+ inspection.dictionary.ordered,
263
+ ]);
264
+ arrowFields.push(field(module, inspection.dictionary.field, type, source.nullable, source.metadata));
265
+ arrowData.push(data(module, {
266
+ type,
267
+ length: batch.rowCount,
268
+ data: inspection.dictionary.indices,
269
+ dictionary: dictionaryVector,
270
+ ...(inspection.dictionary.validity === undefined ? {} : { nullBitmap: inspection.dictionary.validity }),
271
+ }));
272
+ }
273
+ if (inspection.featureIds) {
274
+ const source = sourceField(batch, inspection.featureIds.field);
275
+ const type = construct(module, "Uint32", []);
276
+ arrowFields.push(field(module, inspection.featureIds.field, type, source.nullable, source.metadata));
277
+ arrowData.push(data(module, { type, length: batch.rowCount, data: inspection.featureIds.values }));
278
+ }
279
+ const schema = construct(module, "Schema", [arrowFields, transportMetadata(batch)]);
280
+ const structType = construct(module, "Struct", [arrowFields]);
281
+ const structData = data(module, { type: structType, length: batch.rowCount, children: arrowData });
282
+ const recordBatch = construct(module, "RecordBatch", [schema, structData]);
283
+ if (recordBatch.numRows !== batch.rowCount)
284
+ fail("invalid-batch", "Apache Arrow changed the RecordBatch row count.");
285
+ const metrics = inspectColumnarBatch(batch);
286
+ return Object.freeze({
287
+ recordBatch,
288
+ metrics: Object.freeze({
289
+ rows: batch.rowCount,
290
+ backingBytes: metrics.backingBytes,
291
+ referencedBytes: metrics.logicalBytes,
292
+ copiedBytes: 0,
293
+ }),
294
+ });
295
+ }
296
+ function requireMap(value, label) {
297
+ if (typeof value !== "object" ||
298
+ value === null ||
299
+ typeof value.get !== "function" ||
300
+ !Number.isSafeInteger(value.size) ||
301
+ value.size < 0) {
302
+ fail("invalid-batch", `${label} must be an Arrow metadata Map.`);
303
+ }
304
+ return value;
305
+ }
306
+ function mapValue(metadata, key) {
307
+ const value = metadata.get(key);
308
+ if (value !== undefined && typeof value !== "string")
309
+ fail("invalid-batch", `Arrow metadata "${key}" must be a string.`);
310
+ return value;
311
+ }
312
+ function requiredMapValue(metadata, key) {
313
+ const value = mapValue(metadata, key);
314
+ if (value === undefined)
315
+ fail("invalid-batch", `Arrow schema metadata is missing "${key}".`, { key });
316
+ return value;
317
+ }
318
+ function positiveMetadataLimit(value, fallback, label) {
319
+ const resolved = value ?? fallback;
320
+ if (!Number.isSafeInteger(resolved) || resolved <= 0) {
321
+ fail("invalid-input", `${label} must be a positive safe integer.`);
322
+ }
323
+ return resolved;
324
+ }
325
+ function assertBoundedUtf8(value, limit, label) {
326
+ let bytes = 0;
327
+ for (let index = 0; index < value.length; index += 1) {
328
+ const code = value.charCodeAt(index);
329
+ if (code <= 0x7f)
330
+ bytes += 1;
331
+ else if (code <= 0x7ff)
332
+ bytes += 2;
333
+ else if (code >= 0xd800 && code <= 0xdbff && index + 1 < value.length) {
334
+ const next = value.charCodeAt(index + 1);
335
+ if (next >= 0xdc00 && next <= 0xdfff) {
336
+ bytes += 4;
337
+ index += 1;
338
+ }
339
+ else
340
+ bytes += 3;
341
+ }
342
+ else
343
+ bytes += 3;
344
+ if (bytes > limit)
345
+ fail("invalid-batch", `${label} exceeds the ${limit}-byte metadata limit.`);
346
+ }
347
+ }
348
+ function parseJson(encoded, label, limits) {
349
+ const maxBytes = Math.min(positiveMetadataLimit(limits.maxStringBytes, DEFAULT_COLUMNAR_BATCH_MAX_STRING_BYTES, "maxStringBytes"), MAX_ARROW_METADATA_BYTES);
350
+ const maxNodes = Math.min(positiveMetadataLimit(limits.maxMetadataEntries, DEFAULT_COLUMNAR_BATCH_MAX_METADATA_ENTRIES, "maxMetadataEntries") + positiveMetadataLimit(limits.maxSchemaNodes, DEFAULT_COLUMNAR_BATCH_MAX_SCHEMA_NODES, "maxSchemaNodes"), MAX_ARROW_METADATA_NODES);
351
+ assertBoundedUtf8(encoded, maxBytes, label);
352
+ let parsed;
353
+ try {
354
+ parsed = JSON.parse(encoded);
355
+ }
356
+ catch (cause) {
357
+ fail("invalid-batch", `${label} is not valid JSON.`, undefined, cause);
358
+ }
359
+ const pending = [{ value: parsed, depth: 0 }];
360
+ let nodes = 0;
361
+ while (pending.length > 0) {
362
+ const current = pending.pop();
363
+ nodes += 1;
364
+ if (nodes > maxNodes)
365
+ fail("invalid-batch", `${label} exceeds the ${maxNodes}-node metadata limit.`);
366
+ if (current.depth > MAX_ARROW_METADATA_DEPTH) {
367
+ fail("invalid-batch", `${label} exceeds the ${MAX_ARROW_METADATA_DEPTH}-level metadata depth limit.`);
368
+ }
369
+ if (typeof current.value !== "object" || current.value === null)
370
+ continue;
371
+ for (const child of Object.values(current.value)) {
372
+ pending.push({ value: child, depth: current.depth + 1 });
373
+ }
374
+ }
375
+ return parsed;
376
+ }
377
+ function parseInteger(value, fallback, label) {
378
+ if (value === undefined)
379
+ return fallback;
380
+ const parsed = Number(value);
381
+ if (!Number.isSafeInteger(parsed) || parsed < 0)
382
+ fail("invalid-batch", `${label} must be a non-negative safe integer.`);
383
+ return parsed;
384
+ }
385
+ function typeString(field, label) {
386
+ if (!isRecord(field) || !isRecord(field.type) || typeof field.type.toString !== "function") {
387
+ fail("invalid-batch", `${label} must expose an Arrow type.`);
388
+ }
389
+ try {
390
+ const value = field.type.toString();
391
+ if (typeof value !== "string" || value.length === 0)
392
+ fail("invalid-batch", `${label} Arrow type is empty.`);
393
+ return value;
394
+ }
395
+ catch (cause) {
396
+ if (cause instanceof HonuaGeoArrowError)
397
+ throw cause;
398
+ fail("invalid-batch", `${label} Arrow type could not be inspected.`, undefined, cause);
399
+ }
400
+ }
401
+ function typeChildren(field, label) {
402
+ const children = field.type.children;
403
+ if (!Array.isArray(children))
404
+ fail("invalid-batch", `${label} Arrow type must expose its child fields.`);
405
+ return children;
406
+ }
407
+ function requireArrowLeaf(field, expectedType, label) {
408
+ if (field.nullable || typeString(field, label) !== expectedType) {
409
+ fail("invalid-batch", `${label} must be a non-nullable ${expectedType} Arrow field.`);
410
+ }
411
+ if (field.type.children != null && (!Array.isArray(field.type.children) || field.type.children.length !== 0)) {
412
+ fail("invalid-batch", `${label} must not contain Arrow child fields.`);
413
+ }
414
+ }
415
+ function inferCoordinate(field, label) {
416
+ const actualType = typeString(field, label);
417
+ const children = typeChildren(field, label);
418
+ if (actualType.startsWith("FixedSizeList[")) {
419
+ if (children.length !== 1)
420
+ fail("invalid-batch", `${label} interleaved coordinates need one child field.`);
421
+ const values = children[0];
422
+ const dimensions = values.name;
423
+ const names = dimensionNames(dimensions);
424
+ requireArrowLeaf(values, "Float64", `${label}.${values.name}`);
425
+ if (values.name !== names.join("") || actualType !== `FixedSizeList[${names.length}]<Float64>`) {
426
+ fail("invalid-batch", `${label} interleaved coordinate dimensions do not match its fixed-size list.`);
427
+ }
428
+ return Object.freeze({
429
+ dimensions,
430
+ coordinateLayout: "interleaved",
431
+ schema: {
432
+ name: values.name,
433
+ type: { name: "fixed_size_list", parameters: { size: names.length, valueType: "float64" } },
434
+ nullable: false,
435
+ children: [{ name: "values", type: { name: "float64" }, nullable: false }],
436
+ },
437
+ });
438
+ }
439
+ if (!actualType.startsWith("Struct<")) {
440
+ fail("unsupported-layout", `${label} must use separated Struct or interleaved FixedSizeList coordinates.`);
441
+ }
442
+ const joined = children.map(({ name }) => name).join("");
443
+ const dimensions = joined;
444
+ const names = dimensionNames(dimensions);
445
+ if (children.length !== names.length || children.some((child, index) => child.name !== names[index])) {
446
+ fail("invalid-batch", `${label} separated coordinate dimensions are not in normative x/y/z/m order.`);
447
+ }
448
+ children.forEach((child) => requireArrowLeaf(child, "Float64", `${label}.${child.name}`));
449
+ const expected = `Struct<{${names.map((name) => `${name}:Float64`).join(", ")}}>`;
450
+ if (actualType !== expected)
451
+ fail("invalid-batch", `${label} separated coordinate type is malformed.`);
452
+ return Object.freeze({
453
+ dimensions,
454
+ coordinateLayout: "separated",
455
+ schema: {
456
+ name: "coordinates",
457
+ type: { name: "struct", parameters: { dimensions } },
458
+ nullable: false,
459
+ children: names.map((name) => ({ name, type: { name: "float64" }, nullable: false })),
460
+ },
461
+ });
462
+ }
463
+ function inferGeometry(field) {
464
+ const metadata = requireMap(field.metadata, `Arrow field "${field.name}" metadata`);
465
+ const extensionName = mapValue(metadata, "ARROW:extension:name");
466
+ if (extensionName !== "geoarrow.point" &&
467
+ extensionName !== "geoarrow.linestring" &&
468
+ extensionName !== "geoarrow.polygon") {
469
+ fail("unsupported-layout", `Arrow field "${field.name}" is not a supported GeoArrow geometry extension.`);
470
+ }
471
+ const kind = extensionName.slice("geoarrow.".length);
472
+ let coordinateField = field;
473
+ if (kind === "linestring" || kind === "polygon") {
474
+ const outer = typeChildren(field, `Arrow geometry field "${field.name}"`);
475
+ const outerName = kind === "linestring" ? "vertices" : "rings";
476
+ if (outer.length !== 1 || outer[0]?.name !== outerName || outer[0].nullable) {
477
+ fail("invalid-batch", `Arrow ${kind} storage must contain one non-nullable "${outerName}" child.`);
478
+ }
479
+ coordinateField = outer[0];
480
+ if (kind === "polygon") {
481
+ const rings = typeChildren(coordinateField, `Arrow geometry field "${field.name}".rings`);
482
+ if (rings.length !== 1 || rings[0]?.name !== "vertices" || rings[0].nullable) {
483
+ fail("invalid-batch", "Arrow polygon rings must contain one non-nullable vertices child.");
484
+ }
485
+ coordinateField = rings[0];
486
+ }
487
+ }
488
+ const coordinate = inferCoordinate(coordinateField, `Arrow geometry field "${field.name}" coordinates`);
489
+ let children;
490
+ if (kind === "point")
491
+ children = [coordinate.schema];
492
+ else if (kind === "linestring") {
493
+ children = [
494
+ {
495
+ name: "vertices",
496
+ type: { name: "list", parameters: { offsetType: "int32" } },
497
+ nullable: false,
498
+ children: [coordinate.schema],
499
+ },
500
+ ];
501
+ }
502
+ else {
503
+ children = [
504
+ {
505
+ name: "rings",
506
+ type: { name: "list", parameters: { offsetType: "int32" } },
507
+ nullable: false,
508
+ children: [
509
+ {
510
+ name: "vertices",
511
+ type: { name: "list", parameters: { offsetType: "int32" } },
512
+ nullable: false,
513
+ children: [coordinate.schema],
514
+ },
515
+ ],
516
+ },
517
+ ];
518
+ }
519
+ const extensionMetadata = mapValue(metadata, "ARROW:extension:metadata");
520
+ if (metadata.size !== (extensionMetadata === undefined ? 1 : 2)) {
521
+ fail("unsupported-layout", `Arrow geometry field "${field.name}" contains unsupported custom metadata.`);
522
+ }
523
+ return Object.freeze({
524
+ kind,
525
+ dimensions: coordinate.dimensions,
526
+ coordinateLayout: coordinate.coordinateLayout,
527
+ field: {
528
+ name: field.name,
529
+ type: {
530
+ name: `geoarrow.${kind}`,
531
+ parameters: {
532
+ dimensions: coordinate.dimensions,
533
+ coordinateLayout: coordinate.coordinateLayout,
534
+ offsetType: "int32",
535
+ },
536
+ },
537
+ nullable: field.nullable,
538
+ children,
539
+ metadata: {
540
+ "ARROW:extension:name": extensionName,
541
+ ...(extensionMetadata === undefined ? {} : { "ARROW:extension:metadata": extensionMetadata }),
542
+ },
543
+ },
544
+ });
545
+ }
546
+ function inferTimestamp(field) {
547
+ const match = /^Timestamp<(SECOND|MILLISECOND|MICROSECOND|NANOSECOND)(?:, (.+))?>$/.exec(typeString(field, `Arrow field "${field.name}"`));
548
+ if (!match)
549
+ return null;
550
+ return Object.freeze({
551
+ unit: match[1].toLowerCase(),
552
+ ...(match[2] === undefined ? {} : { timezone: match[2] }),
553
+ });
554
+ }
555
+ function inferStandardSchema(recordBatch, schemaId) {
556
+ const arrowFields = recordBatch.schema.fields;
557
+ const geometryFields = arrowFields.filter((candidate) => mapValue(requireMap(candidate.metadata, `Arrow field "${candidate.name}" metadata`), "ARROW:extension:name")?.startsWith("geoarrow."));
558
+ if (geometryFields.length !== 1 || arrowFields[0] !== geometryFields[0]) {
559
+ fail("unsupported-layout", "A standard import requires exactly one leading supported GeoArrow geometry field.");
560
+ }
561
+ const geometry = inferGeometry(geometryFields[0]);
562
+ const schemaMetadata = {
563
+ [LAYOUT_META.version]: HONUA_GEOARROW_LAYOUT_VERSION,
564
+ [LAYOUT_META.specVersion]: GEOARROW_SPEC_VERSION,
565
+ [LAYOUT_META.geometryField]: geometry.field.name,
566
+ [LAYOUT_META.geometryKind]: geometry.kind,
567
+ [LAYOUT_META.dimensions]: geometry.dimensions,
568
+ [LAYOUT_META.coordinateLayout]: geometry.coordinateLayout,
569
+ };
570
+ const fields = [geometry.field];
571
+ let stage = 0;
572
+ for (const arrowField of arrowFields.slice(1)) {
573
+ if (requireMap(arrowField.metadata, `Arrow field "${arrowField.name}" metadata`).size !== 0) {
574
+ fail("unsupported-layout", `Arrow field "${arrowField.name}" contains unsupported custom metadata.`);
575
+ }
576
+ if (arrowField.type.children != null &&
577
+ (!Array.isArray(arrowField.type.children) || arrowField.type.children.length !== 0)) {
578
+ fail("unsupported-layout", `Arrow attribute field "${arrowField.name}" must be a scalar supported column.`);
579
+ }
580
+ const timestamp = inferTimestamp(arrowField);
581
+ const actualType = typeString(arrowField, `Arrow field "${arrowField.name}"`);
582
+ if (timestamp) {
583
+ if (stage > 0)
584
+ fail("unsupported-layout", "Timestamp must precede dictionary and feature-id fields.");
585
+ stage = 1;
586
+ schemaMetadata[LAYOUT_META.temporalField] = arrowField.name;
587
+ schemaMetadata[LAYOUT_META.temporalUnit] = timestamp.unit;
588
+ if (timestamp.timezone !== undefined)
589
+ schemaMetadata[LAYOUT_META.temporalTimezone] = timestamp.timezone;
590
+ fields.push({
591
+ name: arrowField.name,
592
+ type: {
593
+ name: "timestamp",
594
+ parameters: {
595
+ unit: timestamp.unit,
596
+ ...(timestamp.timezone === undefined ? {} : { timezone: timestamp.timezone }),
597
+ },
598
+ },
599
+ nullable: arrowField.nullable,
600
+ });
601
+ }
602
+ else if (actualType === "Dictionary<Int32, Utf8>") {
603
+ if (stage > 1)
604
+ fail("unsupported-layout", "Dictionary must precede the feature-id field.");
605
+ stage = 2;
606
+ const ordered = arrowField.type.isOrdered;
607
+ if (typeof ordered !== "boolean")
608
+ fail("invalid-batch", `Arrow dictionary "${arrowField.name}" lacks ordering.`);
609
+ schemaMetadata[LAYOUT_META.dictionaryField] = arrowField.name;
610
+ schemaMetadata[LAYOUT_META.dictionaryOrdered] = String(ordered);
611
+ fields.push({
612
+ name: arrowField.name,
613
+ type: { name: "dictionary", parameters: { indexType: "int32", valueType: "utf8", ordered } },
614
+ nullable: arrowField.nullable,
615
+ });
616
+ }
617
+ else if (actualType === "Uint32") {
618
+ if (stage > 2)
619
+ fail("unsupported-layout", "Only one feature-id field is supported.");
620
+ stage = 3;
621
+ if (arrowField.nullable)
622
+ fail("unsupported-layout", "The normative uint32 feature-id field must be non-nullable.");
623
+ schemaMetadata[LAYOUT_META.featureIdField] = arrowField.name;
624
+ fields.push({ name: arrowField.name, type: { name: "uint32" }, nullable: false });
625
+ }
626
+ else {
627
+ fail("unsupported-layout", `Arrow field "${arrowField.name}" is outside the supported normative layout.`);
628
+ }
629
+ }
630
+ return Object.freeze({
631
+ schema: { id: schemaId, fields, metadata: schemaMetadata },
632
+ metadata: new Map(Object.entries(schemaMetadata)),
633
+ });
634
+ }
635
+ function requireRecordBatch(value) {
636
+ if (!isRecord(value) || !Number.isSafeInteger(value.numRows) || value.numRows < 0) {
637
+ fail("invalid-batch", "Apache Arrow RecordBatch must expose a non-negative numRows.");
638
+ }
639
+ if (!isRecord(value.schema) || !Array.isArray(value.schema.fields))
640
+ fail("invalid-batch", "Arrow schema is malformed.");
641
+ if (typeof value.getChildAt !== "function")
642
+ fail("invalid-batch", "Arrow RecordBatch must expose getChildAt().");
643
+ requireMap(value.schema.metadata, "recordBatch.schema.metadata");
644
+ value.schema.fields.forEach((field, index) => {
645
+ if (!isRecord(field) ||
646
+ typeof field.name !== "string" ||
647
+ field.name.length === 0 ||
648
+ typeof field.nullable !== "boolean") {
649
+ fail("invalid-batch", `Arrow schema field ${index} is malformed.`);
650
+ }
651
+ requireMap(field.metadata, `recordBatch.schema.fields[${index}].metadata`);
652
+ typeString(field, `recordBatch.schema.fields[${index}]`);
653
+ });
654
+ return value;
655
+ }
656
+ function vectorData(recordBatch, fieldIndex, fieldName) {
657
+ const vector = recordBatch.getChildAt(fieldIndex);
658
+ if (!vector || !Array.isArray(vector.data) || vector.data.length !== 1) {
659
+ fail("unsupported-layout", `Arrow field "${fieldName}" must contain exactly one contiguous data node.`);
660
+ }
661
+ const node = vector.data[0];
662
+ if (node.offset !== 0)
663
+ fail("unsupported-layout", `Sliced Arrow field "${fieldName}" is not supported without copying.`);
664
+ if (node.length !== recordBatch.numRows)
665
+ fail("invalid-batch", `Arrow field "${fieldName}" length must equal numRows.`);
666
+ return node;
667
+ }
668
+ function requireChild(node, index, label) {
669
+ if (!Array.isArray(node.children))
670
+ fail("invalid-batch", `${label}.children must be an array.`);
671
+ const child = node.children[index];
672
+ if (!child)
673
+ fail("invalid-batch", `${label} is missing child ${index}.`);
674
+ if (child.offset !== 0)
675
+ fail("unsupported-layout", `${label} contains a sliced child and would require copying.`);
676
+ if (child.nullBitmap !== undefined && child.nullBitmap.byteLength !== 0) {
677
+ fail("unsupported-layout", `${label} contains nullable child storage outside the normative layout.`);
678
+ }
679
+ return child;
680
+ }
681
+ function requireView(value, ctor, label) {
682
+ if (!(value instanceof ctor))
683
+ fail("invalid-batch", `${label} has the wrong Arrow buffer component type.`);
684
+ if (!(value.buffer instanceof ArrayBuffer)) {
685
+ fail("unsupported-layout", `${label} uses shared or foreign backing memory that cannot be transferred.`);
686
+ }
687
+ return value;
688
+ }
689
+ function pushView(buffers, id, role, fieldName, view) {
690
+ if (!(view.buffer instanceof ArrayBuffer))
691
+ fail("unsupported-layout", `Arrow buffer "${id}" is not transferable.`);
692
+ buffers.push({
693
+ id,
694
+ role,
695
+ field: fieldName,
696
+ data: view.buffer,
697
+ byteOffset: view.byteOffset,
698
+ byteLength: view.byteLength,
699
+ });
700
+ }
701
+ function exactPrefix(view, length, label) {
702
+ if (!Number.isSafeInteger(length) || length < 0 || view.length < length) {
703
+ fail("invalid-batch", `${label} does not contain its required ${length} logical values.`);
704
+ }
705
+ return view.length === length ? view : view.subarray(0, length);
706
+ }
707
+ function resolveForeignCopyLimits(limits) {
708
+ const maxBackingBytes = positiveMetadataLimit(limits.maxBackingBytes, DEFAULT_COLUMNAR_BATCH_MAX_BACKING_BYTES, "maxBackingBytes");
709
+ const maxCopiedBytes = positiveMetadataLimit(limits.maxCopiedBytes, maxBackingBytes, "maxCopiedBytes");
710
+ if (maxCopiedBytes > 0x7fff_ffff) {
711
+ fail("invalid-input", "maxCopiedBytes exceeds the int32 GeoArrow representation ceiling.");
712
+ }
713
+ return Object.freeze({ maxBackingBytes, maxCopiedBytes: Math.min(maxCopiedBytes, maxBackingBytes) });
714
+ }
715
+ function fullyCoveredBacking(backing, descriptors) {
716
+ if (backing.byteLength === 0)
717
+ return true;
718
+ const ranges = descriptors
719
+ .filter(({ byteLength }) => byteLength > 0)
720
+ .map(({ byteOffset, byteLength }) => [byteOffset, byteOffset + byteLength])
721
+ .sort(([left], [right]) => left - right);
722
+ let coveredThrough = 0;
723
+ for (const [start, end] of ranges) {
724
+ if (start > coveredThrough)
725
+ return false;
726
+ if (end > coveredThrough)
727
+ coveredThrough = end;
728
+ }
729
+ return coveredThrough === backing.byteLength;
730
+ }
731
+ function isolateForeignBackings(buffers, limits) {
732
+ const byBacking = new Map();
733
+ for (const buffer of buffers) {
734
+ const descriptors = byBacking.get(buffer.data) ?? [];
735
+ descriptors.push(buffer);
736
+ byBacking.set(buffer.data, descriptors);
737
+ }
738
+ const unsafe = new Set();
739
+ let copiedBytes = 0;
740
+ let retainedBackingBytes = 0;
741
+ const resolved = resolveForeignCopyLimits(limits);
742
+ for (const [backing, descriptors] of byBacking) {
743
+ if (fullyCoveredBacking(backing, descriptors)) {
744
+ retainedBackingBytes += backing.byteLength;
745
+ continue;
746
+ }
747
+ unsafe.add(backing);
748
+ for (const descriptor of descriptors) {
749
+ if (descriptor.byteLength > resolved.maxCopiedBytes - copiedBytes) {
750
+ fail("copy-limit-exceeded", `Foreign Arrow isolation exceeds the ${resolved.maxCopiedBytes}-byte copy limit.`, {
751
+ actual: copiedBytes + descriptor.byteLength,
752
+ limit: resolved.maxCopiedBytes,
753
+ });
754
+ }
755
+ copiedBytes += descriptor.byteLength;
756
+ }
757
+ }
758
+ if (copiedBytes > resolved.maxBackingBytes - retainedBackingBytes) {
759
+ fail("copy-limit-exceeded", "Foreign Arrow isolation would exceed the bounded batch backing allocation.", {
760
+ actual: retainedBackingBytes + copiedBytes,
761
+ limit: resolved.maxBackingBytes,
762
+ });
763
+ }
764
+ if (unsafe.size === 0)
765
+ return Object.freeze({ buffers, copiedBytes: 0 });
766
+ const isolated = buffers.map((buffer) => {
767
+ if (!unsafe.has(buffer.data))
768
+ return buffer;
769
+ let data;
770
+ try {
771
+ data = new ArrayBuffer(buffer.byteLength);
772
+ new Uint8Array(data).set(new Uint8Array(buffer.data, buffer.byteOffset, buffer.byteLength));
773
+ }
774
+ catch (cause) {
775
+ fail("copy-limit-exceeded", `Unable to isolate foreign Arrow buffer "${buffer.id}".`, undefined, cause);
776
+ }
777
+ return {
778
+ ...buffer,
779
+ data,
780
+ byteOffset: 0,
781
+ };
782
+ });
783
+ return Object.freeze({ buffers: Object.freeze(isolated), copiedBytes });
784
+ }
785
+ function pushValidity(buffers, fieldName, node) {
786
+ if (node.nullBitmap === undefined || node.nullBitmap.byteLength === 0)
787
+ return;
788
+ const logicalBytes = Math.ceil(node.length / 8);
789
+ pushView(buffers, `${fieldName}.validity`, "validity", fieldName, exactPrefix(node.nullBitmap, logicalBytes, `${fieldName} validity`));
790
+ }
791
+ function geometryBuffersFromArrow(recordBatch, fieldIndex, fieldName, schemaMetadata, buffers) {
792
+ const node = vectorData(recordBatch, fieldIndex, fieldName);
793
+ const kind = schemaMetadata["honua.geoarrow.geometry.kind"];
794
+ const layout = schemaMetadata["honua.geoarrow.geometry.coordinate-layout"];
795
+ const dimensions = schemaMetadata["honua.geoarrow.geometry.dimensions"];
796
+ const names = dimensionNames(dimensions);
797
+ pushValidity(buffers, fieldName, node);
798
+ let coordinateNode;
799
+ if (kind === "point")
800
+ coordinateNode = node;
801
+ else if (kind === "linestring") {
802
+ pushView(buffers, `${fieldName}.offsets`, "offsets", fieldName, exactPrefix(requireView(node.valueOffsets, Int32Array, `${fieldName} offsets`), node.length + 1, `${fieldName} offsets`));
803
+ coordinateNode = requireChild(node, 0, fieldName);
804
+ }
805
+ else if (kind === "polygon") {
806
+ pushView(buffers, `${fieldName}.offsets`, "offsets", fieldName, exactPrefix(requireView(node.valueOffsets, Int32Array, `${fieldName} offsets`), node.length + 1, `${fieldName} offsets`));
807
+ const rings = requireChild(node, 0, `${fieldName}.rings`);
808
+ pushView(buffers, `${fieldName}.ring-offsets`, "offsets", fieldName, exactPrefix(requireView(rings.valueOffsets, Int32Array, `${fieldName} ring offsets`), rings.length + 1, `${fieldName} ring offsets`));
809
+ coordinateNode = requireChild(rings, 0, `${fieldName}.vertices`);
810
+ }
811
+ else
812
+ fail("unsupported-layout", `Unsupported Arrow GeoArrow geometry kind "${String(kind)}".`);
813
+ if (layout === "interleaved") {
814
+ const values = exactPrefix(requireView(requireChild(coordinateNode, 0, `${fieldName}.coordinates`).values, Float64Array, `${fieldName} coordinates`), coordinateNode.length * names.length, `${fieldName} coordinates`);
815
+ pushView(buffers, `${fieldName}.coordinates`, "geometry", fieldName, values);
816
+ }
817
+ else if (layout === "separated") {
818
+ names.forEach((name, index) => {
819
+ const values = exactPrefix(requireView(requireChild(coordinateNode, index, `${fieldName}.${name}`).values, Float64Array, `${fieldName}.${name}`), coordinateNode.length, `${fieldName}.${name}`);
820
+ pushView(buffers, `${fieldName}.${name}`, "geometry", fieldName, values);
821
+ });
822
+ }
823
+ else
824
+ fail("unsupported-layout", `Unsupported Arrow coordinate layout "${String(layout)}".`);
825
+ }
826
+ function fieldIndex(recordBatch, name) {
827
+ const index = recordBatch.schema.fields.findIndex((candidate) => candidate.name === name);
828
+ if (index < 0)
829
+ fail("invalid-batch", `Arrow field "${name}" is missing.`);
830
+ return index;
831
+ }
832
+ function expectedGeometryType(metadata) {
833
+ const dimensions = metadata["honua.geoarrow.geometry.dimensions"];
834
+ const names = dimensionNames(dimensions);
835
+ const coordinate = metadata["honua.geoarrow.geometry.coordinate-layout"] === "interleaved"
836
+ ? `FixedSizeList[${names.length}]<Float64>`
837
+ : `Struct<{${names.map((name) => `${name}:Float64`).join(", ")}}>`;
838
+ const kind = metadata["honua.geoarrow.geometry.kind"];
839
+ if (kind === "point")
840
+ return coordinate;
841
+ if (kind === "linestring")
842
+ return `List<${coordinate}>`;
843
+ if (kind === "polygon")
844
+ return `List<List<${coordinate}>>`;
845
+ fail("unsupported-layout", `Unsupported Arrow GeoArrow geometry kind "${String(kind)}".`);
846
+ }
847
+ function sameRecord(left, right) {
848
+ const leftEntries = Object.entries(left ?? {}).sort(([a], [b]) => a.localeCompare(b));
849
+ const rightEntries = Object.entries(right ?? {}).sort(([a], [b]) => a.localeCompare(b));
850
+ return (leftEntries.length === rightEntries.length &&
851
+ leftEntries.every(([key, value], index) => key === rightEntries[index]?.[0] && value === rightEntries[index]?.[1]));
852
+ }
853
+ function assertFieldShape(actual, expected, label) {
854
+ if (actual.name !== expected.name ||
855
+ actual.nullable !== expected.nullable ||
856
+ actual.type.name !== expected.type.name ||
857
+ !sameRecord(actual.type.parameters, expected.type.parameters) ||
858
+ !sameRecord(actual.metadata, expected.metadata)) {
859
+ fail("invalid-batch", `${label} does not match the embedded normative field shape.`);
860
+ }
861
+ const actualChildren = actual.children ?? [];
862
+ const expectedChildren = expected.children ?? [];
863
+ if (actualChildren.length !== expectedChildren.length) {
864
+ fail("invalid-batch", `${label} does not match the embedded normative child count.`);
865
+ }
866
+ actualChildren.forEach((child, index) => assertFieldShape(child, expectedChildren[index], `${label}.${child.name}`));
867
+ }
868
+ function assertArrowLeafType(field, label) {
869
+ if (field.type.children != null && (!Array.isArray(field.type.children) || field.type.children.length !== 0)) {
870
+ fail("invalid-batch", `${label} must not contain nested Arrow child fields.`);
871
+ }
872
+ }
873
+ function validateArrowSchema(recordBatch, schema, metadata) {
874
+ if (recordBatch.schema.fields.length !== schema.fields.length ||
875
+ recordBatch.schema.fields.some((candidate, index) => candidate.name !== schema.fields[index]?.name)) {
876
+ fail("invalid-batch", "Arrow RecordBatch fields do not match the embedded normative schema order.");
877
+ }
878
+ const geometryField = requiredMapValue(metadata, LAYOUT_META.geometryField);
879
+ const temporalField = mapValue(metadata, LAYOUT_META.temporalField);
880
+ const dictionaryField = mapValue(metadata, LAYOUT_META.dictionaryField);
881
+ const featureIdField = mapValue(metadata, LAYOUT_META.featureIdField);
882
+ for (let index = 0; index < schema.fields.length; index += 1) {
883
+ const expected = schema.fields[index];
884
+ const actual = recordBatch.schema.fields[index];
885
+ if (actual.nullable !== expected.nullable) {
886
+ fail("invalid-batch", `Arrow field "${expected.name}" nullability differs from the embedded schema.`);
887
+ }
888
+ const actualType = typeString(actual, `Arrow field "${expected.name}"`);
889
+ let expectedType;
890
+ if (expected.name === geometryField) {
891
+ expectedType = expectedGeometryType(schema.metadata ?? {});
892
+ const inferred = inferGeometry(actual);
893
+ assertFieldShape(inferred.field, expected, `Arrow geometry field "${expected.name}"`);
894
+ const actualMetadata = requireMap(actual.metadata, `recordBatch.schema.fields[${index}].metadata`);
895
+ if (mapValue(actualMetadata, "ARROW:extension:name") !== expected.metadata?.["ARROW:extension:name"] ||
896
+ mapValue(actualMetadata, "ARROW:extension:metadata") !== expected.metadata?.["ARROW:extension:metadata"]) {
897
+ fail("invalid-batch", `Arrow geometry field "${expected.name}" extension metadata has drifted.`);
898
+ }
899
+ }
900
+ else if (expected.name === temporalField) {
901
+ assertArrowLeafType(actual, `Arrow timestamp field "${expected.name}"`);
902
+ const unit = String(expected.type.parameters?.unit).toUpperCase();
903
+ const timezone = expected.type.parameters?.timezone;
904
+ expectedType = `Timestamp<${unit}${timezone === undefined ? "" : `, ${String(timezone)}`}>`;
905
+ }
906
+ else if (expected.name === dictionaryField) {
907
+ assertArrowLeafType(actual, `Arrow dictionary field "${expected.name}"`);
908
+ expectedType = "Dictionary<Int32, Utf8>";
909
+ }
910
+ else if (expected.name === featureIdField) {
911
+ assertArrowLeafType(actual, `Arrow feature id field "${expected.name}"`);
912
+ expectedType = "Uint32";
913
+ }
914
+ else
915
+ fail("invalid-batch", `Arrow field "${expected.name}" is not declared by the normative layout metadata.`);
916
+ if (actualType !== expectedType) {
917
+ fail("invalid-batch", `Arrow field "${expected.name}" uses ${actualType}; expected ${expectedType}.`, {
918
+ field: expected.name,
919
+ expected: expectedType,
920
+ actual: actualType,
921
+ });
922
+ }
923
+ if (expected.name === dictionaryField) {
924
+ const ordered = isRecord(actual.type) ? actual.type.isOrdered : undefined;
925
+ if (ordered !== expected.type.parameters?.ordered) {
926
+ fail("invalid-batch", `Arrow dictionary field "${expected.name}" ordered metadata has drifted.`);
927
+ }
928
+ }
929
+ }
930
+ }
931
+ /**
932
+ * Re-wrap one contiguous Apache Arrow RecordBatch as a transferable Honua
933
+ * batch. Supported Arrow buffers are retained by identity; sliced/shared
934
+ * layouts fail explicitly instead of performing an implicit copy.
935
+ */
936
+ export function fromApacheArrowRecordBatch(foreignRecordBatch, options = {}) {
937
+ const recordBatch = requireRecordBatch(foreignRecordBatch);
938
+ const arrowMetadata = requireMap(recordBatch.schema.metadata, "recordBatch.schema.metadata");
939
+ const limits = options.limits ?? {};
940
+ const storedSchemaJson = mapValue(arrowMetadata, TRANSPORT_META.schema);
941
+ const storedIdentityJson = mapValue(arrowMetadata, TRANSPORT_META.identity);
942
+ let schema;
943
+ let identity;
944
+ let metadata;
945
+ let id;
946
+ let sequence;
947
+ let rowOffset;
948
+ if (storedSchemaJson !== undefined || storedIdentityJson !== undefined) {
949
+ if (storedSchemaJson === undefined || storedIdentityJson === undefined) {
950
+ fail("invalid-batch", "Arrow RecordBatch contains incomplete Honua transport metadata.");
951
+ }
952
+ const storedSchema = parseJson(storedSchemaJson, "Arrow schema", limits);
953
+ schema = { ...storedSchema, id: options.schemaId ?? storedSchema.id };
954
+ const storedIdentity = parseJson(storedIdentityJson, "Arrow identity", limits);
955
+ identity = options.identity ?? storedIdentity;
956
+ metadata = arrowMetadata;
957
+ id = options.id ?? requiredMapValue(metadata, TRANSPORT_META.batchId);
958
+ sequence = options.sequence ?? parseInteger(mapValue(metadata, TRANSPORT_META.sequence), 0, "batch sequence");
959
+ rowOffset =
960
+ options.rowOffset ??
961
+ (mapValue(metadata, TRANSPORT_META.rowOffset) === undefined
962
+ ? undefined
963
+ : parseInteger(mapValue(metadata, TRANSPORT_META.rowOffset), 0, "batch rowOffset"));
964
+ }
965
+ else {
966
+ if (options.id === undefined || options.schemaId === undefined || options.identity === undefined) {
967
+ fail("invalid-input", "Importing a standard GeoArrow RecordBatch requires options.id, options.schemaId, and options.identity.");
968
+ }
969
+ if (arrowMetadata.size !== 0) {
970
+ fail("unsupported-layout", "Standard GeoArrow import does not silently discard custom schema metadata.");
971
+ }
972
+ const inferred = inferStandardSchema(recordBatch, options.schemaId);
973
+ schema = inferred.schema;
974
+ identity = options.identity;
975
+ metadata = inferred.metadata;
976
+ id = options.id;
977
+ sequence = options.sequence ?? 0;
978
+ rowOffset = options.rowOffset;
979
+ }
980
+ validateArrowSchema(recordBatch, schema, metadata);
981
+ const buffers = [];
982
+ const schemaMetadata = schema.metadata ?? {};
983
+ const geometryField = requiredMapValue(metadata, LAYOUT_META.geometryField);
984
+ geometryBuffersFromArrow(recordBatch, fieldIndex(recordBatch, geometryField), geometryField, schemaMetadata, buffers);
985
+ const temporalField = mapValue(metadata, LAYOUT_META.temporalField);
986
+ if (temporalField !== undefined) {
987
+ const node = vectorData(recordBatch, fieldIndex(recordBatch, temporalField), temporalField);
988
+ pushValidity(buffers, temporalField, node);
989
+ pushView(buffers, `${temporalField}.values`, "values", temporalField, exactPrefix(requireView(node.values, BigInt64Array, `${temporalField} values`), node.length, `${temporalField} values`));
990
+ }
991
+ const dictionaryField = mapValue(metadata, LAYOUT_META.dictionaryField);
992
+ if (dictionaryField !== undefined) {
993
+ const node = vectorData(recordBatch, fieldIndex(recordBatch, dictionaryField), dictionaryField);
994
+ pushValidity(buffers, dictionaryField, node);
995
+ pushView(buffers, `${dictionaryField}.indices`, "dictionary", dictionaryField, exactPrefix(requireView(node.values, Int32Array, `${dictionaryField} indices`), node.length, `${dictionaryField} indices`));
996
+ const dictionary = node.dictionary;
997
+ if (!dictionary || !Array.isArray(dictionary.data) || dictionary.data.length !== 1) {
998
+ fail("invalid-batch", `Arrow dictionary field "${dictionaryField}" has no contiguous dictionary vector.`);
999
+ }
1000
+ const dictionaryNode = dictionary.data[0];
1001
+ if (dictionaryNode.nullBitmap !== undefined && dictionaryNode.nullBitmap.byteLength !== 0) {
1002
+ fail("unsupported-layout", `Arrow dictionary field "${dictionaryField}" contains nullable dictionary values.`);
1003
+ }
1004
+ const dictionaryOffsets = exactPrefix(requireView(dictionaryNode.valueOffsets, Int32Array, `${dictionaryField} dictionary offsets`), dictionaryNode.length + 1, `${dictionaryField} dictionary offsets`);
1005
+ pushView(buffers, `${dictionaryField}.offsets`, "offsets", dictionaryField, dictionaryOffsets);
1006
+ const dictionaryByteLength = dictionaryOffsets[dictionaryOffsets.length - 1];
1007
+ pushView(buffers, `${dictionaryField}.values`, "dictionary", dictionaryField, exactPrefix(requireView(dictionaryNode.values, Uint8Array, `${dictionaryField} dictionary values`), dictionaryByteLength, `${dictionaryField} dictionary values`));
1008
+ }
1009
+ const featureIdField = mapValue(metadata, LAYOUT_META.featureIdField);
1010
+ if (featureIdField !== undefined) {
1011
+ const node = vectorData(recordBatch, fieldIndex(recordBatch, featureIdField), featureIdField);
1012
+ if (node.nullBitmap !== undefined && node.nullBitmap.byteLength !== 0) {
1013
+ fail("unsupported-layout", `Arrow feature id field "${featureIdField}" contains null values.`);
1014
+ }
1015
+ pushView(buffers, `${featureIdField}.values`, "values", featureIdField, exactPrefix(requireView(node.values, Uint32Array, `${featureIdField} values`), node.length, `${featureIdField} values`));
1016
+ }
1017
+ const isolated = isolateForeignBackings(buffers, limits);
1018
+ const batch = createColumnarBatch({
1019
+ id,
1020
+ sequence,
1021
+ rowOffset,
1022
+ rowCount: recordBatch.numRows,
1023
+ schema,
1024
+ identity,
1025
+ buffers: isolated.buffers,
1026
+ }, limits);
1027
+ inspectGeoArrowBatch(batch, limits);
1028
+ const metrics = inspectColumnarBatch(batch, limits);
1029
+ const resultMetrics = Object.freeze({
1030
+ rows: batch.rowCount,
1031
+ backingBytes: metrics.backingBytes,
1032
+ referencedBytes: metrics.logicalBytes,
1033
+ copiedBytes: isolated.copiedBytes,
1034
+ });
1035
+ return Object.freeze({ batch, metrics: resultMetrics });
1036
+ }