@walkthru-earth/objex 1.3.1 → 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 (184) 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 +263 -112
  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 +1 -1
  44. package/dist/components/viewers/StacMosaicViewer.svelte +1760 -408
  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 +177 -20
  104. package/dist/utils/cog.js +361 -76
  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/cog-pure.d.ts +0 -25
  144. package/dist/utils/cog-pure.js +0 -35
  145. package/dist/utils/column-types.d.ts +0 -5
  146. package/dist/utils/column-types.js +0 -137
  147. package/dist/utils/connection-identity.d.ts +0 -51
  148. package/dist/utils/connection-identity.js +0 -97
  149. package/dist/utils/error.d.ts +0 -8
  150. package/dist/utils/error.js +0 -12
  151. package/dist/utils/evidence-context.d.ts +0 -22
  152. package/dist/utils/evidence-context.js +0 -56
  153. package/dist/utils/export.d.ts +0 -22
  154. package/dist/utils/export.js +0 -76
  155. package/dist/utils/file-sort.d.ts +0 -20
  156. package/dist/utils/file-sort.js +0 -41
  157. package/dist/utils/format.d.ts +0 -24
  158. package/dist/utils/format.js +0 -78
  159. package/dist/utils/geoarrow.d.ts +0 -32
  160. package/dist/utils/geoarrow.js +0 -672
  161. package/dist/utils/geometry-type.d.ts +0 -52
  162. package/dist/utils/geometry-type.js +0 -76
  163. package/dist/utils/hex.d.ts +0 -10
  164. package/dist/utils/hex.js +0 -27
  165. package/dist/utils/host-detection.d.ts +0 -23
  166. package/dist/utils/host-detection.js +0 -95
  167. package/dist/utils/local-storage.d.ts +0 -16
  168. package/dist/utils/local-storage.js +0 -37
  169. package/dist/utils/markdown-sql.d.ts +0 -30
  170. package/dist/utils/markdown-sql.js +0 -72
  171. package/dist/utils/notebook.d.ts +0 -59
  172. package/dist/utils/notebook.js +0 -211
  173. package/dist/utils/parquet-metadata.d.ts +0 -64
  174. package/dist/utils/parquet-metadata.js +0 -262
  175. package/dist/utils/stac-geoparquet.d.ts +0 -90
  176. package/dist/utils/stac-geoparquet.js +0 -223
  177. package/dist/utils/stac-hydrate.d.ts +0 -38
  178. package/dist/utils/stac-hydrate.js +0 -243
  179. package/dist/utils/stac.d.ts +0 -136
  180. package/dist/utils/stac.js +0 -176
  181. package/dist/utils/storage-url.d.ts +0 -90
  182. package/dist/utils/storage-url.js +0 -568
  183. package/dist/utils/wkb.d.ts +0 -43
  184. package/dist/utils/wkb.js +0 -359
