@walkthru-earth/objex 1.3.0 → 1.4.0

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 (182) hide show
  1. package/LICENSE +5 -0
  2. package/README.md +20 -12
  3. package/dist/components/browser/FileTreeSidebar.svelte +32 -17
  4. package/dist/components/layout/AboutSheet.svelte +5 -2
  5. package/dist/components/layout/ConnectionDialog.svelte +1 -1
  6. package/dist/components/layout/SettingsSheet.svelte +237 -0
  7. package/dist/components/layout/SettingsSheet.svelte.d.ts +6 -0
  8. package/dist/components/layout/Sidebar.svelte +73 -6
  9. package/dist/components/layout/Sidebar.svelte.d.ts +4 -1
  10. package/dist/components/layout/StatusBar.svelte +1 -1
  11. package/dist/components/layout/TabBar.svelte +2 -2
  12. package/dist/components/ui/context-menu/context-menu-radio-group.svelte.d.ts +1 -1
  13. package/dist/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte.d.ts +1 -1
  14. package/dist/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte.d.ts +1 -1
  15. package/dist/components/ui/input/input.svelte.d.ts +1 -1
  16. package/dist/components/ui/resizable/index.d.ts +1 -1
  17. package/dist/components/ui/resizable/index.js +2 -2
  18. package/dist/components/ui/slider/index.d.ts +3 -0
  19. package/dist/components/ui/slider/index.js +5 -0
  20. package/dist/components/ui/slider/range-slider.svelte +94 -0
  21. package/dist/components/ui/slider/range-slider.svelte.d.ts +21 -0
  22. package/dist/components/ui/slider/slider.svelte +83 -0
  23. package/dist/components/ui/slider/slider.svelte.d.ts +7 -0
  24. package/dist/components/viewers/ArchiveViewer.svelte +2 -2
  25. package/dist/components/viewers/CodeViewer.svelte +31 -22
  26. package/dist/components/viewers/CogControls.svelte +338 -184
  27. package/dist/components/viewers/CogControls.svelte.d.ts +33 -10
  28. package/dist/components/viewers/CogViewer.svelte +320 -119
  29. package/dist/components/viewers/CopcViewer.svelte +1 -1
  30. package/dist/components/viewers/FlatGeobufViewer.svelte +1 -1
  31. package/dist/components/viewers/GeoParquetMapViewer.svelte +6 -6
  32. package/dist/components/viewers/GeoParquetMapViewer.svelte.d.ts +1 -1
  33. package/dist/components/viewers/ImageViewer.svelte +2 -2
  34. package/dist/components/viewers/MarkdownViewer.svelte +12 -9
  35. package/dist/components/viewers/MediaViewer.svelte +2 -2
  36. package/dist/components/viewers/ModelViewer.svelte +1 -1
  37. package/dist/components/viewers/MultiCogViewer.svelte +467 -102
  38. package/dist/components/viewers/MultiCogViewer.svelte.d.ts +1 -1
  39. package/dist/components/viewers/NotebookViewer.svelte +6 -3
  40. package/dist/components/viewers/PdfViewer.svelte +2 -2
  41. package/dist/components/viewers/PmtilesViewer.svelte +3 -6
  42. package/dist/components/viewers/RawViewer.svelte +6 -3
  43. package/dist/components/viewers/StacMapViewer.svelte +10 -2
  44. package/dist/components/viewers/StacMosaicViewer.svelte +1800 -362
  45. package/dist/components/viewers/StacMosaicViewer.svelte.d.ts +1 -1
  46. package/dist/components/viewers/StacTabViewer.svelte +24 -13
  47. package/dist/components/viewers/StacTabViewer.svelte.d.ts +1 -1
  48. package/dist/components/viewers/TableGrid.svelte +4 -4
  49. package/dist/components/viewers/TableStatusBar.svelte +1 -1
  50. package/dist/components/viewers/TableToolbar.svelte +1 -1
  51. package/dist/components/viewers/TableViewer.svelte +25 -17
  52. package/dist/components/viewers/TableViewer.svelte.d.ts +1 -0
  53. package/dist/components/viewers/ViewerRouter.svelte +16 -8
  54. package/dist/components/viewers/ZarrMapViewer.svelte +11 -9
  55. package/dist/components/viewers/ZarrViewer.svelte +4 -4
  56. package/dist/components/viewers/cog/ChannelPicker.svelte +83 -0
  57. package/dist/components/viewers/cog/ChannelPicker.svelte.d.ts +13 -0
  58. package/dist/components/viewers/cog/PixelInspectorPanel.svelte +87 -0
  59. package/dist/components/viewers/cog/PixelInspectorPanel.svelte.d.ts +17 -0
  60. package/dist/components/viewers/cog/buildRgbLayer.d.ts +78 -0
  61. package/dist/components/viewers/cog/buildRgbLayer.js +176 -0
  62. package/dist/components/viewers/map/AttributeTable.svelte +1 -1
  63. package/dist/components/viewers/map/MapContainer.svelte +37 -11
  64. package/dist/components/viewers/pmtiles/PmtilesArchiveView.svelte +1 -1
  65. package/dist/components/viewers/pmtiles/PmtilesTileInspector.svelte +1 -1
  66. package/dist/components/viewers/stac/StacDatetimeBar.svelte +175 -0
  67. package/dist/components/viewers/stac/StacDatetimeBar.svelte.d.ts +10 -0
  68. package/dist/components/viewers/stac/StacFilterPanel.svelte +243 -0
  69. package/dist/components/viewers/stac/StacFilterPanel.svelte.d.ts +14 -0
  70. package/dist/components/viewers/stac/StacItemInspector.svelte +223 -0
  71. package/dist/components/viewers/stac/StacItemInspector.svelte.d.ts +10 -0
  72. package/dist/components/viewers/stac/StacItemStrip.svelte +228 -0
  73. package/dist/components/viewers/stac/StacItemStrip.svelte.d.ts +12 -0
  74. package/dist/file-icons/index.d.ts +1 -1
  75. package/dist/file-icons/index.js +1 -1
  76. package/dist/i18n/ar.js +110 -2
  77. package/dist/i18n/en.js +110 -2
  78. package/dist/index.d.ts +2 -28
  79. package/dist/index.js +7 -23
  80. package/dist/query/engine.d.ts +10 -0
  81. package/dist/query/source.js +1 -1
  82. package/dist/query/stac-source-factory.d.ts +65 -0
  83. package/dist/query/stac-source-factory.js +77 -0
  84. package/dist/query/stac-source-parquet.d.ts +135 -0
  85. package/dist/query/stac-source-parquet.js +465 -0
  86. package/dist/query/wasm.d.ts +8 -0
  87. package/dist/query/wasm.js +304 -2
  88. package/dist/storage/presign.js +1 -1
  89. package/dist/storage/providers.js +5 -5
  90. package/dist/stores/config.svelte.d.ts +15 -0
  91. package/dist/stores/config.svelte.js +46 -0
  92. package/dist/stores/connections.svelte.d.ts +2 -2
  93. package/dist/stores/connections.svelte.js +1 -2
  94. package/dist/stores/files.svelte.d.ts +1 -1
  95. package/dist/stores/files.svelte.js +1 -1
  96. package/dist/stores/query-history.svelte.js +1 -1
  97. package/dist/stores/settings.svelte.d.ts +16 -1
  98. package/dist/stores/settings.svelte.js +104 -48
  99. package/dist/stores/tabs.svelte.d.ts +3 -0
  100. package/dist/stores/tabs.svelte.js +17 -0
  101. package/dist/utils/cog-histogram.d.ts +121 -0
  102. package/dist/utils/cog-histogram.js +424 -0
  103. package/dist/utils/cog.d.ts +200 -60
  104. package/dist/utils/cog.js +377 -114
  105. package/dist/utils/colormap-sprite.d.ts +0 -9
  106. package/dist/utils/colormap-sprite.js +0 -21
  107. package/dist/utils/deck.d.ts +16 -12
  108. package/dist/utils/deck.js +10 -4
  109. package/dist/utils/pmtiles-tile.js +2 -2
  110. package/dist/utils/{url.d.ts → signed-url.d.ts} +15 -1
  111. package/dist/utils/{url.js → signed-url.js} +32 -10
  112. package/dist/utils/url-state.d.ts +36 -0
  113. package/dist/utils/url-state.js +72 -2
  114. package/dist/utils/zarr-tab.d.ts +1 -2
  115. package/dist/utils/zarr-tab.js +1 -2
  116. package/dist/utils/zarr.d.ts +0 -17
  117. package/dist/utils/zarr.js +1 -45
  118. package/package.json +55 -84
  119. package/dist/components/browser/Breadcrumb.svelte +0 -50
  120. package/dist/components/browser/Breadcrumb.svelte.d.ts +0 -7
  121. package/dist/components/browser/CreateFolderDialog.svelte +0 -98
  122. package/dist/components/browser/CreateFolderDialog.svelte.d.ts +0 -6
  123. package/dist/components/browser/DeleteConfirmDialog.svelte +0 -90
  124. package/dist/components/browser/DeleteConfirmDialog.svelte.d.ts +0 -8
  125. package/dist/components/browser/DropZone.svelte +0 -83
  126. package/dist/components/browser/DropZone.svelte.d.ts +0 -7
  127. package/dist/components/browser/FileBrowser.svelte +0 -252
  128. package/dist/components/browser/FileBrowser.svelte.d.ts +0 -3
  129. package/dist/components/browser/FileRow.svelte +0 -117
  130. package/dist/components/browser/FileRow.svelte.d.ts +0 -9
  131. package/dist/components/browser/RenameDialog.svelte +0 -101
  132. package/dist/components/browser/RenameDialog.svelte.d.ts +0 -8
  133. package/dist/components/browser/SearchBar.svelte +0 -40
  134. package/dist/components/browser/SearchBar.svelte.d.ts +0 -6
  135. package/dist/components/browser/UploadButton.svelte +0 -65
  136. package/dist/components/browser/UploadButton.svelte.d.ts +0 -3
  137. package/dist/query/stac-geoparquet.d.ts +0 -31
  138. package/dist/query/stac-geoparquet.js +0 -136
  139. package/dist/utils/clipboard.d.ts +0 -13
  140. package/dist/utils/clipboard.js +0 -38
  141. package/dist/utils/cloud-url.d.ts +0 -27
  142. package/dist/utils/cloud-url.js +0 -61
  143. package/dist/utils/column-types.d.ts +0 -5
  144. package/dist/utils/column-types.js +0 -137
  145. package/dist/utils/connection-identity.d.ts +0 -51
  146. package/dist/utils/connection-identity.js +0 -97
  147. package/dist/utils/error.d.ts +0 -8
  148. package/dist/utils/error.js +0 -12
  149. package/dist/utils/evidence-context.d.ts +0 -22
  150. package/dist/utils/evidence-context.js +0 -56
  151. package/dist/utils/export.d.ts +0 -22
  152. package/dist/utils/export.js +0 -76
  153. package/dist/utils/file-sort.d.ts +0 -20
  154. package/dist/utils/file-sort.js +0 -41
  155. package/dist/utils/format.d.ts +0 -24
  156. package/dist/utils/format.js +0 -78
  157. package/dist/utils/geoarrow.d.ts +0 -32
  158. package/dist/utils/geoarrow.js +0 -672
  159. package/dist/utils/geometry-type.d.ts +0 -52
  160. package/dist/utils/geometry-type.js +0 -76
  161. package/dist/utils/hex.d.ts +0 -10
  162. package/dist/utils/hex.js +0 -27
  163. package/dist/utils/host-detection.d.ts +0 -23
  164. package/dist/utils/host-detection.js +0 -95
  165. package/dist/utils/local-storage.d.ts +0 -16
  166. package/dist/utils/local-storage.js +0 -37
  167. package/dist/utils/markdown-sql.d.ts +0 -30
  168. package/dist/utils/markdown-sql.js +0 -72
  169. package/dist/utils/notebook.d.ts +0 -59
  170. package/dist/utils/notebook.js +0 -211
  171. package/dist/utils/parquet-metadata.d.ts +0 -64
  172. package/dist/utils/parquet-metadata.js +0 -262
  173. package/dist/utils/stac-geoparquet.d.ts +0 -90
  174. package/dist/utils/stac-geoparquet.js +0 -223
  175. package/dist/utils/stac-hydrate.d.ts +0 -38
  176. package/dist/utils/stac-hydrate.js +0 -243
  177. package/dist/utils/stac.d.ts +0 -136
  178. package/dist/utils/stac.js +0 -176
  179. package/dist/utils/storage-url.d.ts +0 -90
  180. package/dist/utils/storage-url.js +0 -568
  181. package/dist/utils/wkb.d.ts +0 -43
  182. package/dist/utils/wkb.js +0 -359
