@walkthru-earth/objex 0.1.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.
- package/LICENSE +396 -0
- package/README.md +114 -0
- package/dist/assets/favicon.svg +17 -0
- package/dist/components/CLAUDE.md +44 -0
- package/dist/components/browser/Breadcrumb.svelte +50 -0
- package/dist/components/browser/Breadcrumb.svelte.d.ts +7 -0
- package/dist/components/browser/CreateFolderDialog.svelte +98 -0
- package/dist/components/browser/CreateFolderDialog.svelte.d.ts +6 -0
- package/dist/components/browser/DeleteConfirmDialog.svelte +90 -0
- package/dist/components/browser/DeleteConfirmDialog.svelte.d.ts +8 -0
- package/dist/components/browser/DropZone.svelte +83 -0
- package/dist/components/browser/DropZone.svelte.d.ts +7 -0
- package/dist/components/browser/FileBrowser.svelte +229 -0
- package/dist/components/browser/FileBrowser.svelte.d.ts +3 -0
- package/dist/components/browser/FileRow.svelte +112 -0
- package/dist/components/browser/FileRow.svelte.d.ts +9 -0
- package/dist/components/browser/FileTreeSidebar.svelte +559 -0
- package/dist/components/browser/FileTreeSidebar.svelte.d.ts +8 -0
- package/dist/components/browser/RenameDialog.svelte +101 -0
- package/dist/components/browser/RenameDialog.svelte.d.ts +8 -0
- package/dist/components/browser/SearchBar.svelte +40 -0
- package/dist/components/browser/SearchBar.svelte.d.ts +6 -0
- package/dist/components/browser/UploadButton.svelte +65 -0
- package/dist/components/browser/UploadButton.svelte.d.ts +3 -0
- package/dist/components/editor/CodeMirrorEditor.svelte +404 -0
- package/dist/components/editor/CodeMirrorEditor.svelte.d.ts +12 -0
- package/dist/components/editor/MilkdownEditor.svelte +98 -0
- package/dist/components/editor/MilkdownEditor.svelte.d.ts +9 -0
- package/dist/components/editor/SqlEditor.svelte +173 -0
- package/dist/components/editor/SqlEditor.svelte.d.ts +7 -0
- package/dist/components/editor/SqlResultBlock.svelte +199 -0
- package/dist/components/editor/SqlResultBlock.svelte.d.ts +9 -0
- package/dist/components/layout/ConnectionDialog.svelte +439 -0
- package/dist/components/layout/ConnectionDialog.svelte.d.ts +9 -0
- package/dist/components/layout/LocaleToggle.svelte +32 -0
- package/dist/components/layout/LocaleToggle.svelte.d.ts +3 -0
- package/dist/components/layout/SafeLockToggle.svelte +37 -0
- package/dist/components/layout/SafeLockToggle.svelte.d.ts +18 -0
- package/dist/components/layout/Sidebar.svelte +314 -0
- package/dist/components/layout/Sidebar.svelte.d.ts +3 -0
- package/dist/components/layout/StatusBar.svelte +73 -0
- package/dist/components/layout/StatusBar.svelte.d.ts +3 -0
- package/dist/components/layout/TabBar.svelte +102 -0
- package/dist/components/layout/TabBar.svelte.d.ts +7 -0
- package/dist/components/layout/ThemeToggle.svelte +52 -0
- package/dist/components/layout/ThemeToggle.svelte.d.ts +3 -0
- package/dist/components/ui/badge/badge.svelte +49 -0
- package/dist/components/ui/badge/badge.svelte.d.ts +32 -0
- package/dist/components/ui/badge/index.d.ts +1 -0
- package/dist/components/ui/badge/index.js +1 -0
- package/dist/components/ui/button/button.svelte +82 -0
- package/dist/components/ui/button/button.svelte.d.ts +64 -0
- package/dist/components/ui/button/index.d.ts +2 -0
- package/dist/components/ui/button/index.js +4 -0
- package/dist/components/ui/context-menu/context-menu-checkbox-item.svelte +40 -0
- package/dist/components/ui/context-menu/context-menu-checkbox-item.svelte.d.ts +9 -0
- package/dist/components/ui/context-menu/context-menu-content.svelte +28 -0
- package/dist/components/ui/context-menu/context-menu-content.svelte.d.ts +10 -0
- package/dist/components/ui/context-menu/context-menu-group-heading.svelte +21 -0
- package/dist/components/ui/context-menu/context-menu-group-heading.svelte.d.ts +7 -0
- package/dist/components/ui/context-menu/context-menu-group.svelte +7 -0
- package/dist/components/ui/context-menu/context-menu-group.svelte.d.ts +4 -0
- package/dist/components/ui/context-menu/context-menu-item.svelte +27 -0
- package/dist/components/ui/context-menu/context-menu-item.svelte.d.ts +8 -0
- package/dist/components/ui/context-menu/context-menu-label.svelte +24 -0
- package/dist/components/ui/context-menu/context-menu-label.svelte.d.ts +8 -0
- package/dist/components/ui/context-menu/context-menu-portal.svelte +7 -0
- package/dist/components/ui/context-menu/context-menu-portal.svelte.d.ts +3 -0
- package/dist/components/ui/context-menu/context-menu-radio-group.svelte +16 -0
- package/dist/components/ui/context-menu/context-menu-radio-group.svelte.d.ts +4 -0
- package/dist/components/ui/context-menu/context-menu-radio-item.svelte +33 -0
- package/dist/components/ui/context-menu/context-menu-radio-item.svelte.d.ts +4 -0
- package/dist/components/ui/context-menu/context-menu-separator.svelte +17 -0
- package/dist/components/ui/context-menu/context-menu-separator.svelte.d.ts +4 -0
- package/dist/components/ui/context-menu/context-menu-shortcut.svelte +20 -0
- package/dist/components/ui/context-menu/context-menu-shortcut.svelte.d.ts +5 -0
- package/dist/components/ui/context-menu/context-menu-sub-content.svelte +20 -0
- package/dist/components/ui/context-menu/context-menu-sub-content.svelte.d.ts +4 -0
- package/dist/components/ui/context-menu/context-menu-sub-trigger.svelte +29 -0
- package/dist/components/ui/context-menu/context-menu-sub-trigger.svelte.d.ts +8 -0
- package/dist/components/ui/context-menu/context-menu-sub.svelte +7 -0
- package/dist/components/ui/context-menu/context-menu-sub.svelte.d.ts +3 -0
- package/dist/components/ui/context-menu/context-menu-trigger.svelte +7 -0
- package/dist/components/ui/context-menu/context-menu-trigger.svelte.d.ts +4 -0
- package/dist/components/ui/context-menu/context-menu.svelte +7 -0
- package/dist/components/ui/context-menu/context-menu.svelte.d.ts +3 -0
- package/dist/components/ui/context-menu/index.d.ts +17 -0
- package/dist/components/ui/context-menu/index.js +19 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte +16 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte.d.ts +4 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte +43 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte.d.ts +9 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-content.svelte +29 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-content.svelte.d.ts +10 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte +22 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte.d.ts +8 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-group.svelte +7 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-group.svelte.d.ts +4 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-item.svelte +27 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-item.svelte.d.ts +8 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-label.svelte +24 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-label.svelte.d.ts +8 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-portal.svelte +7 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-portal.svelte.d.ts +3 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte +16 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte.d.ts +4 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte +33 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte.d.ts +4 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-separator.svelte +17 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-separator.svelte.d.ts +4 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte +20 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte.d.ts +5 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte +20 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte.d.ts +4 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte +29 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte.d.ts +7 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-sub.svelte +7 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-sub.svelte.d.ts +3 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-trigger.svelte +7 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-trigger.svelte.d.ts +4 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu.svelte +7 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu.svelte.d.ts +3 -0
- package/dist/components/ui/dropdown-menu/index.d.ts +18 -0
- package/dist/components/ui/dropdown-menu/index.js +18 -0
- package/dist/components/ui/input/index.d.ts +2 -0
- package/dist/components/ui/input/index.js +4 -0
- package/dist/components/ui/input/input.svelte +52 -0
- package/dist/components/ui/input/input.svelte.d.ts +13 -0
- package/dist/components/ui/resizable/index.d.ts +4 -0
- package/dist/components/ui/resizable/index.js +6 -0
- package/dist/components/ui/resizable/resizable-handle.svelte +30 -0
- package/dist/components/ui/resizable/resizable-handle.svelte.d.ts +8 -0
- package/dist/components/ui/resizable/resizable-pane-group.svelte +20 -0
- package/dist/components/ui/resizable/resizable-pane-group.svelte.d.ts +7 -0
- package/dist/components/ui/scroll-area/index.d.ts +3 -0
- package/dist/components/ui/scroll-area/index.js +5 -0
- package/dist/components/ui/scroll-area/scroll-area-scrollbar.svelte +31 -0
- package/dist/components/ui/scroll-area/scroll-area-scrollbar.svelte.d.ts +4 -0
- package/dist/components/ui/scroll-area/scroll-area.svelte +47 -0
- package/dist/components/ui/scroll-area/scroll-area.svelte.d.ts +11 -0
- package/dist/components/ui/separator/index.d.ts +2 -0
- package/dist/components/ui/separator/index.js +4 -0
- package/dist/components/ui/separator/separator.svelte +21 -0
- package/dist/components/ui/separator/separator.svelte.d.ts +4 -0
- package/dist/components/ui/sheet/index.d.ts +11 -0
- package/dist/components/ui/sheet/index.js +13 -0
- package/dist/components/ui/sheet/sheet-close.svelte +7 -0
- package/dist/components/ui/sheet/sheet-close.svelte.d.ts +4 -0
- package/dist/components/ui/sheet/sheet-content.svelte +62 -0
- package/dist/components/ui/sheet/sheet-content.svelte.d.ts +37 -0
- package/dist/components/ui/sheet/sheet-description.svelte +17 -0
- package/dist/components/ui/sheet/sheet-description.svelte.d.ts +4 -0
- package/dist/components/ui/sheet/sheet-footer.svelte +20 -0
- package/dist/components/ui/sheet/sheet-footer.svelte.d.ts +5 -0
- package/dist/components/ui/sheet/sheet-header.svelte +20 -0
- package/dist/components/ui/sheet/sheet-header.svelte.d.ts +5 -0
- package/dist/components/ui/sheet/sheet-overlay.svelte +20 -0
- package/dist/components/ui/sheet/sheet-overlay.svelte.d.ts +4 -0
- package/dist/components/ui/sheet/sheet-portal.svelte +7 -0
- package/dist/components/ui/sheet/sheet-portal.svelte.d.ts +3 -0
- package/dist/components/ui/sheet/sheet-title.svelte +13 -0
- package/dist/components/ui/sheet/sheet-title.svelte.d.ts +4 -0
- package/dist/components/ui/sheet/sheet-trigger.svelte +7 -0
- package/dist/components/ui/sheet/sheet-trigger.svelte.d.ts +4 -0
- package/dist/components/ui/sheet/sheet.svelte +7 -0
- package/dist/components/ui/sheet/sheet.svelte.d.ts +3 -0
- package/dist/components/ui/switch/index.d.ts +2 -0
- package/dist/components/ui/switch/index.js +4 -0
- package/dist/components/ui/switch/switch.svelte +28 -0
- package/dist/components/ui/switch/switch.svelte.d.ts +8 -0
- package/dist/components/ui/tabs/index.d.ts +5 -0
- package/dist/components/ui/tabs/index.js +7 -0
- package/dist/components/ui/tabs/tabs-content.svelte +17 -0
- package/dist/components/ui/tabs/tabs-content.svelte.d.ts +4 -0
- package/dist/components/ui/tabs/tabs-list.svelte +16 -0
- package/dist/components/ui/tabs/tabs-list.svelte.d.ts +4 -0
- package/dist/components/ui/tabs/tabs-trigger.svelte +20 -0
- package/dist/components/ui/tabs/tabs-trigger.svelte.d.ts +4 -0
- package/dist/components/ui/tabs/tabs.svelte +19 -0
- package/dist/components/ui/tabs/tabs.svelte.d.ts +4 -0
- package/dist/components/ui/tooltip/index.d.ts +6 -0
- package/dist/components/ui/tooltip/index.js +8 -0
- package/dist/components/ui/tooltip/tooltip-content.svelte +52 -0
- package/dist/components/ui/tooltip/tooltip-content.svelte.d.ts +11 -0
- package/dist/components/ui/tooltip/tooltip-portal.svelte +7 -0
- package/dist/components/ui/tooltip/tooltip-portal.svelte.d.ts +4 -0
- package/dist/components/ui/tooltip/tooltip-provider.svelte +7 -0
- package/dist/components/ui/tooltip/tooltip-provider.svelte.d.ts +4 -0
- package/dist/components/ui/tooltip/tooltip-trigger.svelte +7 -0
- package/dist/components/ui/tooltip/tooltip-trigger.svelte.d.ts +4 -0
- package/dist/components/ui/tooltip/tooltip.svelte +7 -0
- package/dist/components/ui/tooltip/tooltip.svelte.d.ts +4 -0
- package/dist/components/viewers/ArchiveViewer.svelte +586 -0
- package/dist/components/viewers/ArchiveViewer.svelte.d.ts +7 -0
- package/dist/components/viewers/CLAUDE.md +60 -0
- package/dist/components/viewers/CodeViewer.svelte +553 -0
- package/dist/components/viewers/CodeViewer.svelte.d.ts +7 -0
- package/dist/components/viewers/CogViewer.svelte +1345 -0
- package/dist/components/viewers/CogViewer.svelte.d.ts +7 -0
- package/dist/components/viewers/CopcViewer.svelte +25 -0
- package/dist/components/viewers/CopcViewer.svelte.d.ts +7 -0
- package/dist/components/viewers/DatabaseViewer.svelte +169 -0
- package/dist/components/viewers/DatabaseViewer.svelte.d.ts +7 -0
- package/dist/components/viewers/FileInfo.svelte +174 -0
- package/dist/components/viewers/FileInfo.svelte.d.ts +10 -0
- package/dist/components/viewers/FlatGeobufViewer.svelte +755 -0
- package/dist/components/viewers/FlatGeobufViewer.svelte.d.ts +7 -0
- package/dist/components/viewers/GeoParquetMapViewer.svelte +278 -0
- package/dist/components/viewers/GeoParquetMapViewer.svelte.d.ts +17 -0
- package/dist/components/viewers/ImageViewer.svelte +233 -0
- package/dist/components/viewers/ImageViewer.svelte.d.ts +7 -0
- package/dist/components/viewers/LoadProgress.svelte +93 -0
- package/dist/components/viewers/LoadProgress.svelte.d.ts +15 -0
- package/dist/components/viewers/MapViewer.svelte +234 -0
- package/dist/components/viewers/MapViewer.svelte.d.ts +7 -0
- package/dist/components/viewers/MarkdownViewer.svelte +478 -0
- package/dist/components/viewers/MarkdownViewer.svelte.d.ts +7 -0
- package/dist/components/viewers/MediaViewer.svelte +121 -0
- package/dist/components/viewers/MediaViewer.svelte.d.ts +7 -0
- package/dist/components/viewers/ModelViewer.svelte +164 -0
- package/dist/components/viewers/ModelViewer.svelte.d.ts +7 -0
- package/dist/components/viewers/NotebookViewer.svelte +389 -0
- package/dist/components/viewers/NotebookViewer.svelte.d.ts +7 -0
- package/dist/components/viewers/PdfViewer.svelte +278 -0
- package/dist/components/viewers/PdfViewer.svelte.d.ts +7 -0
- package/dist/components/viewers/PmtilesViewer.svelte +191 -0
- package/dist/components/viewers/PmtilesViewer.svelte.d.ts +7 -0
- package/dist/components/viewers/QueryHistoryPanel.svelte +159 -0
- package/dist/components/viewers/QueryHistoryPanel.svelte.d.ts +8 -0
- package/dist/components/viewers/RawViewer.svelte +117 -0
- package/dist/components/viewers/RawViewer.svelte.d.ts +7 -0
- package/dist/components/viewers/StacMapViewer.svelte +20 -0
- package/dist/components/viewers/StacMapViewer.svelte.d.ts +7 -0
- package/dist/components/viewers/StyleEditorOverlay.svelte +27 -0
- package/dist/components/viewers/StyleEditorOverlay.svelte.d.ts +7 -0
- package/dist/components/viewers/TableGrid.svelte +355 -0
- package/dist/components/viewers/TableGrid.svelte.d.ts +12 -0
- package/dist/components/viewers/TableStatusBar.svelte +92 -0
- package/dist/components/viewers/TableStatusBar.svelte.d.ts +11 -0
- package/dist/components/viewers/TableToolbar.svelte +382 -0
- package/dist/components/viewers/TableToolbar.svelte.d.ts +25 -0
- package/dist/components/viewers/TableViewer.svelte +923 -0
- package/dist/components/viewers/TableViewer.svelte.d.ts +7 -0
- package/dist/components/viewers/ViewerRouter.svelte +70 -0
- package/dist/components/viewers/ViewerRouter.svelte.d.ts +7 -0
- package/dist/components/viewers/ZarrMapViewer.svelte +288 -0
- package/dist/components/viewers/ZarrMapViewer.svelte.d.ts +17 -0
- package/dist/components/viewers/ZarrViewer.svelte +256 -0
- package/dist/components/viewers/ZarrViewer.svelte.d.ts +7 -0
- package/dist/components/viewers/map/AttributeTable.svelte +52 -0
- package/dist/components/viewers/map/AttributeTable.svelte.d.ts +8 -0
- package/dist/components/viewers/map/MapContainer.svelte +158 -0
- package/dist/components/viewers/map/MapContainer.svelte.d.ts +12 -0
- package/dist/components/viewers/pmtiles/PmtilesArchiveView.svelte +389 -0
- package/dist/components/viewers/pmtiles/PmtilesArchiveView.svelte.d.ts +10 -0
- package/dist/components/viewers/pmtiles/PmtilesMapView.svelte +332 -0
- package/dist/components/viewers/pmtiles/PmtilesMapView.svelte.d.ts +11 -0
- package/dist/components/viewers/pmtiles/PmtilesTileInspector.svelte +373 -0
- package/dist/components/viewers/pmtiles/PmtilesTileInspector.svelte.d.ts +12 -0
- package/dist/components/viewers/pmtiles/SvgTileRenderer.svelte +112 -0
- package/dist/components/viewers/pmtiles/SvgTileRenderer.svelte.d.ts +10 -0
- package/dist/file-icons/CLAUDE.md +21 -0
- package/dist/file-icons/FileTypeIcon.svelte +74 -0
- package/dist/file-icons/FileTypeIcon.svelte.d.ts +9 -0
- package/dist/file-icons/index.d.ts +56 -0
- package/dist/file-icons/index.js +1070 -0
- package/dist/i18n/CLAUDE.md +19 -0
- package/dist/i18n/ar.d.ts +1 -0
- package/dist/i18n/ar.js +404 -0
- package/dist/i18n/en.d.ts +1 -0
- package/dist/i18n/en.js +404 -0
- package/dist/i18n/index.svelte.d.ts +9 -0
- package/dist/i18n/index.svelte.js +27 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +13 -0
- package/dist/query/CLAUDE.md +22 -0
- package/dist/query/engine.d.ts +56 -0
- package/dist/query/engine.js +6 -0
- package/dist/query/index.d.ts +4 -0
- package/dist/query/index.js +19 -0
- package/dist/query/wasm.d.ts +20 -0
- package/dist/query/wasm.js +890 -0
- package/dist/storage/CLAUDE.md +23 -0
- package/dist/storage/adapter.d.ts +21 -0
- package/dist/storage/adapter.js +1 -0
- package/dist/storage/browser-azure.d.ts +25 -0
- package/dist/storage/browser-azure.js +271 -0
- package/dist/storage/browser-cloud.d.ts +32 -0
- package/dist/storage/browser-cloud.js +293 -0
- package/dist/storage/index.d.ts +11 -0
- package/dist/storage/index.js +37 -0
- package/dist/storage/url-adapter.d.ts +19 -0
- package/dist/storage/url-adapter.js +51 -0
- package/dist/stores/CLAUDE.md +29 -0
- package/dist/stores/browser.svelte.d.ts +28 -0
- package/dist/stores/browser.svelte.js +160 -0
- package/dist/stores/connections.svelte.d.ts +56 -0
- package/dist/stores/connections.svelte.js +272 -0
- package/dist/stores/credentials.svelte.d.ts +56 -0
- package/dist/stores/credentials.svelte.js +79 -0
- package/dist/stores/files.svelte.d.ts +20 -0
- package/dist/stores/files.svelte.js +76 -0
- package/dist/stores/query-history.svelte.d.ts +16 -0
- package/dist/stores/query-history.svelte.js +57 -0
- package/dist/stores/safelock.svelte.d.ts +8 -0
- package/dist/stores/safelock.svelte.js +52 -0
- package/dist/stores/settings.svelte.d.ts +11 -0
- package/dist/stores/settings.svelte.js +101 -0
- package/dist/stores/tab-resources.svelte.d.ts +25 -0
- package/dist/stores/tab-resources.svelte.js +61 -0
- package/dist/stores/tabs.svelte.d.ts +17 -0
- package/dist/stores/tabs.svelte.js +110 -0
- package/dist/types/notebookjs.d.ts +14 -0
- package/dist/types.d.ts +47 -0
- package/dist/types.js +1 -0
- package/dist/utils/CLAUDE.md +54 -0
- package/dist/utils/analytics.d.ts +10 -0
- package/dist/utils/analytics.js +38 -0
- package/dist/utils/archive.d.ts +70 -0
- package/dist/utils/archive.js +333 -0
- package/dist/utils/column-types.d.ts +5 -0
- package/dist/utils/column-types.js +137 -0
- package/dist/utils/deck.d.ts +98 -0
- package/dist/utils/deck.js +208 -0
- package/dist/utils/evidence-context.d.ts +22 -0
- package/dist/utils/evidence-context.js +56 -0
- package/dist/utils/export.d.ts +2 -0
- package/dist/utils/export.js +51 -0
- package/dist/utils/format.d.ts +14 -0
- package/dist/utils/format.js +56 -0
- package/dist/utils/geoarrow.d.ts +32 -0
- package/dist/utils/geoarrow.js +672 -0
- package/dist/utils/hex.d.ts +10 -0
- package/dist/utils/hex.js +27 -0
- package/dist/utils/host-detection.d.ts +23 -0
- package/dist/utils/host-detection.js +289 -0
- package/dist/utils/map-selection.d.ts +12 -0
- package/dist/utils/map-selection.js +45 -0
- package/dist/utils/markdown-sql.d.ts +30 -0
- package/dist/utils/markdown-sql.js +73 -0
- package/dist/utils/markdown.d.ts +18 -0
- package/dist/utils/markdown.js +146 -0
- package/dist/utils/model3d.d.ts +13 -0
- package/dist/utils/model3d.js +62 -0
- package/dist/utils/parquet-metadata.d.ts +58 -0
- package/dist/utils/parquet-metadata.js +228 -0
- package/dist/utils/pdf.d.ts +8 -0
- package/dist/utils/pdf.js +28 -0
- package/dist/utils/pmtiles-tile.d.ts +38 -0
- package/dist/utils/pmtiles-tile.js +64 -0
- package/dist/utils/pmtiles.d.ts +46 -0
- package/dist/utils/pmtiles.js +135 -0
- package/dist/utils/shiki.d.ts +8 -0
- package/dist/utils/shiki.js +98 -0
- package/dist/utils/storage-url.d.ts +64 -0
- package/dist/utils/storage-url.js +374 -0
- package/dist/utils/url-state.d.ts +40 -0
- package/dist/utils/url-state.js +113 -0
- package/dist/utils/url.d.ts +27 -0
- package/dist/utils/url.js +115 -0
- package/dist/utils/wkb.d.ts +43 -0
- package/dist/utils/wkb.js +345 -0
- package/dist/utils/zarr.d.ts +39 -0
- package/dist/utils/zarr.js +204 -0
- package/dist/utils.d.ts +12 -0
- package/dist/utils.js +5 -0
- package/package.json +203 -0
|
@@ -0,0 +1,755 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import LocateIcon from '@lucide/svelte/icons/locate';
|
|
3
|
+
import { geojson as fgbGeojson } from 'flatgeobuf';
|
|
4
|
+
import { magicbytes } from 'flatgeobuf/lib/mjs/constants.js';
|
|
5
|
+
import { buildHeader as fgbBuildHeader } from 'flatgeobuf/lib/mjs/generic/featurecollection.js';
|
|
6
|
+
import type { HeaderMeta } from 'flatgeobuf/lib/mjs/header-meta.js';
|
|
7
|
+
import { HttpReader } from 'flatgeobuf/lib/mjs/http-reader.js';
|
|
8
|
+
import type maplibregl from 'maplibre-gl';
|
|
9
|
+
import proj4 from 'proj4';
|
|
10
|
+
import { onDestroy, untrack } from 'svelte';
|
|
11
|
+
import { t } from '../../i18n/index.svelte.js';
|
|
12
|
+
import { settings } from '../../stores/settings.svelte.js';
|
|
13
|
+
import { tabResources } from '../../stores/tab-resources.svelte.js';
|
|
14
|
+
import type { Tab } from '../../types';
|
|
15
|
+
import {
|
|
16
|
+
buildSelectionLayer,
|
|
17
|
+
geojsonFillColor,
|
|
18
|
+
geojsonLineColor,
|
|
19
|
+
hoverCursor,
|
|
20
|
+
loadDeckModules
|
|
21
|
+
} from '../../utils/deck.js';
|
|
22
|
+
import { buildHttpsUrl } from '../../utils/url.js';
|
|
23
|
+
import AttributeTable from './map/AttributeTable.svelte';
|
|
24
|
+
import MapContainer from './map/MapContainer.svelte';
|
|
25
|
+
|
|
26
|
+
let { tab }: { tab: Tab } = $props();
|
|
27
|
+
|
|
28
|
+
const BATCH_SIZE = 1000;
|
|
29
|
+
|
|
30
|
+
let loading = $state(true);
|
|
31
|
+
let streaming = $state(false);
|
|
32
|
+
let error = $state<string | null>(null);
|
|
33
|
+
let featureCount = $state(0);
|
|
34
|
+
let totalFeatures = $state<number | null>(null);
|
|
35
|
+
let selectedFeature = $state<Record<string, any> | null>(null);
|
|
36
|
+
let selectedGeoFeature: GeoJSON.Feature | null = null;
|
|
37
|
+
let showAttributes = $state(false);
|
|
38
|
+
let showInfo = $state(false);
|
|
39
|
+
let bounds = $state<[number, number, number, number] | undefined>();
|
|
40
|
+
let hasMore = $state(false);
|
|
41
|
+
|
|
42
|
+
let firstFeatureCoord = $state<[number, number] | null>(null);
|
|
43
|
+
|
|
44
|
+
let deckModules: { MapboxOverlay: any; GeoJsonLayer: any } | null = null;
|
|
45
|
+
let overlay: any = null;
|
|
46
|
+
let mapRef: maplibregl.Map | null = null;
|
|
47
|
+
let features: GeoJSON.Feature[] = [];
|
|
48
|
+
/** Monotonic counter — bump to signal deck.gl that data changed (avoids spreading the features array). */
|
|
49
|
+
let dataVersion = 0;
|
|
50
|
+
let abortController: AbortController | null = null;
|
|
51
|
+
let activeStreamCancel: (() => void) | null = null;
|
|
52
|
+
let resolveMapReady: (() => void) | null = null;
|
|
53
|
+
let mapReadyPromise: Promise<void> | null = null;
|
|
54
|
+
|
|
55
|
+
// Stored from preview for load-all (skip index)
|
|
56
|
+
let storedHeader: HeaderMeta | null = null;
|
|
57
|
+
let storedFeatureOffset = 0;
|
|
58
|
+
|
|
59
|
+
// proj4 converter for reprojecting from source CRS → WGS84
|
|
60
|
+
let proj4Forward: ((coord: [number, number]) => [number, number]) | null = null;
|
|
61
|
+
|
|
62
|
+
// Header metadata from FlatGeobuf
|
|
63
|
+
let headerInfo = $state<{
|
|
64
|
+
geometryType: string;
|
|
65
|
+
featuresCount: number;
|
|
66
|
+
columns: { name: string; type: number }[];
|
|
67
|
+
crs: { org: string | null; code: number; name: string | null } | null;
|
|
68
|
+
title: string | null;
|
|
69
|
+
description: string | null;
|
|
70
|
+
hasIndex: boolean;
|
|
71
|
+
} | null>(null);
|
|
72
|
+
|
|
73
|
+
const WGS84_CODES = new Set([4326, 4979, 4267, 4269]);
|
|
74
|
+
const CRS84_NAMES = ['CRS84', 'CRS 84', 'OGC:CRS84'];
|
|
75
|
+
|
|
76
|
+
/** Returns true if the header CRS is WGS84/CRS84 or absent (assumed WGS84). */
|
|
77
|
+
function isWgs84Crs(crs: HeaderMeta['crs']): boolean {
|
|
78
|
+
if (!crs) return true; // no CRS declared → assume WGS84
|
|
79
|
+
if (crs.code && WGS84_CODES.has(crs.code)) return true;
|
|
80
|
+
if (crs.name && CRS84_NAMES.some((n) => crs.name!.includes(n))) return true;
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Fetch a proj4 definition string from epsg.io for the given EPSG code. */
|
|
85
|
+
async function fetchProj4Def(code: number): Promise<string> {
|
|
86
|
+
const resp = await fetch(`https://epsg.io/${code}.proj4`);
|
|
87
|
+
if (!resp.ok) throw new Error(`Failed to fetch proj4 definition for EPSG:${code}`);
|
|
88
|
+
const text = (await resp.text()).trim();
|
|
89
|
+
if (!text || text.startsWith('<')) throw new Error(`Invalid proj4 definition for EPSG:${code}`);
|
|
90
|
+
return text;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Recursively transform all coordinates in a GeoJSON geometry in-place. */
|
|
94
|
+
function reprojectGeometry(
|
|
95
|
+
geometry: GeoJSON.Geometry,
|
|
96
|
+
forward: (coord: [number, number]) => [number, number]
|
|
97
|
+
) {
|
|
98
|
+
if (!geometry) return;
|
|
99
|
+
if (geometry.type === 'GeometryCollection') {
|
|
100
|
+
for (const g of geometry.geometries) reprojectGeometry(g, forward);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
reprojectCoords((geometry as any).coordinates, forward);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function reprojectCoords(coords: any, forward: (coord: [number, number]) => [number, number]) {
|
|
107
|
+
if (!coords) return;
|
|
108
|
+
if (typeof coords[0] === 'number' && coords.length >= 2) {
|
|
109
|
+
const [x, y] = forward([coords[0], coords[1]]);
|
|
110
|
+
coords[0] = x;
|
|
111
|
+
coords[1] = y;
|
|
112
|
+
} else if (Array.isArray(coords[0])) {
|
|
113
|
+
for (const c of coords) reprojectCoords(c, forward);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Map exotic OGC/ISO geometry types to standard GeoJSON equivalents.
|
|
118
|
+
* deck.gl only understands the 7 GeoJSON types. */
|
|
119
|
+
const GEOJSON_TYPE_MAP: Record<string, string> = {
|
|
120
|
+
MultiSurface: 'MultiPolygon',
|
|
121
|
+
CurvePolygon: 'Polygon',
|
|
122
|
+
Surface: 'Polygon',
|
|
123
|
+
CompoundCurve: 'LineString',
|
|
124
|
+
CircularString: 'LineString',
|
|
125
|
+
Curve: 'LineString',
|
|
126
|
+
MultiCurve: 'MultiLineString',
|
|
127
|
+
PolyhedralSurface: 'MultiPolygon',
|
|
128
|
+
TIN: 'MultiPolygon',
|
|
129
|
+
Triangle: 'Polygon'
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
/** Normalize a feature's geometry type to valid GeoJSON if needed. */
|
|
133
|
+
function normalizeGeometryType(geometry: GeoJSON.Geometry) {
|
|
134
|
+
const mapped = GEOJSON_TYPE_MAP[geometry.type];
|
|
135
|
+
if (mapped) (geometry as any).type = mapped;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const GEOM_TYPE_NAMES: Record<number, string> = {
|
|
139
|
+
0: 'Unknown',
|
|
140
|
+
1: 'Point',
|
|
141
|
+
2: 'LineString',
|
|
142
|
+
3: 'Polygon',
|
|
143
|
+
4: 'MultiPoint',
|
|
144
|
+
5: 'MultiLineString',
|
|
145
|
+
6: 'MultiPolygon',
|
|
146
|
+
7: 'GeometryCollection',
|
|
147
|
+
8: 'CircularString',
|
|
148
|
+
9: 'CompoundCurve',
|
|
149
|
+
10: 'CurvePolygon',
|
|
150
|
+
11: 'MultiCurve',
|
|
151
|
+
12: 'MultiSurface',
|
|
152
|
+
13: 'Curve',
|
|
153
|
+
14: 'Surface',
|
|
154
|
+
15: 'PolyhedralSurface',
|
|
155
|
+
16: 'TIN',
|
|
156
|
+
17: 'Triangle'
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
function populateHeaderInfo(header: HeaderMeta) {
|
|
160
|
+
totalFeatures = header.featuresCount;
|
|
161
|
+
headerInfo = {
|
|
162
|
+
geometryType: GEOM_TYPE_NAMES[header.geometryType] ?? `Type ${header.geometryType}`,
|
|
163
|
+
featuresCount: header.featuresCount,
|
|
164
|
+
columns: (header.columns ?? []).map((c) => ({ name: c.name, type: c.type })),
|
|
165
|
+
crs: header.crs ? { org: header.crs.org, code: header.crs.code, name: header.crs.name } : null,
|
|
166
|
+
title: header.title,
|
|
167
|
+
description: header.description,
|
|
168
|
+
hasIndex: header.indexNodeSize > 0
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
if (header.envelope && header.envelope.length >= 4) {
|
|
172
|
+
const [minX, minY, maxX, maxY] = header.envelope;
|
|
173
|
+
// Only use envelope for fitBounds if coordinates are within WGS84 range.
|
|
174
|
+
// Projected CRS files (e.g. EPSG:3310) have envelope in meters — skip fitBounds
|
|
175
|
+
// and let flyToFeaturesBounds() handle it after features are streamed.
|
|
176
|
+
if (minX >= -180 && maxX <= 180 && minY >= -90 && maxY <= 90) {
|
|
177
|
+
bounds = [minX, minY, maxX, maxY];
|
|
178
|
+
mapRef?.fitBounds(bounds, { padding: 40 });
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Drill into nested coordinate arrays to find the first [lng, lat] pair. */
|
|
184
|
+
function extractFirstCoord(coords: any): [number, number] | null {
|
|
185
|
+
if (!Array.isArray(coords) || coords.length === 0) return null;
|
|
186
|
+
if (typeof coords[0] === 'number') return [coords[0] as number, coords[1] as number];
|
|
187
|
+
return extractFirstCoord(coords[0]);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function flyToFirstFeature() {
|
|
191
|
+
if (!mapRef || !firstFeatureCoord) return;
|
|
192
|
+
mapRef.flyTo({ center: firstFeatureCoord, zoom: 14 });
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function cleanup() {
|
|
196
|
+
resolveMapReady?.();
|
|
197
|
+
resolveMapReady = null;
|
|
198
|
+
hasMore = false;
|
|
199
|
+
firstFeatureCoord = null;
|
|
200
|
+
// Force-close any active stream + HTTP connection
|
|
201
|
+
activeStreamCancel?.();
|
|
202
|
+
activeStreamCancel = null;
|
|
203
|
+
if (abortController) {
|
|
204
|
+
abortController.abort();
|
|
205
|
+
abortController = null;
|
|
206
|
+
}
|
|
207
|
+
if (overlay && mapRef) {
|
|
208
|
+
try {
|
|
209
|
+
mapRef.removeControl(overlay);
|
|
210
|
+
} catch {
|
|
211
|
+
/* already removed */
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
overlay = null;
|
|
215
|
+
mapRef = null;
|
|
216
|
+
features = [];
|
|
217
|
+
dataVersion = 0;
|
|
218
|
+
storedHeader = null;
|
|
219
|
+
storedFeatureOffset = 0;
|
|
220
|
+
proj4Forward = null;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
$effect(() => {
|
|
224
|
+
if (!tab) return;
|
|
225
|
+
const _tabId = tab.id;
|
|
226
|
+
untrack(() => {
|
|
227
|
+
loadFlatGeobuf();
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
$effect(() => {
|
|
232
|
+
const id = tab.id;
|
|
233
|
+
const unregister = tabResources.register(id, cleanup);
|
|
234
|
+
return unregister;
|
|
235
|
+
});
|
|
236
|
+
onDestroy(cleanup);
|
|
237
|
+
|
|
238
|
+
async function loadFlatGeobuf() {
|
|
239
|
+
console.log('[FGB]', 'loadFlatGeobuf() start');
|
|
240
|
+
cleanup();
|
|
241
|
+
|
|
242
|
+
loading = true;
|
|
243
|
+
streaming = false;
|
|
244
|
+
error = null;
|
|
245
|
+
features = [];
|
|
246
|
+
featureCount = 0;
|
|
247
|
+
totalFeatures = null;
|
|
248
|
+
headerInfo = null;
|
|
249
|
+
bounds = undefined;
|
|
250
|
+
hasMore = false;
|
|
251
|
+
|
|
252
|
+
try {
|
|
253
|
+
mapReadyPromise = new Promise<void>((r) => {
|
|
254
|
+
resolveMapReady = r;
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
deckModules = await loadDeckModules();
|
|
258
|
+
loading = false;
|
|
259
|
+
streaming = true;
|
|
260
|
+
|
|
261
|
+
await mapReadyPromise;
|
|
262
|
+
if (!overlay) return;
|
|
263
|
+
|
|
264
|
+
// Read header via range requests (fast: 1-2 small requests)
|
|
265
|
+
// Gets metadata + feature offset to skip the spatial index
|
|
266
|
+
await readHeaderWithRangeRequests();
|
|
267
|
+
|
|
268
|
+
// Set up on-the-fly reprojection if the file uses a non-WGS84 CRS
|
|
269
|
+
proj4Forward = null;
|
|
270
|
+
if (storedHeader?.crs && !isWgs84Crs(storedHeader.crs)) {
|
|
271
|
+
const code = storedHeader.crs.code;
|
|
272
|
+
const crsLabel =
|
|
273
|
+
storedHeader.crs.org && code
|
|
274
|
+
? `${storedHeader.crs.org}:${code}`
|
|
275
|
+
: (storedHeader.crs.name ?? 'unknown');
|
|
276
|
+
console.log('[FGB]', 'Projected CRS detected:', crsLabel, '→ fetching proj4 definition');
|
|
277
|
+
|
|
278
|
+
try {
|
|
279
|
+
const proj4Def = await fetchProj4Def(code);
|
|
280
|
+
const converter = proj4(proj4Def, 'EPSG:4326') as any;
|
|
281
|
+
proj4Forward = (coord) => converter.forward(coord);
|
|
282
|
+
console.log('[FGB]', 'Reprojection ready:', crsLabel, '→ WGS84');
|
|
283
|
+
|
|
284
|
+
// Reproject envelope bounds so fitBounds works
|
|
285
|
+
if (storedHeader.envelope && storedHeader.envelope.length >= 4) {
|
|
286
|
+
const [x0, y0, x1, y1] = storedHeader.envelope;
|
|
287
|
+
const sw = proj4Forward([x0, y0]);
|
|
288
|
+
const ne = proj4Forward([x1, y1]);
|
|
289
|
+
bounds = [sw[0], sw[1], ne[0], ne[1]];
|
|
290
|
+
mapRef?.fitBounds(bounds, { padding: 40 });
|
|
291
|
+
}
|
|
292
|
+
} catch (err) {
|
|
293
|
+
console.error('[FGB]', 'Failed to set up reprojection:', err);
|
|
294
|
+
error = `Cannot reproject CRS ${crsLabel} → WGS84: ${err instanceof Error ? err.message : err}`;
|
|
295
|
+
streaming = false;
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Stream features (skips index if header was read, else sequential)
|
|
301
|
+
await streamFeatures(settings.featureLimit);
|
|
302
|
+
} catch (err) {
|
|
303
|
+
console.error('[FGB]', 'loadFlatGeobuf error:', err);
|
|
304
|
+
if (err instanceof DOMException && err.name === 'AbortError') return;
|
|
305
|
+
error = err instanceof Error ? err.message : String(err);
|
|
306
|
+
loading = false;
|
|
307
|
+
} finally {
|
|
308
|
+
console.log('[FGB]', 'done, features:', features.length);
|
|
309
|
+
streaming = false;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Read header via range requests (fast: 1-2 small requests).
|
|
315
|
+
* Stores header + feature offset for the composite stream approach.
|
|
316
|
+
*/
|
|
317
|
+
async function readHeaderWithRangeRequests(): Promise<boolean> {
|
|
318
|
+
const url = buildHttpsUrl(tab);
|
|
319
|
+
|
|
320
|
+
let reader: HttpReader;
|
|
321
|
+
try {
|
|
322
|
+
reader = await HttpReader.open(url, false);
|
|
323
|
+
} catch (e) {
|
|
324
|
+
console.warn('[FGB]', 'HttpReader.open failed:', e);
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const header = reader.header;
|
|
329
|
+
console.log('[FGB]', 'header:', {
|
|
330
|
+
geometryType: header.geometryType,
|
|
331
|
+
featuresCount: header.featuresCount,
|
|
332
|
+
indexNodeSize: header.indexNodeSize,
|
|
333
|
+
envelope: header.envelope ? Array.from(header.envelope) : null,
|
|
334
|
+
columns: header.columns?.length
|
|
335
|
+
});
|
|
336
|
+
populateHeaderInfo(header);
|
|
337
|
+
|
|
338
|
+
storedHeader = header;
|
|
339
|
+
storedFeatureOffset = reader.lengthBeforeFeatures();
|
|
340
|
+
console.log(
|
|
341
|
+
'[FGB]',
|
|
342
|
+
'featureOffset:',
|
|
343
|
+
storedFeatureOffset,
|
|
344
|
+
'(index ~',
|
|
345
|
+
((storedFeatureOffset - 12) / 1024 / 1024).toFixed(1),
|
|
346
|
+
'MB skipped)'
|
|
347
|
+
);
|
|
348
|
+
return true;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/** Load all features — skips the spatial index using a Range request. */
|
|
352
|
+
async function loadAllFeatures() {
|
|
353
|
+
if (!overlay) return;
|
|
354
|
+
hasMore = false;
|
|
355
|
+
streaming = true;
|
|
356
|
+
|
|
357
|
+
try {
|
|
358
|
+
features = [];
|
|
359
|
+
featureCount = 0;
|
|
360
|
+
await streamFeatures();
|
|
361
|
+
} catch (err) {
|
|
362
|
+
console.error('[FGB]', 'loadAllFeatures error:', err);
|
|
363
|
+
if (err instanceof DOMException && err.name === 'AbortError') return;
|
|
364
|
+
error = err instanceof Error ? err.message : String(err);
|
|
365
|
+
} finally {
|
|
366
|
+
console.log('[FGB]', 'loadAllFeatures done, features:', features.length);
|
|
367
|
+
streaming = false;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Stream features sequentially.
|
|
373
|
+
* If storedHeader is available, skips the index with a Range request + composite stream.
|
|
374
|
+
*/
|
|
375
|
+
async function streamFeatures(limit?: number) {
|
|
376
|
+
const ac = new AbortController();
|
|
377
|
+
abortController = ac;
|
|
378
|
+
const url = buildHttpsUrl(tab);
|
|
379
|
+
const t0 = performance.now();
|
|
380
|
+
|
|
381
|
+
let iter: AsyncGenerator;
|
|
382
|
+
activeStreamCancel = null;
|
|
383
|
+
|
|
384
|
+
if (storedHeader && storedFeatureOffset > 0) {
|
|
385
|
+
const fakeHeaderBytes = fgbBuildHeader({
|
|
386
|
+
...storedHeader,
|
|
387
|
+
indexNodeSize: 0,
|
|
388
|
+
envelope: null
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
const featureResp = await fetch(url, {
|
|
392
|
+
headers: { Range: `bytes=${storedFeatureOffset}-` },
|
|
393
|
+
signal: ac.signal
|
|
394
|
+
});
|
|
395
|
+
console.log(
|
|
396
|
+
'[FGB]',
|
|
397
|
+
'Range fetch:',
|
|
398
|
+
featureResp.status,
|
|
399
|
+
featureResp.headers.get('content-range')
|
|
400
|
+
);
|
|
401
|
+
if (!featureResp.ok && featureResp.status !== 206)
|
|
402
|
+
throw new Error(`HTTP ${featureResp.status}: ${featureResp.statusText}`);
|
|
403
|
+
if (!featureResp.body) throw new Error('No response body');
|
|
404
|
+
|
|
405
|
+
const composite = createCompositeStream(fakeHeaderBytes, featureResp.body);
|
|
406
|
+
activeStreamCancel = composite.cancel;
|
|
407
|
+
// Don't pass populateHeaderInfo — we already have the real header from
|
|
408
|
+
// readHeaderWithRangeRequests(). The composite stream has a fake header
|
|
409
|
+
// (indexNodeSize: 0, envelope: null) that would overwrite the real metadata.
|
|
410
|
+
iter = fgbGeojson.deserialize(composite.stream) as AsyncGenerator;
|
|
411
|
+
} else {
|
|
412
|
+
const response = await fetch(url, { signal: ac.signal });
|
|
413
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
414
|
+
if (!response.body) throw new Error('No response body');
|
|
415
|
+
iter = fgbGeojson.deserialize(response.body, undefined, (h: any) =>
|
|
416
|
+
populateHeaderInfo(h)
|
|
417
|
+
) as AsyncGenerator;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
let batchCount = 0;
|
|
421
|
+
let lastUpdateTime = Date.now();
|
|
422
|
+
let flewToFeatures = false;
|
|
423
|
+
let hitLimit = false;
|
|
424
|
+
|
|
425
|
+
try {
|
|
426
|
+
for await (const feature of iter) {
|
|
427
|
+
if (ac.signal.aborted) return;
|
|
428
|
+
|
|
429
|
+
const f = feature as GeoJSON.Feature;
|
|
430
|
+
if (f.geometry) {
|
|
431
|
+
normalizeGeometryType(f.geometry);
|
|
432
|
+
if (proj4Forward) reprojectGeometry(f.geometry, proj4Forward);
|
|
433
|
+
}
|
|
434
|
+
// Detect actual geometry type from first feature when header says Unknown
|
|
435
|
+
if (features.length === 0 && f.geometry && headerInfo?.geometryType === 'Unknown') {
|
|
436
|
+
headerInfo = { ...headerInfo, geometryType: f.geometry.type };
|
|
437
|
+
}
|
|
438
|
+
// Capture first feature coordinate for fly-to button
|
|
439
|
+
if (features.length === 0 && f.geometry) {
|
|
440
|
+
firstFeatureCoord = extractFirstCoord((f.geometry as any).coordinates);
|
|
441
|
+
}
|
|
442
|
+
features.push(f);
|
|
443
|
+
batchCount++;
|
|
444
|
+
|
|
445
|
+
const now = Date.now();
|
|
446
|
+
if (batchCount >= BATCH_SIZE || now - lastUpdateTime > 200) {
|
|
447
|
+
featureCount = features.length;
|
|
448
|
+
updateLayer();
|
|
449
|
+
if (!flewToFeatures) {
|
|
450
|
+
flyToFeaturesBounds();
|
|
451
|
+
flewToFeatures = true;
|
|
452
|
+
}
|
|
453
|
+
batchCount = 0;
|
|
454
|
+
lastUpdateTime = now;
|
|
455
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
456
|
+
if (ac.signal.aborted) return;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
if (limit && features.length >= limit) {
|
|
460
|
+
hitLimit = true;
|
|
461
|
+
break;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
} finally {
|
|
465
|
+
// Force-close the HTTP connection. break from for-await doesn't release
|
|
466
|
+
// the flatgeobuf library's internal stream reader, so the browser keeps
|
|
467
|
+
// downloading gigabytes in the background unless we explicitly cancel.
|
|
468
|
+
if (hitLimit) {
|
|
469
|
+
activeStreamCancel?.();
|
|
470
|
+
activeStreamCancel = null;
|
|
471
|
+
ac.abort();
|
|
472
|
+
console.log('[FGB]', 'force-closed connection after', features.length, 'features');
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
if (features.length === 0) {
|
|
477
|
+
error = 'No features found in FlatGeobuf file';
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
featureCount = features.length;
|
|
482
|
+
updateLayer();
|
|
483
|
+
if (!flewToFeatures) flyToFeaturesBounds();
|
|
484
|
+
console.log(
|
|
485
|
+
'[FGB]',
|
|
486
|
+
'stream done:',
|
|
487
|
+
features.length,
|
|
488
|
+
'features in',
|
|
489
|
+
(performance.now() - t0).toFixed(0),
|
|
490
|
+
'ms'
|
|
491
|
+
);
|
|
492
|
+
|
|
493
|
+
if (hitLimit) {
|
|
494
|
+
hasMore = totalFeatures != null && totalFeatures > features.length;
|
|
495
|
+
} else {
|
|
496
|
+
hasMore = false;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/** Compute bounding box of current features and fly the map to it. */
|
|
501
|
+
function flyToFeaturesBounds() {
|
|
502
|
+
if (!mapRef || features.length === 0) return;
|
|
503
|
+
let minLng = Infinity,
|
|
504
|
+
minLat = Infinity,
|
|
505
|
+
maxLng = -Infinity,
|
|
506
|
+
maxLat = -Infinity;
|
|
507
|
+
|
|
508
|
+
function processCoords(coords: any) {
|
|
509
|
+
if (!coords) return;
|
|
510
|
+
if (typeof coords[0] === 'number' && coords.length >= 2) {
|
|
511
|
+
minLng = Math.min(minLng, coords[0]);
|
|
512
|
+
minLat = Math.min(minLat, coords[1]);
|
|
513
|
+
maxLng = Math.max(maxLng, coords[0]);
|
|
514
|
+
maxLat = Math.max(maxLat, coords[1]);
|
|
515
|
+
} else if (Array.isArray(coords[0])) {
|
|
516
|
+
for (const c of coords) processCoords(c);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
for (const f of features) {
|
|
521
|
+
processCoords((f.geometry as any)?.coordinates);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
if (minLng !== Infinity && minLng >= -180 && maxLng <= 180 && minLat >= -90 && maxLat <= 90) {
|
|
525
|
+
bounds = [minLng, minLat, maxLng, maxLat];
|
|
526
|
+
mapRef.fitBounds(bounds, { padding: 40 });
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/** Create a ReadableStream: magic bytes + header bytes + feature data stream.
|
|
531
|
+
* Returns both the stream and a cancel() handle to force-close the connection. */
|
|
532
|
+
function createCompositeStream(
|
|
533
|
+
headerBytes: Uint8Array,
|
|
534
|
+
featureStream: ReadableStream<Uint8Array>
|
|
535
|
+
): { stream: ReadableStream<Uint8Array>; cancel: () => void } {
|
|
536
|
+
const featureReader = featureStream.getReader();
|
|
537
|
+
let headerSent = false;
|
|
538
|
+
|
|
539
|
+
const stream = new ReadableStream({
|
|
540
|
+
pull(controller) {
|
|
541
|
+
if (!headerSent) {
|
|
542
|
+
controller.enqueue(new Uint8Array(magicbytes));
|
|
543
|
+
controller.enqueue(headerBytes);
|
|
544
|
+
headerSent = true;
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
return featureReader.read().then(({ value, done }) => {
|
|
548
|
+
if (done) {
|
|
549
|
+
controller.close();
|
|
550
|
+
} else {
|
|
551
|
+
controller.enqueue(value);
|
|
552
|
+
}
|
|
553
|
+
});
|
|
554
|
+
},
|
|
555
|
+
cancel() {
|
|
556
|
+
featureReader.cancel();
|
|
557
|
+
}
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
return {
|
|
561
|
+
stream,
|
|
562
|
+
cancel: () => {
|
|
563
|
+
try {
|
|
564
|
+
featureReader.cancel();
|
|
565
|
+
} catch {
|
|
566
|
+
/* already released */
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
function updateLayer() {
|
|
573
|
+
if (!overlay || !deckModules) return;
|
|
574
|
+
|
|
575
|
+
// Bump version so deck.gl sees a new data identity without copying the array.
|
|
576
|
+
dataVersion++;
|
|
577
|
+
const fc = { type: 'FeatureCollection' as const, features, _v: dataVersion };
|
|
578
|
+
|
|
579
|
+
const { GeoJsonLayer } = deckModules;
|
|
580
|
+
overlay.setProps({
|
|
581
|
+
layers: [
|
|
582
|
+
new GeoJsonLayer({
|
|
583
|
+
id: 'flatgeobuf-data',
|
|
584
|
+
data: fc,
|
|
585
|
+
dataComparator: (a: any, b: any) => a._v === b._v,
|
|
586
|
+
pickable: true,
|
|
587
|
+
stroked: true,
|
|
588
|
+
filled: true,
|
|
589
|
+
pointType: 'circle',
|
|
590
|
+
getFillColor: geojsonFillColor as any,
|
|
591
|
+
getLineColor: geojsonLineColor as any,
|
|
592
|
+
getPointRadius: 6,
|
|
593
|
+
getLineWidth: 2.5,
|
|
594
|
+
lineWidthMinPixels: 1.5,
|
|
595
|
+
pointRadiusMinPixels: 4,
|
|
596
|
+
pointRadiusMaxPixels: 12,
|
|
597
|
+
autoHighlight: true,
|
|
598
|
+
highlightColor: [255, 255, 255, 100],
|
|
599
|
+
onHover: mapRef ? hoverCursor(mapRef) : undefined,
|
|
600
|
+
onClick: (info: any) => {
|
|
601
|
+
if (info.object?.properties) {
|
|
602
|
+
selectedFeature = { ...info.object.properties };
|
|
603
|
+
selectedGeoFeature = info.object;
|
|
604
|
+
showAttributes = true;
|
|
605
|
+
// Re-render with selection layer
|
|
606
|
+
updateLayer();
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}),
|
|
610
|
+
buildSelectionLayer(GeoJsonLayer, selectedGeoFeature)
|
|
611
|
+
].filter(Boolean)
|
|
612
|
+
});
|
|
613
|
+
mapRef?.triggerRepaint();
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
function onMapReady(map: maplibregl.Map) {
|
|
617
|
+
if (!deckModules) return;
|
|
618
|
+
mapRef = map;
|
|
619
|
+
|
|
620
|
+
const { MapboxOverlay } = deckModules;
|
|
621
|
+
overlay = new MapboxOverlay({
|
|
622
|
+
interleaved: false,
|
|
623
|
+
layers: []
|
|
624
|
+
});
|
|
625
|
+
map.addControl(overlay as any);
|
|
626
|
+
resolveMapReady?.();
|
|
627
|
+
|
|
628
|
+
if (bounds) map.fitBounds(bounds, { padding: 40 });
|
|
629
|
+
}
|
|
630
|
+
</script>
|
|
631
|
+
|
|
632
|
+
<div class="relative flex h-full overflow-hidden">
|
|
633
|
+
{#if loading}
|
|
634
|
+
<div class="flex flex-1 items-center justify-center">
|
|
635
|
+
<p class="text-sm text-zinc-400">{t('map.loadingFgb')}</p>
|
|
636
|
+
</div>
|
|
637
|
+
{:else if error && featureCount === 0}
|
|
638
|
+
<div class="flex flex-1 items-center justify-center">
|
|
639
|
+
<p class="text-sm text-red-400">{error}</p>
|
|
640
|
+
</div>
|
|
641
|
+
{:else}
|
|
642
|
+
<div class="flex-1">
|
|
643
|
+
<MapContainer {onMapReady} {bounds} />
|
|
644
|
+
</div>
|
|
645
|
+
|
|
646
|
+
<!-- Floating feature count badge + fly-to + load-all button -->
|
|
647
|
+
<div class="absolute left-2 top-2 z-10 flex flex-col gap-1">
|
|
648
|
+
<div class="flex items-center gap-1">
|
|
649
|
+
<div
|
|
650
|
+
class="pointer-events-none rounded bg-card/80 px-2 py-1 text-xs text-card-foreground backdrop-blur-sm"
|
|
651
|
+
>
|
|
652
|
+
{#if streaming}
|
|
653
|
+
<span class="animate-pulse">
|
|
654
|
+
{featureCount.toLocaleString()} features...
|
|
655
|
+
</span>
|
|
656
|
+
{:else if featureCount > 0}
|
|
657
|
+
{featureCount.toLocaleString()} features{#if totalFeatures && featureCount >= settings.featureLimit}{' '}
|
|
658
|
+
<span class="text-amber-300">
|
|
659
|
+
of {totalFeatures.toLocaleString()} (limit)
|
|
660
|
+
</span>
|
|
661
|
+
{/if}
|
|
662
|
+
{/if}
|
|
663
|
+
</div>
|
|
664
|
+
{#if firstFeatureCoord && !streaming}
|
|
665
|
+
<button
|
|
666
|
+
class="rounded bg-card/80 p-1.5 text-card-foreground backdrop-blur-sm hover:bg-card"
|
|
667
|
+
onclick={flyToFirstFeature}
|
|
668
|
+
title={t('map.flyToFirst')}
|
|
669
|
+
>
|
|
670
|
+
<LocateIcon class="size-3.5" />
|
|
671
|
+
</button>
|
|
672
|
+
{/if}
|
|
673
|
+
</div>
|
|
674
|
+
{#if hasMore && !streaming}
|
|
675
|
+
<button
|
|
676
|
+
class="rounded bg-primary/90 px-2 py-1 text-xs text-primary-foreground backdrop-blur-sm hover:bg-primary"
|
|
677
|
+
onclick={loadAllFeatures}
|
|
678
|
+
>
|
|
679
|
+
Stream all {(totalFeatures ?? 0).toLocaleString()}
|
|
680
|
+
</button>
|
|
681
|
+
{/if}
|
|
682
|
+
</div>
|
|
683
|
+
|
|
684
|
+
<!-- Floating toggle buttons -->
|
|
685
|
+
<div class="absolute right-2 top-2 z-10 flex gap-1">
|
|
686
|
+
{#if headerInfo}
|
|
687
|
+
<button
|
|
688
|
+
class="rounded bg-card/80 px-2 py-1 text-xs text-card-foreground backdrop-blur-sm hover:bg-card"
|
|
689
|
+
class:ring-1={showInfo}
|
|
690
|
+
class:ring-primary={showInfo}
|
|
691
|
+
onclick={() => (showInfo = !showInfo)}
|
|
692
|
+
>
|
|
693
|
+
{t('map.info')}
|
|
694
|
+
</button>
|
|
695
|
+
{/if}
|
|
696
|
+
{#if selectedFeature}
|
|
697
|
+
<button
|
|
698
|
+
class="rounded bg-card/80 px-2 py-1 text-xs text-card-foreground backdrop-blur-sm hover:bg-card"
|
|
699
|
+
class:ring-1={showAttributes}
|
|
700
|
+
class:ring-primary={showAttributes}
|
|
701
|
+
onclick={() => (showAttributes = !showAttributes)}
|
|
702
|
+
>
|
|
703
|
+
{t('map.attributes')}
|
|
704
|
+
</button>
|
|
705
|
+
{/if}
|
|
706
|
+
</div>
|
|
707
|
+
|
|
708
|
+
{#if showInfo && headerInfo}
|
|
709
|
+
<div
|
|
710
|
+
class="absolute right-2 top-10 z-10 max-h-[70vh] w-64 overflow-auto rounded bg-card/90 p-3 text-xs text-card-foreground backdrop-blur-sm"
|
|
711
|
+
>
|
|
712
|
+
<h3 class="mb-2 font-medium">{t('map.flatgeobufInfo')}</h3>
|
|
713
|
+
<dl class="space-y-1.5">
|
|
714
|
+
{#if headerInfo.title}
|
|
715
|
+
<dt class="text-muted-foreground">{t('mapInfo.title')}</dt>
|
|
716
|
+
<dd>{headerInfo.title}</dd>
|
|
717
|
+
{/if}
|
|
718
|
+
{#if headerInfo.description}
|
|
719
|
+
<dt class="text-muted-foreground">{t('mapInfo.description')}</dt>
|
|
720
|
+
<dd class="opacity-80">{headerInfo.description}</dd>
|
|
721
|
+
{/if}
|
|
722
|
+
<dt class="text-muted-foreground">{t('mapInfo.geometryType')}</dt>
|
|
723
|
+
<dd>{headerInfo.geometryType}</dd>
|
|
724
|
+
<dt class="text-muted-foreground">{t('mapInfo.totalFeatures')}</dt>
|
|
725
|
+
<dd>{headerInfo.featuresCount.toLocaleString()}</dd>
|
|
726
|
+
<dt class="text-muted-foreground">{t('mapInfo.spatialIndex')}</dt>
|
|
727
|
+
<dd>{headerInfo.hasIndex ? t('mapInfo.yesRTree') : t('mapInfo.no')}</dd>
|
|
728
|
+
{#if headerInfo.crs}
|
|
729
|
+
<dt class="text-muted-foreground">{t('mapInfo.crs')}</dt>
|
|
730
|
+
<dd>
|
|
731
|
+
{#if headerInfo.crs.org && headerInfo.crs.code}
|
|
732
|
+
{headerInfo.crs.org}:{headerInfo.crs.code}
|
|
733
|
+
{/if}
|
|
734
|
+
{#if headerInfo.crs.name}
|
|
735
|
+
({headerInfo.crs.name})
|
|
736
|
+
{/if}
|
|
737
|
+
</dd>
|
|
738
|
+
{/if}
|
|
739
|
+
{#if headerInfo.columns.length > 0}
|
|
740
|
+
<dt class="text-muted-foreground">{t('mapInfo.columns')} ({headerInfo.columns.length})</dt>
|
|
741
|
+
{#each headerInfo.columns as col}
|
|
742
|
+
<dd class="ms-2 opacity-80">- {col.name}</dd>
|
|
743
|
+
{/each}
|
|
744
|
+
{/if}
|
|
745
|
+
</dl>
|
|
746
|
+
</div>
|
|
747
|
+
{/if}
|
|
748
|
+
|
|
749
|
+
<AttributeTable
|
|
750
|
+
feature={selectedFeature}
|
|
751
|
+
visible={showAttributes}
|
|
752
|
+
onClose={() => (showAttributes = false)}
|
|
753
|
+
/>
|
|
754
|
+
{/if}
|
|
755
|
+
</div>
|