@@ -1,211 +0,0 @@
1
- /**
2
- * Lightweight browser-native Jupyter notebook renderer.
3
- * Replaces `notebookjs` which depends on jsdom/Buffer (Node.js only).
4
- * Handles nbformat 2, 3, 4, and 5.
5
- */
6
- const PREFIX = 'nb-';
7
- // ── Helpers ──────────────────────────────────────────────────────────────────
8
- function el(tag, classNames = []) {
9
- const e = document.createElement(tag);
10
- if (classNames.length)
11
- e.className = classNames.map((c) => PREFIX + c).join(' ');
12
- return e;
13
- }
14
- function escapeHTML(raw) {
15
- return raw.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
16
- }
17
- function joinText(text) {
18
- if (Array.isArray(text))
19
- return text.join('');
20
- return text ?? '';
21
- }
22
- // ── Display renderers ────────────────────────────────────────────────────────
23
- const IMAGE_FORMATS = ['image/png', 'image/jpeg', 'image/gif', 'image/webp'];
24
- function renderImage(format, data) {
25
- const img = el('img', ['image-output']);
26
- img.src = `data:${format};base64,${joinText(data).replace(/\n/g, '')}`;
27
- return img;
28
- }
29
- function renderDisplayData(output, config) {
30
- // Resolve data from v4 `data` field or v3 flat fields
31
- const getData = (mime) => output.data?.[mime] ?? output[mime];
32
- // Priority order (richest first)
33
- for (const fmt of IMAGE_FORMATS) {
34
- const d = getData(fmt);
35
- if (d)
36
- return renderImage(fmt, d);
37
- }
38
- const svg = getData('image/svg+xml') ?? getData('text/svg+xml');
39
- if (svg) {
40
- const wrapper = el('div', ['svg-output']);
41
- wrapper.innerHTML = joinText(svg);
42
- return wrapper;
43
- }
44
- const html = getData('text/html');
45
- if (html) {
46
- const wrapper = el('div', ['html-output']);
47
- wrapper.innerHTML = joinText(html);
48
- return wrapper;
49
- }
50
- const md = getData('text/markdown');
51
- if (md) {
52
- const wrapper = el('div', ['html-output']);
53
- wrapper.innerHTML = config.markdown(joinText(md));
54
- return wrapper;
55
- }
56
- const latex = getData('text/latex');
57
- if (latex) {
58
- const wrapper = el('div', ['latex-output']);
59
- wrapper.textContent = joinText(latex);
60
- return wrapper;
61
- }
62
- const plain = getData('text/plain');
63
- if (plain) {
64
- const pre = el('pre', ['text-output']);
65
- pre.innerHTML = config.ansi(escapeHTML(joinText(plain)));
66
- return pre;
67
- }
68
- return el('div', ['empty-output']);
69
- }
70
- function renderStream(output, config) {
71
- const streamName = output.stream ?? output.name ?? 'stdout';
72
- const pre = el('pre', [streamName]);
73
- pre.innerHTML = config.ansi(escapeHTML(joinText(output.text ?? '')));
74
- return pre;
75
- }
76
- function renderError(output, config) {
77
- const pre = el('pre', ['pyerr']);
78
- const raw = (output.traceback ?? []).join('\n');
79
- pre.innerHTML = config.ansi(escapeHTML(raw));
80
- return pre;
81
- }
82
- // ── Output coalescing (merge consecutive same-name streams) ──────────────────
83
- function coalesceStreams(outputs) {
84
- if (!outputs.length)
85
- return outputs;
86
- const result = [{ ...outputs[0] }];
87
- for (let i = 1; i < outputs.length; i++) {
88
- const o = outputs[i];
89
- const last = result[result.length - 1];
90
- if (o.output_type === 'stream' &&
91
- last.output_type === 'stream' &&
92
- (o.stream ?? o.name) === (last.stream ?? last.name)) {
93
- // Merge text
94
- const lastText = Array.isArray(last.text) ? last.text : [last.text ?? ''];
95
- const oText = Array.isArray(o.text) ? o.text : [o.text ?? ''];
96
- last.text = lastText.concat(oText);
97
- }
98
- else {
99
- result.push({ ...o });
100
- }
101
- }
102
- return result;
103
- }
104
- // ── Cell renderers ───────────────────────────────────────────────────────────
105
- function renderCodeCell(cell, lang, config) {
106
- const cellEl = el('div', ['cell', 'code-cell']);
107
- // Input
108
- const source = joinText(cell.source ?? cell.input ?? '');
109
- if (source) {
110
- const holder = el('div', ['input']);
111
- const num = cell.prompt_number ?? cell.execution_count;
112
- if (typeof num === 'number' && num > -1) {
113
- holder.setAttribute('data-prompt-number', String(num));
114
- }
115
- const pre = el('pre');
116
- const code = document.createElement('code');
117
- code.setAttribute('data-language', lang);
118
- code.className = `lang-${lang}`;
119
- code.innerHTML = config.highlighter(escapeHTML(source), lang);
120
- pre.appendChild(code);
121
- holder.appendChild(pre);
122
- cellEl.appendChild(holder);
123
- }
124
- // Outputs
125
- const rawOutputs = coalesceStreams(cell.outputs ?? []);
126
- for (const output of rawOutputs) {
127
- const outer = el('div', ['output']);
128
- let inner;
129
- switch (output.output_type) {
130
- case 'display_data':
131
- case 'execute_result':
132
- case 'pyout':
133
- inner = renderDisplayData(output, config);
134
- break;
135
- case 'stream':
136
- inner = renderStream(output, config);
137
- break;
138
- case 'error':
139
- case 'pyerr':
140
- inner = renderError(output, config);
141
- break;
142
- default:
143
- inner = el('div', ['empty-output']);
144
- }
145
- outer.appendChild(inner);
146
- cellEl.appendChild(outer);
147
- }
148
- return cellEl;
149
- }
150
- function renderMarkdownCell(cell, config) {
151
- const cellEl = el('div', ['cell', 'markdown-cell']);
152
- const source = joinText(cell.source ?? cell.input ?? '');
153
- cellEl.innerHTML = config.markdown(source);
154
- return cellEl;
155
- }
156
- function renderHeadingCell(cell) {
157
- const level = Math.min(Math.max(cell.level ?? 1, 1), 6);
158
- const heading = el(`h${level}`, ['cell', 'heading-cell']);
159
- heading.innerHTML = escapeHTML(joinText(cell.source ?? cell.input ?? ''));
160
- return heading;
161
- }
162
- function renderRawCell(cell) {
163
- const cellEl = el('div', ['cell', 'raw-cell']);
164
- cellEl.innerHTML = escapeHTML(joinText(cell.source ?? cell.input ?? ''));
165
- return cellEl;
166
- }
167
- /**
168
- * Parse and render a Jupyter notebook JSON to a DOM element.
169
- * Returns the rendered element and metadata.
170
- */
171
- export function renderNotebook(raw, config) {
172
- const meta = raw.metadata ?? {};
173
- const lang = meta.kernelspec?.language ?? meta.language_info?.name ?? meta.language ?? 'python';
174
- const kernelName = meta.kernelspec?.display_name ?? meta.language_info?.name ?? '';
175
- // Normalize cells: v4+ has top-level `cells`, v2/v3 uses `worksheets`
176
- let allCells = [];
177
- if (Array.isArray(raw.cells)) {
178
- allCells = raw.cells;
179
- }
180
- else if (Array.isArray(raw.worksheets)) {
181
- for (const ws of raw.worksheets) {
182
- if (Array.isArray(ws.cells))
183
- allCells.push(...ws.cells);
184
- }
185
- }
186
- const notebook = el('div', ['notebook']);
187
- for (const cell of allCells) {
188
- let rendered;
189
- switch (cell.cell_type) {
190
- case 'code':
191
- rendered = renderCodeCell(cell, cell.language ?? lang, config);
192
- break;
193
- case 'markdown':
194
- rendered = renderMarkdownCell(cell, config);
195
- break;
196
- case 'heading':
197
- rendered = renderHeadingCell(cell);
198
- break;
199
- case 'raw':
200
- rendered = renderRawCell(cell);
201
- break;
202
- default:
203
- rendered = renderRawCell(cell);
204
- }
205
- notebook.appendChild(rendered);
206
- }
207
- return {
208
- element: notebook,
209
- meta: { kernelName, language: lang, cellCount: allCells.length }
210
- };
211
- }
@@ -1,64 +0,0 @@
1
- /**
2
- * Lightweight Parquet metadata reader using hyparquet.
3
- *
4
- * Reads schema, row count, CRS, and geometry types from the Parquet footer
5
- * via a single HTTP range request (~512KB). No DuckDB boot needed.
6
- *
7
- * This provides instant metadata display before DuckDB-WASM finishes loading.
8
- */
9
- import type { GeoArrowGeomType } from './geoarrow.js';
10
- export interface GeoColumnMeta {
11
- encoding: string;
12
- geometryTypes: string[];
13
- crs: any | null;
14
- bbox?: number[];
15
- }
16
- export interface GeoParquetMeta {
17
- primaryColumn: string;
18
- columns: Record<string, GeoColumnMeta>;
19
- }
20
- export interface ParquetFileMetadata {
21
- /** Total number of rows across all row groups. */
22
- rowCount: number;
23
- /** Column schema (name + type). Leaf columns only (structs flattened to children). */
24
- schema: {
25
- name: string;
26
- type: string;
27
- }[];
28
- /**
29
- * Top-level column names as written. Includes struct/group parents (e.g.
30
- * `assets`, `bbox`) that `schema` flattens away. Required for stac-geoparquet
31
- * detection, which looks up struct columns by their parent name.
32
- */
33
- topLevelColumns: string[];
34
- /** GeoParquet "geo" metadata if present. */
35
- geo: GeoParquetMeta | null;
36
- /** True when file has legacy GeoParquet metadata (schema_version 0.x without "version" field). */
37
- legacyGeoParquet: boolean;
38
- /** Tool that created the file (e.g. "pyarrow 15.0.0"). */
39
- createdBy: string | null;
40
- /** Number of row groups. */
41
- numRowGroups: number;
42
- /** Primary compression codec (e.g. "SNAPPY", "ZSTD"). */
43
- compression: string | null;
44
- }
45
- /**
46
- * Read Parquet footer metadata from a remote URL using hyparquet.
47
- * Uses HTTP range requests — typically a single ~512KB fetch.
48
- */
49
- export declare function readParquetMetadata(url: string): Promise<ParquetFileMetadata>;
50
- /**
51
- * Extract EPSG code from GeoParquet CRS metadata.
52
- * Returns null for WGS84/CRS84 (no reprojection needed).
53
- */
54
- export declare function extractEpsgFromGeoMeta(geo: GeoParquetMeta): string | null;
55
- /**
56
- * Extract normalized geometry types from GeoParquet metadata.
57
- * Returns the set of geometry types for the primary column.
58
- */
59
- export declare function extractGeometryTypes(geo: GeoParquetMeta): GeoArrowGeomType[];
60
- /**
61
- * Extract bounds from GeoParquet metadata (bbox field).
62
- * Returns [minX, minY, maxX, maxY] or null.
63
- */
64
- export declare function extractBounds(geo: GeoParquetMeta): [number, number, number, number] | null;
@@ -1,262 +0,0 @@
1
- /**
2
- * Lightweight Parquet metadata reader using hyparquet.
3
- *
4
- * Reads schema, row count, CRS, and geometry types from the Parquet footer
5
- * via a single HTTP range request (~512KB). No DuckDB boot needed.
6
- *
7
- * This provides instant metadata display before DuckDB-WASM finishes loading.
8
- */
9
- import { WGS84_CODES } from '../constants.js';
10
- /** Map hyparquet schema element types to DuckDB-like type strings. */
11
- function mapParquetType(col) {
12
- const lt = col.logical_type;
13
- if (lt) {
14
- // Parquet Format 2.11+ native geometry/geography types
15
- if (lt.type === 'GEOMETRY' || lt.type === 'GEOGRAPHY')
16
- return 'GEOMETRY';
17
- // String types
18
- if (lt.type === 'STRING' || lt.type === 'UTF8')
19
- return 'VARCHAR';
20
- if (lt.type === 'JSON')
21
- return 'JSON';
22
- if (lt.type === 'UUID')
23
- return 'UUID';
24
- if (lt.type === 'ENUM')
25
- return 'VARCHAR';
26
- // Integer types
27
- if (lt.type === 'INT' || lt.type === 'INTEGER') {
28
- const bits = lt.bitWidth ?? 32;
29
- const signed = lt.isSigned !== false;
30
- if (bits <= 8)
31
- return signed ? 'TINYINT' : 'UTINYINT';
32
- if (bits <= 16)
33
- return signed ? 'SMALLINT' : 'USMALLINT';
34
- if (bits <= 32)
35
- return signed ? 'INTEGER' : 'UINTEGER';
36
- return signed ? 'BIGINT' : 'UBIGINT';
37
- }
38
- // Decimal
39
- if (lt.type === 'DECIMAL')
40
- return `DECIMAL(${lt.precision ?? 18},${lt.scale ?? 0})`;
41
- // Date/Time
42
- if (lt.type === 'DATE')
43
- return 'DATE';
44
- if (lt.type === 'TIME')
45
- return 'TIME';
46
- if (lt.type === 'TIMESTAMP')
47
- return 'TIMESTAMP';
48
- // Binary
49
- if (lt.type === 'BSON')
50
- return 'BLOB';
51
- }
52
- // Fallback to physical type
53
- const ct = col.converted_type;
54
- if (ct === 'UTF8')
55
- return 'VARCHAR';
56
- if (ct === 'JSON')
57
- return 'JSON';
58
- if (ct === 'DATE')
59
- return 'DATE';
60
- if (ct === 'TIMESTAMP_MILLIS' || ct === 'TIMESTAMP_MICROS')
61
- return 'TIMESTAMP';
62
- if (ct === 'DECIMAL')
63
- return `DECIMAL(${col.precision ?? 18},${col.scale ?? 0})`;
64
- if (ct === 'INT_8')
65
- return 'TINYINT';
66
- if (ct === 'INT_16')
67
- return 'SMALLINT';
68
- if (ct === 'INT_32')
69
- return 'INTEGER';
70
- if (ct === 'INT_64')
71
- return 'BIGINT';
72
- if (ct === 'UINT_8')
73
- return 'UTINYINT';
74
- if (ct === 'UINT_16')
75
- return 'USMALLINT';
76
- if (ct === 'UINT_32')
77
- return 'UINTEGER';
78
- if (ct === 'UINT_64')
79
- return 'UBIGINT';
80
- const pt = col.type;
81
- if (pt === 'BOOLEAN')
82
- return 'BOOLEAN';
83
- if (pt === 'INT32')
84
- return 'INTEGER';
85
- if (pt === 'INT64')
86
- return 'BIGINT';
87
- if (pt === 'INT96')
88
- return 'TIMESTAMP';
89
- if (pt === 'FLOAT')
90
- return 'FLOAT';
91
- if (pt === 'DOUBLE')
92
- return 'DOUBLE';
93
- if (pt === 'BYTE_ARRAY')
94
- return 'BLOB';
95
- if (pt === 'FIXED_LEN_BYTE_ARRAY')
96
- return 'BLOB';
97
- return 'VARCHAR';
98
- }
99
- /**
100
- * Read Parquet footer metadata from a remote URL using hyparquet.
101
- * Uses HTTP range requests — typically a single ~512KB fetch.
102
- */
103
- export async function readParquetMetadata(url) {
104
- const { parquetMetadataAsync, asyncBufferFromUrl } = await import('hyparquet');
105
- const file = await asyncBufferFromUrl({ url });
106
- const metadata = await parquetMetadataAsync(file);
107
- // Row count from row group metadata (no data scan needed)
108
- const rowCount = metadata.row_groups.reduce((sum, rg) => sum + Number(rg.num_rows), 0);
109
- // Schema from metadata (skip root element at index 0)
110
- const schema = metadata.schema
111
- .slice(1)
112
- .filter((col) => col.num_children === undefined) // skip group elements
113
- .map((col) => ({
114
- name: col.name,
115
- type: mapParquetType(col)
116
- }));
117
- // Walk depth-first to collect top-level column names (including struct parents).
118
- // hyparquet serializes the schema as a flat depth-first array where the root
119
- // carries `num_children` and each group element claims the next N siblings.
120
- const topLevelColumns = [];
121
- const root = metadata.schema[0];
122
- const rootChildren = root?.num_children ?? 0;
123
- let cursor = 1;
124
- for (let i = 0; i < rootChildren && cursor < metadata.schema.length; i++) {
125
- const el = metadata.schema[cursor];
126
- if (el?.name)
127
- topLevelColumns.push(el.name);
128
- cursor += countSubtree(metadata.schema, cursor);
129
- }
130
- // GeoParquet "geo" key-value metadata
131
- let geo = null;
132
- let legacyGeoParquet = false;
133
- const geoKv = metadata.key_value_metadata?.find((kv) => kv.key === 'geo');
134
- if (geoKv) {
135
- try {
136
- const geoJson = JSON.parse(geoKv.value ?? '');
137
- // Detect legacy GeoParquet (schema_version 0.x without "version" field).
138
- // Early geopandas (<0.12) wrote schema_version instead of version.
139
- // DuckDB's spatial extension rejects these files with:
140
- // "Geoparquet metadata does not have a version"
141
- if (geoJson.schema_version && !geoJson.version) {
142
- legacyGeoParquet = true;
143
- }
144
- geo = {
145
- primaryColumn: geoJson.primary_column ?? 'geometry',
146
- columns: {}
147
- };
148
- if (geoJson.columns) {
149
- for (const [colName, colMeta] of Object.entries(geoJson.columns)) {
150
- geo.columns[colName] = {
151
- encoding: colMeta.encoding ?? 'WKB',
152
- geometryTypes: colMeta.geometry_types ?? [],
153
- crs: colMeta.crs ?? null,
154
- bbox: colMeta.bbox
155
- };
156
- }
157
- }
158
- }
159
- catch {
160
- // malformed geo metadata — ignore
161
- }
162
- }
163
- // Created-by tool
164
- const createdBy = metadata.created_by ?? null;
165
- // Row group count
166
- const numRowGroups = metadata.row_groups.length;
167
- // Compression: collect unique codecs from first row group's columns
168
- let compression = null;
169
- if (numRowGroups > 0 && metadata.row_groups[0].columns) {
170
- const codecs = new Set();
171
- for (const col of metadata.row_groups[0].columns) {
172
- const codec = col.meta_data?.codec;
173
- if (codec)
174
- codecs.add(codec);
175
- }
176
- if (codecs.size === 1) {
177
- compression = [...codecs][0];
178
- }
179
- else if (codecs.size > 1) {
180
- compression = [...codecs].join(', ');
181
- }
182
- }
183
- return {
184
- rowCount,
185
- schema,
186
- topLevelColumns,
187
- geo,
188
- legacyGeoParquet,
189
- createdBy,
190
- numRowGroups,
191
- compression
192
- };
193
- }
194
- /** Count how many schema elements belong to the subtree rooted at `start` (inclusive). */
195
- function countSubtree(schema, start) {
196
- const el = schema[start];
197
- if (!el)
198
- return 0;
199
- const n = el.num_children ?? 0;
200
- let cursor = start + 1;
201
- for (let i = 0; i < n; i++) {
202
- cursor += countSubtree(schema, cursor);
203
- }
204
- return cursor - start;
205
- }
206
- /**
207
- * Extract EPSG code from GeoParquet CRS metadata.
208
- * Returns null for WGS84/CRS84 (no reprojection needed).
209
- */
210
- export function extractEpsgFromGeoMeta(geo) {
211
- const primaryCol = geo.columns[geo.primaryColumn];
212
- if (!primaryCol?.crs)
213
- return null;
214
- const crs = primaryCol.crs;
215
- // OGC CRS84 is lon/lat WGS84
216
- if (crs.type === 'name' && crs.properties?.name?.includes('CRS84'))
217
- return null;
218
- // PROJJSON: { "id": { "authority": "EPSG", "code": 27700 } }
219
- if (crs.id?.authority === 'EPSG') {
220
- const code = crs.id.code;
221
- if (WGS84_CODES.has(code))
222
- return null;
223
- return `EPSG:${code}`;
224
- }
225
- return null;
226
- }
227
- /**
228
- * Extract normalized geometry types from GeoParquet metadata.
229
- * Returns the set of geometry types for the primary column.
230
- */
231
- export function extractGeometryTypes(geo) {
232
- const primaryCol = geo.columns[geo.primaryColumn];
233
- if (!primaryCol?.geometryTypes?.length)
234
- return [];
235
- const typeMap = {
236
- Point: 'point',
237
- LineString: 'linestring',
238
- Polygon: 'polygon',
239
- MultiPoint: 'multipoint',
240
- MultiLineString: 'multilinestring',
241
- MultiPolygon: 'multipolygon'
242
- };
243
- const types = [];
244
- for (const raw of primaryCol.geometryTypes) {
245
- // GeoParquet geometry_types may include Z/M suffixes like "Polygon Z"
246
- const base = raw.split(' ')[0];
247
- const mapped = typeMap[base];
248
- if (mapped && !types.includes(mapped))
249
- types.push(mapped);
250
- }
251
- return types;
252
- }
253
- /**
254
- * Extract bounds from GeoParquet metadata (bbox field).
255
- * Returns [minX, minY, maxX, maxY] or null.
256
- */
257
- export function extractBounds(geo) {
258
- const primaryCol = geo.columns[geo.primaryColumn];
259
- if (!primaryCol?.bbox || primaryCol.bbox.length < 4)
260
- return null;
261
- return [primaryCol.bbox[0], primaryCol.bbox[1], primaryCol.bbox[2], primaryCol.bbox[3]];
262
- }
@@ -1,90 +0,0 @@
1
- /**
2
- * stac-geoparquet helpers.
3
- *
4
- * Pure TypeScript, zero Svelte / DuckDB / deck.gl dependencies. Re-exported
5
- * through `@walkthru-earth/objex-utils` so other projects can consume the
6
- * detection + row-to-Item transforms without pulling the Svelte lib.
7
- *
8
- * The module is decoupled from WKB decoding: callers pass a `wkbParser`
9
- * callback (the objex Svelte lib threads its `parseWKB`; other consumers
10
- * can plug in `geoarrow-wasm`, `wkx`, or any other library).
11
- */
12
- import type { StacAsset, StacItem } from './stac.js';
13
- /** Minimal schema column shape. Works with hyparquet, DuckDB, Arrow. */
14
- export interface StacGeoparquetSchemaColumn {
15
- name: string;
16
- type?: string;
17
- }
18
- /** Bbox in struct shape as produced by DuckDB's `bbox struct(xmin,ymin,xmax,ymax)`. */
19
- export interface StacBboxStruct {
20
- xmin: number;
21
- ymin: number;
22
- xmax: number;
23
- ymax: number;
24
- }
25
- /** Generic Record shape representing a single stac-geoparquet row after DuckDB decoding. */
26
- export type StacGeoparquetRow = Record<string, unknown>;
27
- export interface StacRowToItemOptions {
28
- /**
29
- * Decoder for the geometry column. Accepts a Uint8Array of WKB bytes and
30
- * returns a GeoJSON geometry object (or null on failure).
31
- *
32
- * Consumers in the objex Svelte lib should pass `parseWKB` from
33
- * `@walkthru-earth/objex-utils`. Other projects can use any WKB library.
34
- */
35
- wkbParser?: (bytes: Uint8Array) => unknown;
36
- /**
37
- * Column name holding the WKB bytes. Defaults to `'geom_wkb'` because the
38
- * recommended SQL projection is `ST_AsWKB(geometry) AS geom_wkb`.
39
- */
40
- wkbColumn?: string;
41
- /** Override the column holding the pre-decoded GeoJSON geometry, when available. */
42
- geometryColumn?: string;
43
- }
44
- /** Columns every stac-geoparquet file MUST carry per the stac-geoparquet spec. */
45
- export declare const STAC_GEOPARQUET_REQUIRED_COLUMNS: readonly ["stac_version", "type", "geometry", "assets"];
46
- /**
47
- * Detect stac-geoparquet by presence of the required STAC columns.
48
- *
49
- * Deliberately type-agnostic: some pipelines know the type (DuckDB DESCRIBE,
50
- * Arrow Field), others only have the name list (hyparquet schema walk). The
51
- * set of names is sufficient for routing.
52
- */
53
- export declare function isStacGeoparquetSchema(schema: StacGeoparquetSchemaColumn[]): boolean;
54
- /**
55
- * Flatten a DuckDB `struct(xmin,ymin,xmax,ymax)` bbox to the `[minX, minY, maxX, maxY]`
56
- * array shape that STAC Items and deck.gl-geotiff MosaicLayer expect.
57
- *
58
- * Pass-through for arrays so callers that already have `[minX,minY,maxX,maxY]`
59
- * shape (e.g. from a Feature's `bbox` field) don't need a separate path.
60
- */
61
- export declare function flattenStacBbox(bbox: StacBboxStruct | number[] | null | undefined): [number, number, number, number] | null;
62
- /**
63
- * Resolve a possibly-relative STAC asset href against the parquet file URL.
64
- *
65
- * `./foo.tif` or `foo.tif` → absolute against `baseUrl`. Absolute URLs
66
- * (`http(s)://`, `s3://`, `gs://`, etc.) are returned unchanged.
67
- */
68
- export declare function resolveStacAssetHref(href: string, baseUrl: string): string;
69
- /**
70
- * Pick the "primary" asset from a STAC Item's `assets` map.
71
- *
72
- * Priority: caller-specified `preferredKeys` → `data` key → first asset with
73
- * `roles` containing `'data'` → first asset. Returns `null` if the map is
74
- * empty or not an object.
75
- */
76
- export declare function pickStacPrimaryAsset(assets: Record<string, StacAsset> | null | undefined, preferredKeys?: readonly string[]): {
77
- key: string;
78
- asset: StacAsset;
79
- } | null;
80
- /**
81
- * Convert one stac-geoparquet row into a standard STAC Item JSON object.
82
- *
83
- * Handles:
84
- * - `assets` named-struct flattening + relative href resolution
85
- * - `bbox` struct → `[minX, minY, maxX, maxY]` array
86
- * - Optional WKB geometry → GeoJSON via `opts.wkbParser`
87
- * - `datetime` → ISO string (passes through already-string values)
88
- * - Promotes `properties.*` columns (`proj:*`, `datetime`) onto `item.properties`
89
- */
90
- export declare function stacRowToItem(row: StacGeoparquetRow, baseUrl: string, opts?: StacRowToItemOptions): StacItem;