@@ -1,672 +0,0 @@
1
- /**
2
- * Direct WKB → GeoArrow bridge (zero-copy).
3
- *
4
- * Reads raw WKB binary directly into pre-allocated Arrow typed arrays
5
- * without any intermediate JS object allocation. No parseWKB(), no GeoJSON.
6
- *
7
- * Data flow: WKB Uint8Array[] → DataView binary reads → Float64Array/Int32Array
8
- * → arrow.makeData() → arrow.Table with ARROW:extension:name metadata
9
- */
10
- import { Field, FixedSizeList, Float64, List, makeData, RecordBatch, Schema, Struct, Table, Utf8 } from 'apache-arrow';
11
- /** Map DuckDB ST_GeometryType output to our normalized type. */
12
- export function normalizeGeomType(raw) {
13
- const s = raw.toUpperCase().replace(/\s+/g, '');
14
- if (s === 'POINT')
15
- return 'point';
16
- if (s === 'LINESTRING')
17
- return 'linestring';
18
- if (s === 'POLYGON')
19
- return 'polygon';
20
- if (s === 'MULTIPOINT')
21
- return 'multipoint';
22
- if (s === 'MULTILINESTRING')
23
- return 'multilinestring';
24
- if (s === 'MULTIPOLYGON')
25
- return 'multipolygon';
26
- return 'polygon'; // fallback
27
- }
28
- /** GeoArrow extension name for each geometry type. */
29
- const EXTENSION_NAMES = {
30
- point: 'geoarrow.point',
31
- linestring: 'geoarrow.linestring',
32
- polygon: 'geoarrow.polygon',
33
- multipoint: 'geoarrow.multipoint',
34
- multilinestring: 'geoarrow.multilinestring',
35
- multipolygon: 'geoarrow.multipolygon'
36
- };
37
- /** Read WKB/EWKB header — 5 bytes + optional SRID. No allocations. */
38
- function readWkbHeader(wkb) {
39
- if (wkb.length < 5)
40
- return null;
41
- const le = wkb[0] === 1;
42
- const dv = new DataView(wkb.buffer, wkb.byteOffset, wkb.byteLength);
43
- const rawType = dv.getUint32(1, le);
44
- let headerSize = 5; // byte_order(1) + type(4)
45
- // EWKB SRID flag — 4 extra bytes after type
46
- if ((rawType & 0x20000000) !== 0)
47
- headerSize += 4;
48
- // EWKB dimension flags
49
- const ewkbZ = (rawType & 0x80000000) !== 0;
50
- const ewkbM = (rawType & 0x40000000) !== 0;
51
- // Strip all EWKB flags to get base type
52
- let type = rawType & 0x0fffffff;
53
- // ISO Z/M ranges: 1001–1006 (Z), 2001–2006 (M), 3001–3006 (ZM)
54
- let isoZ = false;
55
- let isoM = false;
56
- if (type > 3000) {
57
- isoZ = true;
58
- isoM = true;
59
- type -= 3000;
60
- }
61
- else if (type > 2000) {
62
- isoM = true;
63
- type -= 2000;
64
- }
65
- else if (type > 1000) {
66
- isoZ = true;
67
- type -= 1000;
68
- }
69
- const dims = (ewkbZ || isoZ ? 1 : 0) + (ewkbM || isoM ? 1 : 0);
70
- const coordStride = (2 + dims) * 8; // 16, 24, or 32 bytes
71
- return { type, le, coordStride, dataOffset: headerSize };
72
- }
73
- /** Classify WKB type from first 5 bytes. No full parse. */
74
- function classifyWkbType(wkb) {
75
- const h = readWkbHeader(wkb);
76
- if (!h)
77
- return null;
78
- switch (h.type) {
79
- case 1:
80
- return 'point';
81
- case 2:
82
- return 'linestring';
83
- case 3:
84
- return 'polygon';
85
- case 4:
86
- return 'multipoint';
87
- case 5:
88
- return 'multilinestring';
89
- case 6:
90
- return 'multipolygon';
91
- default:
92
- return null;
93
- }
94
- }
95
- function newBounds() {
96
- return { minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity };
97
- }
98
- /** Update bounds, skipping NaN coordinates (EMPTY geometries, invalid data). */
99
- function expandBounds(b, x, y) {
100
- if (Number.isNaN(x) || Number.isNaN(y))
101
- return;
102
- if (x < b.minX)
103
- b.minX = x;
104
- if (y < b.minY)
105
- b.minY = y;
106
- if (x > b.maxX)
107
- b.maxX = x;
108
- if (y > b.maxY)
109
- b.maxY = y;
110
- }
111
- // ─── Arrow coordinate type (shared) ─────────────────────────────────
112
- const coordField = new Field('xy', new Float64());
113
- const coordType = new FixedSizeList(2, coordField);
114
- /** Build FixedSizeList<Float64> Data from a pre-allocated Float64Array. */
115
- function makeCoordData(coords, numPoints) {
116
- const floatData = makeData({ type: new Float64(), length: coords.length, data: coords });
117
- return makeData({ type: coordType, length: numPoints, nullCount: 0, child: floatData });
118
- }
119
- // ─── Direct WKB → Arrow builders (zero intermediate objects) ─────────
120
- /**
121
- * POINT: [byte_order:1][type:4][x:8][y:8]
122
- * Direct read — 2 Float64 reads per geometry. Zero parseWKB.
123
- */
124
- function buildPointData(wkbs, b) {
125
- const n = wkbs.length;
126
- const coords = new Float64Array(n * 2);
127
- for (let i = 0; i < n; i++) {
128
- const wkb = wkbs[i];
129
- const h = readWkbHeader(wkb);
130
- if (!h || h.type !== 1) {
131
- coords[i * 2] = 0;
132
- coords[i * 2 + 1] = 0;
133
- continue;
134
- }
135
- const dv = new DataView(wkb.buffer, wkb.byteOffset, wkb.byteLength);
136
- const x = dv.getFloat64(h.dataOffset, h.le);
137
- const y = dv.getFloat64(h.dataOffset + 8, h.le);
138
- coords[i * 2] = x;
139
- coords[i * 2 + 1] = y;
140
- expandBounds(b, x, y);
141
- }
142
- return makeCoordData(coords, n);
143
- }
144
- /**
145
- * LINESTRING: [byte_order:1][type:4][num_points:4][x:8,y:8 × n]
146
- * Two-pass: count first to pre-allocate, then extract.
147
- */
148
- function buildLineStringData(wkbs, b) {
149
- const n = wkbs.length;
150
- const geomOffsets = new Int32Array(n + 1);
151
- let totalCoords = 0;
152
- // Pass 1: count coordinates
153
- for (let i = 0; i < n; i++) {
154
- geomOffsets[i] = totalCoords;
155
- const h = readWkbHeader(wkbs[i]);
156
- if (!h || h.type !== 2)
157
- continue;
158
- const dv = new DataView(wkbs[i].buffer, wkbs[i].byteOffset, wkbs[i].byteLength);
159
- const numPts = dv.getUint32(h.dataOffset, h.le);
160
- totalCoords += numPts;
161
- }
162
- geomOffsets[n] = totalCoords;
163
- // Pass 2: extract coordinates directly from binary
164
- const coords = new Float64Array(totalCoords * 2);
165
- let ci = 0;
166
- for (const wkb of wkbs) {
167
- const h = readWkbHeader(wkb);
168
- if (!h || h.type !== 2)
169
- continue;
170
- const dv = new DataView(wkb.buffer, wkb.byteOffset, wkb.byteLength);
171
- const numPts = dv.getUint32(h.dataOffset, h.le);
172
- let off = h.dataOffset + 4;
173
- for (let j = 0; j < numPts; j++) {
174
- const x = dv.getFloat64(off, h.le);
175
- const y = dv.getFloat64(off + 8, h.le);
176
- coords[ci++] = x;
177
- coords[ci++] = y;
178
- expandBounds(b, x, y);
179
- off += h.coordStride;
180
- }
181
- }
182
- const fslData = makeCoordData(coords, totalCoords);
183
- const listType = new List(new Field('vertices', coordType));
184
- return makeData({
185
- type: listType,
186
- length: n,
187
- nullCount: 0,
188
- valueOffsets: geomOffsets,
189
- child: fslData
190
- });
191
- }
192
- /**
193
- * POLYGON: [byte_order:1][type:4][num_rings:4]
194
- * { [num_points:4][x:8,y:8 × n] } × rings
195
- * Two-pass: count rings+coords first, then extract.
196
- */
197
- function buildPolygonData(wkbs, b) {
198
- const n = wkbs.length;
199
- const geomOffsets = new Int32Array(n + 1);
200
- let totalRings = 0;
201
- let totalCoords = 0;
202
- // Pass 1: count rings and coordinates
203
- for (let i = 0; i < n; i++) {
204
- geomOffsets[i] = totalRings;
205
- const h = readWkbHeader(wkbs[i]);
206
- if (!h || h.type !== 3)
207
- continue;
208
- const dv = new DataView(wkbs[i].buffer, wkbs[i].byteOffset, wkbs[i].byteLength);
209
- const numRings = dv.getUint32(h.dataOffset, h.le);
210
- let off = h.dataOffset + 4;
211
- for (let r = 0; r < numRings; r++) {
212
- const numPts = dv.getUint32(off, h.le);
213
- off += 4 + numPts * h.coordStride;
214
- totalCoords += numPts;
215
- totalRings++;
216
- }
217
- }
218
- geomOffsets[n] = totalRings;
219
- // Pass 2: extract coordinates and ring offsets
220
- const ringOffsets = new Int32Array(totalRings + 1);
221
- const coords = new Float64Array(totalCoords * 2);
222
- let ri = 0;
223
- let ci = 0;
224
- for (const wkb of wkbs) {
225
- const h = readWkbHeader(wkb);
226
- if (!h || h.type !== 3)
227
- continue;
228
- const dv = new DataView(wkb.buffer, wkb.byteOffset, wkb.byteLength);
229
- const numRings = dv.getUint32(h.dataOffset, h.le);
230
- let off = h.dataOffset + 4;
231
- for (let r = 0; r < numRings; r++) {
232
- ringOffsets[ri++] = ci >> 1; // coordIndex = ci / 2
233
- const numPts = dv.getUint32(off, h.le);
234
- off += 4;
235
- for (let j = 0; j < numPts; j++) {
236
- const x = dv.getFloat64(off, h.le);
237
- const y = dv.getFloat64(off + 8, h.le);
238
- coords[ci++] = x;
239
- coords[ci++] = y;
240
- expandBounds(b, x, y);
241
- off += h.coordStride;
242
- }
243
- }
244
- }
245
- ringOffsets[totalRings] = ci >> 1;
246
- const coordCount = ci >> 1;
247
- const fslData = makeCoordData(coords, coordCount);
248
- const ringListType = new List(new Field('vertices', coordType));
249
- const ringListData = makeData({
250
- type: ringListType,
251
- length: totalRings,
252
- nullCount: 0,
253
- valueOffsets: ringOffsets,
254
- child: fslData
255
- });
256
- const polyType = new List(new Field('rings', ringListType));
257
- return makeData({
258
- type: polyType,
259
- length: n,
260
- nullCount: 0,
261
- valueOffsets: geomOffsets,
262
- child: ringListData
263
- });
264
- }
265
- /** MultiPoint has same Arrow structure as LineString: List<FixedSizeList(2)>. */
266
- function buildMultiPointData(wkbs, b) {
267
- // MultiPoint WKB: [header][num_points:4]{Point WKB × n}
268
- // Each inner point has its own WKB header.
269
- const n = wkbs.length;
270
- const geomOffsets = new Int32Array(n + 1);
271
- let totalCoords = 0;
272
- // Pass 1: count
273
- for (let i = 0; i < n; i++) {
274
- geomOffsets[i] = totalCoords;
275
- const h = readWkbHeader(wkbs[i]);
276
- if (!h || h.type !== 4)
277
- continue;
278
- const dv = new DataView(wkbs[i].buffer, wkbs[i].byteOffset, wkbs[i].byteLength);
279
- totalCoords += dv.getUint32(h.dataOffset, h.le);
280
- }
281
- geomOffsets[n] = totalCoords;
282
- // Pass 2: extract from inner point WKBs
283
- const coords = new Float64Array(totalCoords * 2);
284
- let ci = 0;
285
- for (const wkb of wkbs) {
286
- const h = readWkbHeader(wkb);
287
- if (!h || h.type !== 4)
288
- continue;
289
- const dv = new DataView(wkb.buffer, wkb.byteOffset, wkb.byteLength);
290
- const numPts = dv.getUint32(h.dataOffset, h.le);
291
- let off = h.dataOffset + 4;
292
- for (let j = 0; j < numPts; j++) {
293
- // Each inner point is a full WKB: skip its header to get coords
294
- const innerH = readWkbHeader(new Uint8Array(wkb.buffer, wkb.byteOffset + off, wkb.byteLength - off));
295
- if (innerH) {
296
- const x = dv.getFloat64(off + innerH.dataOffset, innerH.le);
297
- const y = dv.getFloat64(off + innerH.dataOffset + 8, innerH.le);
298
- coords[ci++] = x;
299
- coords[ci++] = y;
300
- expandBounds(b, x, y);
301
- off += innerH.dataOffset + innerH.coordStride;
302
- }
303
- else {
304
- coords[ci++] = 0;
305
- coords[ci++] = 0;
306
- off += 21; // minimum point WKB size
307
- }
308
- }
309
- }
310
- const fslData = makeCoordData(coords, totalCoords);
311
- const listType = new List(new Field('vertices', coordType));
312
- return makeData({
313
- type: listType,
314
- length: n,
315
- nullCount: 0,
316
- valueOffsets: geomOffsets,
317
- child: fslData
318
- });
319
- }
320
- /**
321
- * MULTILINESTRING: [header][num_lines:4]{LineString WKB × n}
322
- * Each inner LineString has its own WKB header.
323
- */
324
- function buildMultiLineStringData(wkbs, b) {
325
- const n = wkbs.length;
326
- const geomOffsetsArr = [0];
327
- let totalLines = 0;
328
- let totalCoords = 0;
329
- // Pass 1: count lines and coordinates
330
- for (const wkb of wkbs) {
331
- const h = readWkbHeader(wkb);
332
- if (!h || h.type !== 5) {
333
- geomOffsetsArr.push(totalLines);
334
- continue;
335
- }
336
- const dv = new DataView(wkb.buffer, wkb.byteOffset, wkb.byteLength);
337
- const numLines = dv.getUint32(h.dataOffset, h.le);
338
- let off = h.dataOffset + 4;
339
- for (let l = 0; l < numLines; l++) {
340
- const innerH = readWkbHeader(new Uint8Array(wkb.buffer, wkb.byteOffset + off, wkb.byteLength - off));
341
- if (!innerH)
342
- break;
343
- const innerDv = new DataView(wkb.buffer, wkb.byteOffset + off, wkb.byteLength - off);
344
- const numPts = innerDv.getUint32(innerH.dataOffset, innerH.le);
345
- totalCoords += numPts;
346
- off += innerH.dataOffset + 4 + numPts * innerH.coordStride;
347
- totalLines++;
348
- }
349
- geomOffsetsArr.push(totalLines);
350
- }
351
- // Pass 2: extract
352
- const geomOffsets = new Int32Array(geomOffsetsArr);
353
- const lineOffsets = new Int32Array(totalLines + 1);
354
- const coords = new Float64Array(totalCoords * 2);
355
- let li = 0;
356
- let ci = 0;
357
- for (const wkb of wkbs) {
358
- const h = readWkbHeader(wkb);
359
- if (!h || h.type !== 5)
360
- continue;
361
- const dv = new DataView(wkb.buffer, wkb.byteOffset, wkb.byteLength);
362
- const numLines = dv.getUint32(h.dataOffset, h.le);
363
- let off = h.dataOffset + 4;
364
- for (let l = 0; l < numLines; l++) {
365
- lineOffsets[li++] = ci >> 1;
366
- const innerH = readWkbHeader(new Uint8Array(wkb.buffer, wkb.byteOffset + off, wkb.byteLength - off));
367
- if (!innerH)
368
- break;
369
- const numPts = new DataView(wkb.buffer, wkb.byteOffset + off, wkb.byteLength - off).getUint32(innerH.dataOffset, innerH.le);
370
- let ptOff = off + innerH.dataOffset + 4;
371
- for (let j = 0; j < numPts; j++) {
372
- const x = dv.getFloat64(ptOff, innerH.le);
373
- const y = dv.getFloat64(ptOff + 8, innerH.le);
374
- coords[ci++] = x;
375
- coords[ci++] = y;
376
- expandBounds(b, x, y);
377
- ptOff += innerH.coordStride;
378
- }
379
- off = ptOff;
380
- }
381
- }
382
- lineOffsets[totalLines] = ci >> 1;
383
- const fslData = makeCoordData(coords, ci >> 1);
384
- const lineListType = new List(new Field('vertices', coordType));
385
- const lineListData = makeData({
386
- type: lineListType,
387
- length: totalLines,
388
- nullCount: 0,
389
- valueOffsets: lineOffsets,
390
- child: fslData
391
- });
392
- const multiLineType = new List(new Field('lines', lineListType));
393
- return makeData({
394
- type: multiLineType,
395
- length: n,
396
- nullCount: 0,
397
- valueOffsets: geomOffsets,
398
- child: lineListData
399
- });
400
- }
401
- /**
402
- * MULTIPOLYGON: [header][num_polys:4]{Polygon WKB × n}
403
- * Each inner Polygon has its own WKB header.
404
- */
405
- function buildMultiPolygonData(wkbs, b) {
406
- const n = wkbs.length;
407
- const geomOffsetsArr = [0];
408
- let totalPolys = 0;
409
- let totalRings = 0;
410
- let totalCoords = 0;
411
- // Pass 1: count polygons, rings, coordinates
412
- for (const wkb of wkbs) {
413
- const h = readWkbHeader(wkb);
414
- if (!h || h.type !== 6) {
415
- geomOffsetsArr.push(totalPolys);
416
- continue;
417
- }
418
- const dv = new DataView(wkb.buffer, wkb.byteOffset, wkb.byteLength);
419
- const numPolys = dv.getUint32(h.dataOffset, h.le);
420
- let off = h.dataOffset + 4;
421
- for (let p = 0; p < numPolys; p++) {
422
- const innerH = readWkbHeader(new Uint8Array(wkb.buffer, wkb.byteOffset + off, wkb.byteLength - off));
423
- if (!innerH)
424
- break;
425
- const innerDv = new DataView(wkb.buffer, wkb.byteOffset + off, wkb.byteLength - off);
426
- const numRings = innerDv.getUint32(innerH.dataOffset, innerH.le);
427
- let ringOff = innerH.dataOffset + 4;
428
- for (let r = 0; r < numRings; r++) {
429
- const numPts = innerDv.getUint32(ringOff, innerH.le);
430
- ringOff += 4 + numPts * innerH.coordStride;
431
- totalCoords += numPts;
432
- totalRings++;
433
- }
434
- off += ringOff;
435
- totalPolys++;
436
- }
437
- geomOffsetsArr.push(totalPolys);
438
- }
439
- // Pass 2: extract
440
- const geomOffsets = new Int32Array(geomOffsetsArr);
441
- const polyOffsets = new Int32Array(totalPolys + 1);
442
- const ringOffsets = new Int32Array(totalRings + 1);
443
- const coords = new Float64Array(totalCoords * 2);
444
- let pi = 0;
445
- let ri = 0;
446
- let ci = 0;
447
- for (const wkb of wkbs) {
448
- const h = readWkbHeader(wkb);
449
- if (!h || h.type !== 6)
450
- continue;
451
- const dv = new DataView(wkb.buffer, wkb.byteOffset, wkb.byteLength);
452
- const numPolys = dv.getUint32(h.dataOffset, h.le);
453
- let off = h.dataOffset + 4;
454
- for (let p = 0; p < numPolys; p++) {
455
- polyOffsets[pi++] = ri;
456
- const innerH = readWkbHeader(new Uint8Array(wkb.buffer, wkb.byteOffset + off, wkb.byteLength - off));
457
- if (!innerH)
458
- break;
459
- const innerDv = new DataView(wkb.buffer, wkb.byteOffset + off, wkb.byteLength - off);
460
- const numRings = innerDv.getUint32(innerH.dataOffset, innerH.le);
461
- let ringOff = off + innerH.dataOffset + 4;
462
- for (let r = 0; r < numRings; r++) {
463
- ringOffsets[ri++] = ci >> 1;
464
- const numPts = dv.getUint32(ringOff, innerH.le);
465
- ringOff += 4;
466
- for (let j = 0; j < numPts; j++) {
467
- const x = dv.getFloat64(ringOff, innerH.le);
468
- const y = dv.getFloat64(ringOff + 8, innerH.le);
469
- coords[ci++] = x;
470
- coords[ci++] = y;
471
- expandBounds(b, x, y);
472
- ringOff += innerH.coordStride;
473
- }
474
- }
475
- off = ringOff;
476
- }
477
- }
478
- polyOffsets[totalPolys] = ri;
479
- ringOffsets[totalRings] = ci >> 1;
480
- const fslData = makeCoordData(coords, ci >> 1);
481
- const ringListType = new List(new Field('vertices', coordType));
482
- const ringListData = makeData({
483
- type: ringListType,
484
- length: totalRings,
485
- nullCount: 0,
486
- valueOffsets: ringOffsets,
487
- child: fslData
488
- });
489
- const polyListType = new List(new Field('rings', ringListType));
490
- const polyListData = makeData({
491
- type: polyListType,
492
- length: totalPolys,
493
- nullCount: 0,
494
- valueOffsets: polyOffsets,
495
- child: ringListData
496
- });
497
- const multiPolyType = new List(new Field('polygons', polyListType));
498
- return makeData({
499
- type: multiPolyType,
500
- length: n,
501
- nullCount: 0,
502
- valueOffsets: geomOffsets,
503
- child: polyListData
504
- });
505
- }
506
- // ─── Build a single GeoArrow table for one type group ────────────────
507
- /** Build attribute columns for a subset of rows identified by indices. */
508
- function buildAttributeColumns(indices, attributes) {
509
- const n = indices.length;
510
- const fields = [];
511
- const dataArr = [];
512
- for (const [name, col] of attributes) {
513
- const { values } = col;
514
- // Detect whether values are numeric or string
515
- let isNumeric = true;
516
- const sampleEnd = Math.min(n, 100);
517
- for (let i = 0; i < sampleEnd; i++) {
518
- if (values[indices[i]] != null && typeof values[indices[i]] !== 'number') {
519
- isNumeric = false;
520
- break;
521
- }
522
- }
523
- if (isNumeric) {
524
- const arr = new Float64Array(n);
525
- for (let i = 0; i < n; i++)
526
- arr[i] = values[indices[i]] ?? NaN;
527
- const data = makeData({ type: new Float64(), length: n, data: arr });
528
- fields.push(new Field(name, new Float64(), true));
529
- dataArr.push(data);
530
- }
531
- else {
532
- const encoder = new TextEncoder();
533
- const offsets = new Int32Array(n + 1);
534
- let totalBytes = 0;
535
- // Single pass: encode directly into a growing buffer
536
- const strParts = [];
537
- for (let i = 0; i < n; i++) {
538
- offsets[i] = totalBytes;
539
- const s = values[indices[i]] != null ? String(values[indices[i]]) : '';
540
- const encoded = encoder.encode(s);
541
- strParts.push(encoded);
542
- totalBytes += encoded.length;
543
- }
544
- offsets[n] = totalBytes;
545
- const valueBuffer = new Uint8Array(totalBytes);
546
- let pos = 0;
547
- for (const sv of strParts) {
548
- valueBuffer.set(sv, pos);
549
- pos += sv.length;
550
- }
551
- const data = makeData({
552
- type: new Utf8(),
553
- length: n,
554
- valueOffsets: offsets,
555
- data: valueBuffer
556
- });
557
- fields.push(new Field(name, new Utf8(), true));
558
- dataArr.push(data);
559
- }
560
- }
561
- return { fields, data: dataArr };
562
- }
563
- /** Build a single GeoArrow table for one geometry type group. */
564
- function buildSingleTable(geomType, wkbs, indices, attributes, b) {
565
- const n = wkbs.length;
566
- let geomData;
567
- switch (geomType) {
568
- case 'point':
569
- geomData = buildPointData(wkbs, b);
570
- break;
571
- case 'linestring':
572
- geomData = buildLineStringData(wkbs, b);
573
- break;
574
- case 'polygon':
575
- geomData = buildPolygonData(wkbs, b);
576
- break;
577
- case 'multipoint':
578
- geomData = buildMultiPointData(wkbs, b);
579
- break;
580
- case 'multilinestring':
581
- geomData = buildMultiLineStringData(wkbs, b);
582
- break;
583
- case 'multipolygon':
584
- geomData = buildMultiPolygonData(wkbs, b);
585
- break;
586
- }
587
- const extensionName = EXTENSION_NAMES[geomType];
588
- const geomMetadata = new Map([
589
- ['ARROW:extension:name', extensionName],
590
- [
591
- 'ARROW:extension:metadata',
592
- JSON.stringify({
593
- crs: {
594
- type: 'name',
595
- properties: { name: 'urn:ogc:def:crs:OGC:1.3:CRS84' }
596
- }
597
- })
598
- ]
599
- ]);
600
- const geomField = new Field('geometry', geomData.type, false, geomMetadata);
601
- const attrCols = buildAttributeColumns(indices, attributes);
602
- const fields = [geomField, ...attrCols.fields];
603
- const childrenData = [geomData, ...attrCols.data];
604
- const arrowSchema = new Schema(fields);
605
- const structType = new Struct(fields);
606
- const structData = makeData({
607
- type: structType,
608
- length: n,
609
- nullCount: 0,
610
- children: childrenData
611
- });
612
- const batch = new RecordBatch(arrowSchema, structData);
613
- const table = new Table(arrowSchema, batch);
614
- return {
615
- table,
616
- geometryType: geomType,
617
- bounds: [b.minX, b.minY, b.maxX, b.maxY],
618
- sourceIndices: indices
619
- };
620
- }
621
- // ─── Main entry point ────────────────────────────────────────────────
622
- /**
623
- * Build GeoArrow tables from raw WKB arrays, automatically splitting by geometry type.
624
- * Returns one GeoArrowResult per non-empty type group, with shared merged bounds.
625
- *
626
- * @param wkbArrays Raw WKB binary arrays from DuckDB
627
- * @param attributes Attribute columns (non-geometry)
628
- * @param knownGeomType If provided (e.g. from GeoParquet metadata), skip classification
629
- */
630
- export function buildGeoArrowTables(wkbArrays, attributes, knownGeomType) {
631
- if (wkbArrays.length === 0)
632
- return [];
633
- // Fast path: known geometry type from metadata — skip classification entirely
634
- if (knownGeomType) {
635
- const globalBounds = newBounds();
636
- const indices = Array.from({ length: wkbArrays.length }, (_, i) => i);
637
- const result = buildSingleTable(knownGeomType, wkbArrays, indices, attributes, globalBounds);
638
- return [result];
639
- }
640
- // Classify by reading only the type bytes (5 bytes per WKB, not full parse)
641
- const groups = new Map();
642
- for (let i = 0; i < wkbArrays.length; i++) {
643
- const geomType = classifyWkbType(wkbArrays[i]);
644
- if (!geomType)
645
- continue;
646
- let group = groups.get(geomType);
647
- if (!group) {
648
- group = { wkbs: [], indices: [] };
649
- groups.set(geomType, group);
650
- }
651
- group.wkbs.push(wkbArrays[i]);
652
- group.indices.push(i);
653
- }
654
- if (groups.size === 0)
655
- return [];
656
- const globalBounds = newBounds();
657
- const results = [];
658
- for (const [geomType, { wkbs, indices }] of groups) {
659
- const result = buildSingleTable(geomType, wkbs, indices, attributes, globalBounds);
660
- results.push(result);
661
- }
662
- // Share merged bounds across all results
663
- const mergedBounds = [
664
- globalBounds.minX,
665
- globalBounds.minY,
666
- globalBounds.maxX,
667
- globalBounds.maxY
668
- ];
669
- for (const r of results)
670
- r.bounds = mergedBounds;
671
- return results;
672
- }