@jupytergis/base 0.15.0 → 0.16.0-alpha.1
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/lib/commands/BaseCommandIDs.d.ts +5 -0
- package/lib/commands/BaseCommandIDs.js +5 -0
- package/lib/commands/index.js +218 -48
- package/lib/commands/operationCommands.js +2 -2
- package/lib/constants.js +9 -1
- package/lib/{panelview/annotationPanel.js → features/annotations/AnnotationsPanel.js} +1 -1
- package/lib/{annotations → features/annotations}/index.d.ts +1 -0
- package/lib/{annotations → features/annotations}/index.js +1 -0
- package/lib/{console → features/console}/consoleview.js +1 -1
- package/lib/{panelview/filter-panel → features/filter}/Filter.js +4 -4
- package/lib/{panelview/filter-panel → features/filter}/FilterRow.d.ts +3 -2
- package/lib/{panelview/filter-panel → features/filter}/FilterRow.js +2 -1
- package/lib/{panelview/identify-panel → features/identify}/IdentifyPanel.d.ts +2 -0
- package/lib/features/identify/IdentifyPanel.js +102 -0
- package/lib/features/identify/components/FeatureCard.d.ts +17 -0
- package/lib/features/identify/components/FeatureCard.js +26 -0
- package/lib/features/identify/components/FeatureCardHeader.d.ts +11 -0
- package/lib/features/identify/components/FeatureCardHeader.js +30 -0
- package/lib/features/identify/components/FeatureFloater.d.ts +7 -0
- package/lib/features/identify/components/FeatureFloater.js +19 -0
- package/lib/features/identify/components/FeaturePropertyList.d.ts +11 -0
- package/lib/features/identify/components/FeaturePropertyList.js +18 -0
- package/lib/features/identify/components/FeatureRow.d.ts +13 -0
- package/lib/features/identify/components/FeatureRow.js +25 -0
- package/lib/features/identify/components/PropertyEditors.d.ts +44 -0
- package/lib/features/identify/components/PropertyEditors.js +56 -0
- package/lib/features/identify/hooks/useIdentifyPropertyEditor.d.ts +11 -0
- package/lib/features/identify/hooks/useIdentifyPropertyEditor.js +132 -0
- package/lib/features/identify/types/editorTypes.d.ts +21 -0
- package/lib/features/identify/utils/getFeatureIdentifier.d.ts +5 -0
- package/lib/features/identify/utils/getFeatureIdentifier.js +14 -0
- package/lib/features/identify/utils/highlightLayer.d.ts +11 -0
- package/lib/features/identify/utils/highlightLayer.js +57 -0
- package/lib/features/identify/utils/highlightStyle.d.ts +7 -0
- package/lib/features/identify/utils/highlightStyle.js +40 -0
- package/lib/{dialogs/layerBrowserDialog.js → features/layer-browser/index.js} +2 -2
- package/lib/{formbuilder/objectform/layer/heatmapLayerForm.d.ts → features/layers/forms/layer/geoTiffLayerForm.d.ts} +1 -1
- package/lib/{formbuilder/objectform/layer/webGlLayerForm.js → features/layers/forms/layer/geoTiffLayerForm.js} +5 -5
- package/lib/{formbuilder/objectform → features/layers/forms}/layer/hillshadeLayerForm.js +4 -4
- package/lib/{formbuilder/objectform → features/layers/forms}/layer/index.d.ts +1 -2
- package/lib/{formbuilder/objectform → features/layers/forms}/layer/index.js +1 -2
- package/lib/{formbuilder/objectform → features/layers/forms}/layer/layerform.d.ts +1 -1
- package/lib/{formbuilder/objectform → features/layers/forms}/layer/layerform.js +4 -4
- package/lib/features/layers/forms/layer/storySegmentLayerForm.js +150 -0
- package/lib/{formbuilder/objectform → features/layers/forms}/layer/vectorlayerform.js +4 -4
- package/lib/{formbuilder/objectform → features/layers/forms}/source/geojsonsource.js +5 -5
- package/lib/features/layers/forms/source/geopackagesource.d.ts +3 -0
- package/lib/features/layers/forms/source/geopackagesource.js +93 -0
- package/lib/{formbuilder/objectform → features/layers/forms}/source/geotiffsource.js +5 -5
- package/lib/{formbuilder/objectform → features/layers/forms}/source/index.d.ts +1 -0
- package/lib/{formbuilder/objectform → features/layers/forms}/source/index.js +1 -0
- package/lib/{formbuilder/objectform → features/layers/forms}/source/pathbasedsource.js +5 -5
- package/lib/{formbuilder/objectform → features/layers/forms}/source/sourceform.d.ts +1 -1
- package/lib/{formbuilder/objectform → features/layers/forms}/source/sourceform.js +4 -4
- package/lib/{formbuilder/objectform → features/layers/forms}/source/tilesourceform.js +4 -4
- package/lib/{formbuilder/objectform → features/layers/forms}/source/wmsTileSource.d.ts +1 -1
- package/lib/{formbuilder/objectform → features/layers/forms}/source/wmsTileSource.js +6 -6
- package/lib/{dialogs → features/layers}/layerCreationFormDialog.d.ts +1 -1
- package/lib/{dialogs → features/layers}/layerCreationFormDialog.js +1 -1
- package/lib/features/layers/symbology/Grammar.d.ts +11 -0
- package/lib/features/layers/symbology/Grammar.js +316 -0
- package/lib/{dialogs → features/layers}/symbology/classificationModes.d.ts +6 -6
- package/lib/{dialogs → features/layers}/symbology/classificationModes.js +48 -44
- package/lib/{dialogs → features/layers}/symbology/colorRampUtils.d.ts +1 -0
- package/lib/{dialogs → features/layers}/symbology/colorRampUtils.js +12 -1
- package/lib/features/layers/symbology/components/MappingRow.d.ts +40 -0
- package/lib/features/layers/symbology/components/MappingRow.js +520 -0
- package/lib/features/layers/symbology/components/NumericInput.d.ts +20 -0
- package/lib/features/layers/symbology/components/NumericInput.js +44 -0
- package/lib/features/layers/symbology/components/ScaleEditor.d.ts +33 -0
- package/lib/features/layers/symbology/components/ScaleEditor.js +221 -0
- package/lib/{dialogs → features/layers}/symbology/components/color_ramp/ColorRampControls.d.ts +1 -1
- package/lib/{dialogs → features/layers}/symbology/components/color_ramp/ColorRampControls.js +4 -3
- package/lib/{dialogs → features/layers}/symbology/components/color_ramp/ColorRampSelector.d.ts +2 -1
- package/lib/{dialogs → features/layers}/symbology/components/color_ramp/ColorRampSelector.js +6 -1
- package/lib/{dialogs → features/layers}/symbology/components/color_ramp/ModeSelectRow.d.ts +1 -1
- package/lib/{dialogs → features/layers}/symbology/components/color_ramp/RgbaColorPicker.js +39 -9
- package/lib/{dialogs → features/layers}/symbology/components/color_stops/StopContainer.js +1 -1
- package/lib/{dialogs → features/layers}/symbology/components/color_stops/StopRow.d.ts +1 -1
- package/lib/{dialogs → features/layers}/symbology/components/color_stops/StopRow.js +14 -2
- package/lib/features/layers/symbology/grammarToOLLayer.d.ts +27 -0
- package/lib/features/layers/symbology/grammarToOLLayer.js +145 -0
- package/lib/features/layers/symbology/grammarToOLStyle.d.ts +32 -0
- package/lib/features/layers/symbology/grammarToOLStyle.js +487 -0
- package/lib/{dialogs → features/layers}/symbology/hooks/useGetBandInfo.d.ts +1 -1
- package/lib/{dialogs → features/layers}/symbology/hooks/useGetBandInfo.js +1 -1
- package/lib/{dialogs → features/layers}/symbology/hooks/useGetProperties.js +1 -1
- package/lib/{dialogs → features/layers}/symbology/hooks/useGetSymbology.js +4 -2
- package/lib/features/layers/symbology/styleBuilder.d.ts +24 -0
- package/lib/features/layers/symbology/styleBuilder.js +158 -0
- package/lib/{dialogs → features/layers}/symbology/symbologyDialog.d.ts +1 -1
- package/lib/{dialogs → features/layers}/symbology/symbologyDialog.js +11 -13
- package/lib/{dialogs → features/layers}/symbology/symbologyUtils.d.ts +18 -10
- package/lib/{dialogs → features/layers}/symbology/symbologyUtils.js +0 -84
- package/lib/{panelview/objectproperties.d.ts → features/objectproperties/index.d.ts} +1 -1
- package/lib/{panelview/objectproperties.js → features/objectproperties/index.js} +5 -10
- package/lib/{dialogs → features/processing}/ProcessingFormDialog.d.ts +1 -1
- package/lib/{dialogs → features/processing}/ProcessingFormDialog.js +28 -14
- package/lib/features/processing/forms/MapExtentToggle.d.ts +13 -0
- package/lib/features/processing/forms/MapExtentToggle.js +20 -0
- package/lib/features/processing/forms/clipRasterByExtentForm.d.ts +10 -0
- package/lib/features/processing/forms/clipRasterByExtentForm.js +99 -0
- package/lib/{formbuilder/objectform/process → features/processing/forms}/dissolveProcessForm.js +5 -5
- package/lib/{formbuilder/objectform → features/processing/forms}/processingForm.d.ts +1 -1
- package/lib/{formbuilder/objectform → features/processing/forms}/processingForm.js +6 -6
- package/lib/features/processing/forms/rasterizeForm.d.ts +10 -0
- package/lib/features/processing/forms/rasterizeForm.js +75 -0
- package/lib/features/processing/forms/useMapExtent.d.ts +22 -0
- package/lib/features/processing/forms/useMapExtent.js +57 -0
- package/lib/{processing → features/processing}/index.d.ts +19 -2
- package/lib/features/processing/index.js +1246 -0
- package/lib/{processing → features/processing}/processingCommands.d.ts +1 -1
- package/lib/features/processing/processingCommands.js +168 -0
- package/lib/features/processing/serverProcessing.d.ts +51 -0
- package/lib/features/processing/serverProcessing.js +99 -0
- package/lib/{stacBrowser → features/stac-browser}/components/StacPanel.js +2 -2
- package/lib/{stacBrowser → features/stac-browser}/components/filter-extension/QueryableComboBox.js +5 -5
- package/lib/{stacBrowser → features/stac-browser}/components/filter-extension/QueryableRow.js +1 -1
- package/lib/{stacBrowser → features/stac-browser}/components/filter-extension/StacFilterExtensionPanel.js +2 -2
- package/lib/{stacBrowser → features/stac-browser}/components/filter-extension/StacQueryableFilters.js +1 -1
- package/lib/{stacBrowser → features/stac-browser}/components/geodes/StacFilterSection.js +3 -3
- package/lib/{stacBrowser → features/stac-browser}/components/shared/StacPanelResults.js +3 -3
- package/lib/{stacBrowser → features/stac-browser}/components/shared/StacSpatialExtent.js +1 -1
- package/lib/{stacBrowser → features/stac-browser}/components/shared/StacTemporalExtent.js +1 -1
- package/lib/{stacBrowser → features/stac-browser}/context/StacResultsContext.js +2 -2
- package/lib/{stacBrowser → features/stac-browser}/hooks/useGeodesSearch.js +2 -2
- package/lib/{stacBrowser → features/stac-browser}/hooks/useStacFilterExtension.js +3 -3
- package/lib/{stacBrowser → features/stac-browser}/hooks/useStacSearch.js +1 -1
- package/lib/features/stac-browser/types/types.js +1 -0
- package/lib/{panelview/story-maps → features/story}/SpectaPanel.d.ts +4 -1
- package/lib/{panelview/story-maps → features/story}/SpectaPanel.js +3 -4
- package/lib/{panelview/story-maps → features/story}/StoryEditorPanel.js +1 -1
- package/lib/{panelview/story-maps → features/story}/StoryViewerPanel.d.ts +12 -11
- package/lib/features/story/StoryViewerPanel.js +64 -0
- package/lib/features/story/__tests__/fixtures/listStoryTestItems.d.ts +9 -0
- package/lib/features/story/__tests__/fixtures/listStoryTestItems.js +21 -0
- package/lib/features/story/components/ListStoryMapOverlayPanel.d.ts +10 -0
- package/lib/features/story/components/ListStoryMapOverlayPanel.js +11 -0
- package/lib/features/story/components/ListStoryMarkdownMeasurePane.d.ts +11 -0
- package/lib/features/story/components/ListStoryMarkdownMeasurePane.js +55 -0
- package/lib/features/story/components/ListStoryOverlayMarkdown.d.ts +15 -0
- package/lib/features/story/components/ListStoryOverlayMarkdown.js +93 -0
- package/lib/features/story/components/ListStoryStageOverlay.d.ts +11 -0
- package/lib/features/story/components/ListStoryStageOverlay.js +150 -0
- package/lib/features/story/components/ListStoryTitleBar.d.ts +7 -0
- package/lib/features/story/components/ListStoryTitleBar.js +20 -0
- package/lib/features/story/components/ListStoryTitleBarDesktop.d.ts +2 -0
- package/lib/features/story/components/ListStoryTitleBarDesktop.js +55 -0
- package/lib/features/story/components/ListStoryTitleBarMobile.d.ts +2 -0
- package/lib/features/story/components/ListStoryTitleBarMobile.js +41 -0
- package/lib/features/story/components/ListStoryVirtualScrollTrack.d.ts +6 -0
- package/lib/features/story/components/ListStoryVirtualScrollTrack.js +7 -0
- package/lib/{panelview/story-maps → features/story}/components/SpectaDesktopView.d.ts +4 -2
- package/lib/features/story/components/SpectaDesktopView.js +67 -0
- package/lib/features/story/components/SpectaMobileListModeContent.d.ts +13 -0
- package/lib/features/story/components/SpectaMobileListModeContent.js +36 -0
- package/lib/features/story/components/SpectaMobileSingleModeContent.d.ts +14 -0
- package/lib/{panelview/story-maps/components/SpectaMobileView.js → features/story/components/SpectaMobileSingleModeContent.js} +4 -6
- package/lib/{panelview/story-maps → features/story}/components/SpectaMobileView.d.ts +5 -3
- package/lib/features/story/components/SpectaMobileView.js +12 -0
- package/lib/features/story/components/SpectaSingleModeContent.d.ts +18 -0
- package/lib/features/story/components/SpectaSingleModeContent.js +8 -0
- package/lib/features/story/context/ListStoryScrollTrackContext.d.ts +19 -0
- package/lib/features/story/context/ListStoryScrollTrackContext.js +186 -0
- package/lib/features/story/hooks/useCurrentSegmentIndex.d.ts +3 -0
- package/lib/features/story/hooks/useCurrentSegmentIndex.js +17 -0
- package/lib/features/story/hooks/useListStoryScroll.d.ts +15 -0
- package/lib/features/story/hooks/useListStoryScroll.js +107 -0
- package/lib/features/story/hooks/useQueuedMarkdownHeightMeasure.d.ts +19 -0
- package/lib/features/story/hooks/useQueuedMarkdownHeightMeasure.js +56 -0
- package/lib/features/story/hooks/useStoryImagePreload.d.ts +1 -0
- package/lib/features/story/hooks/useStoryImagePreload.js +24 -0
- package/lib/{panelview/story-maps → features/story}/hooks/useStoryMap.d.ts +2 -7
- package/lib/{panelview/story-maps → features/story}/hooks/useStoryMap.js +21 -60
- package/lib/features/story/hooks/useStoryScrollState.d.ts +21 -0
- package/lib/features/story/hooks/useStoryScrollState.js +39 -0
- package/lib/features/story/hooks/useStorySegmentSync.d.ts +8 -0
- package/lib/features/story/hooks/useStorySegmentSync.js +51 -0
- package/lib/features/story/types/types.d.ts +43 -0
- package/lib/features/story/types/types.js +1 -0
- package/lib/features/story/utils/computeListStoryScrollState.d.ts +14 -0
- package/lib/features/story/utils/computeListStoryScrollState.js +79 -0
- package/lib/features/story/utils/listStoryMeasureQueue.d.ts +11 -0
- package/lib/features/story/utils/listStoryMeasureQueue.js +14 -0
- package/lib/features/story/utils/listStoryScrollTrack.d.ts +17 -0
- package/lib/features/story/utils/listStoryScrollTrack.js +72 -0
- package/lib/features/story/utils/spectaPresentation.d.ts +9 -0
- package/lib/features/story/utils/spectaPresentation.js +37 -0
- package/lib/features/story/utils/storySegmentViewItems.d.ts +5 -0
- package/lib/features/story/utils/storySegmentViewItems.js +30 -0
- package/lib/index.d.ts +11 -9
- package/lib/index.js +11 -9
- package/lib/keybindings.json +5 -0
- package/lib/mainview/OpenEOTileLayer.d.ts +49 -0
- package/lib/mainview/OpenEOTileLayer.js +179 -0
- package/lib/mainview/TemporalSlider.js +11 -9
- package/lib/mainview/components/MainViewMapSurface.d.ts +15 -0
- package/lib/mainview/components/MainViewMapSurface.js +13 -0
- package/lib/mainview/components/MainViewOverlayLayer.d.ts +9 -0
- package/lib/mainview/components/MainViewOverlayLayer.js +11 -0
- package/lib/mainview/components/MainViewSidePanels.d.ts +17 -0
- package/lib/mainview/components/MainViewSidePanels.js +10 -0
- package/lib/mainview/components/MainViewSpectaPanel.d.ts +17 -0
- package/lib/mainview/components/MainViewSpectaPanel.js +8 -0
- package/lib/mainview/components/MainViewStoryStage.d.ts +13 -0
- package/lib/mainview/components/MainViewStoryStage.js +17 -0
- package/lib/mainview/components/PositionedFloater.d.ts +10 -0
- package/lib/mainview/components/PositionedFloater.js +7 -0
- package/lib/mainview/geoJsonFeaturePatch.d.ts +9 -0
- package/lib/mainview/geoJsonFeaturePatch.js +43 -0
- package/lib/mainview/mainView.d.ts +93 -14
- package/lib/mainview/mainView.js +1216 -686
- package/lib/mainview/mainviewwidget.d.ts +5 -1
- package/lib/mainview/mainviewwidget.js +4 -2
- package/lib/shared/components/Button.d.ts +1 -1
- package/lib/shared/components/DropdownMenu.d.ts +1 -0
- package/lib/shared/components/DropdownMenu.js +3 -2
- package/lib/shared/components/NativeSelect.d.ts +8 -0
- package/lib/shared/components/NativeSelect.js +29 -0
- package/lib/shared/components/Tabs.d.ts +3 -3
- package/lib/shared/components/Tabs.js +5 -5
- package/lib/{formbuilder → shared/formbuilder}/creationform.js +71 -4
- package/lib/{formbuilder → shared/formbuilder}/editform.js +1 -1
- package/lib/{formbuilder → shared/formbuilder}/formselectors.d.ts +2 -2
- package/lib/{formbuilder → shared/formbuilder}/formselectors.js +10 -7
- package/lib/shared/formbuilder/index.d.ts +4 -0
- package/lib/shared/formbuilder/index.js +4 -0
- package/lib/{formbuilder → shared/formbuilder}/objectform/SchemaForm.d.ts +1 -1
- package/lib/{formbuilder → shared/formbuilder}/objectform/StoryEditorForm.d.ts +1 -1
- package/lib/{formbuilder → shared/formbuilder}/objectform/StoryEditorForm.js +1 -1
- package/lib/{formbuilder → shared/formbuilder}/objectform/components/LayerSelect.js +1 -1
- package/lib/{formbuilder → shared/formbuilder}/objectform/components/SegmentFormSymbology.js +4 -4
- package/lib/{formbuilder → shared/formbuilder}/objectform/components/SourcePropertiesField.js +1 -1
- package/lib/{formbuilder → shared/formbuilder}/objectform/components/StorySegmentReset.js +1 -1
- package/lib/{formbuilder → shared/formbuilder}/objectform/components/WmsTileSourceUrlInput.js +4 -4
- package/lib/{formbuilder → shared/formbuilder}/objectform/fileselectorwidget.js +8 -1
- package/lib/{formbuilder → shared/formbuilder}/objectform/schemaUtils.d.ts +3 -1
- package/lib/{formbuilder → shared/formbuilder}/objectform/schemaUtils.js +11 -0
- package/lib/{formbuilder → shared/formbuilder}/objectform/useSchemaFormState.d.ts +1 -1
- package/lib/{formbuilder → shared/formbuilder}/objectform/useSchemaFormState.js +1 -1
- package/lib/{icons.d.ts → shared/icons.d.ts} +2 -0
- package/lib/{icons.js → shared/icons.js} +28 -18
- package/lib/tools.d.ts +2 -0
- package/lib/tools.js +138 -4
- package/lib/types.d.ts +6 -1
- package/lib/types.js +6 -2
- package/lib/{menus.js → workspace/menus.js} +10 -2
- package/lib/workspace/panels/components/TabbedPanel.d.ts +17 -0
- package/lib/workspace/panels/components/TabbedPanel.js +19 -0
- package/lib/{panelview → workspace/panels}/components/layers.js +63 -18
- package/lib/workspace/panels/components/legendItem.js +680 -0
- package/lib/workspace/panels/hooks/useLayerTree.d.ts +19 -0
- package/lib/workspace/panels/hooks/useLayerTree.js +103 -0
- package/lib/workspace/panels/hooks/useRightPanelOptions.d.ts +27 -0
- package/lib/workspace/panels/hooks/useRightPanelOptions.js +72 -0
- package/lib/workspace/panels/hooks/useUIState.d.ts +2 -0
- package/lib/workspace/panels/hooks/useUIState.js +25 -0
- package/lib/{panelview → workspace/panels}/index.d.ts +1 -1
- package/lib/{panelview → workspace/panels}/index.js +1 -1
- package/lib/{panelview → workspace/panels}/leftpanel.d.ts +1 -1
- package/lib/workspace/panels/leftpanel.js +70 -0
- package/lib/{panelview/rightpanel.d.ts → workspace/panels/mergedpanel.d.ts} +6 -5
- package/lib/workspace/panels/mergedpanel.js +166 -0
- package/lib/workspace/panels/rightpanel.d.ts +25 -0
- package/lib/{panelview → workspace/panels}/rightpanel.js +53 -63
- package/lib/{statusbar → workspace/statusbar}/StatusBar.js +5 -4
- package/lib/{toolbar → workspace/toolbar}/widget.d.ts +2 -0
- package/lib/{toolbar → workspace/toolbar}/widget.js +33 -5
- package/lib/{widget.d.ts → workspace/widget.d.ts} +7 -5
- package/lib/{widget.js → workspace/widget.js} +16 -7
- package/package.json +16 -4
- package/style/base.css +124 -2
- package/style/icons/geopackage.svg +95 -0
- package/style/icons/pencil_solid.svg +7 -0
- package/style/identify.css +95 -0
- package/style/layerBrowser.css +29 -1
- package/style/leftPanel.css +25 -0
- package/style/shared/button.css +12 -0
- package/style/shared/dropdownMenu.css +9 -0
- package/style/shared/nativeSelect.css +75 -0
- package/style/shared/tabs.css +1 -1
- package/style/spectaProgressBar.css +0 -1
- package/style/storyPanel.css +330 -9
- package/style/storySpectaArticleOverlay.css +129 -0
- package/style/symbologyDialog.css +522 -27
- package/lib/dialogs/symbology/tiff_layer/TiffRendering.d.ts +0 -4
- package/lib/dialogs/symbology/tiff_layer/TiffRendering.js +0 -42
- package/lib/dialogs/symbology/tiff_layer/components/BandRow.d.ts +0 -23
- package/lib/dialogs/symbology/tiff_layer/components/BandRow.js +0 -59
- package/lib/dialogs/symbology/tiff_layer/types/MultibandColor.d.ts +0 -4
- package/lib/dialogs/symbology/tiff_layer/types/MultibandColor.js +0 -92
- package/lib/dialogs/symbology/tiff_layer/types/SingleBandPseudoColor.d.ts +0 -5
- package/lib/dialogs/symbology/tiff_layer/types/SingleBandPseudoColor.js +0 -313
- package/lib/dialogs/symbology/vector_layer/VectorRendering.d.ts +0 -4
- package/lib/dialogs/symbology/vector_layer/VectorRendering.js +0 -112
- package/lib/dialogs/symbology/vector_layer/components/ValueSelect.d.ts +0 -8
- package/lib/dialogs/symbology/vector_layer/components/ValueSelect.js +0 -9
- package/lib/dialogs/symbology/vector_layer/types/Canonical.d.ts +0 -4
- package/lib/dialogs/symbology/vector_layer/types/Canonical.js +0 -130
- package/lib/dialogs/symbology/vector_layer/types/Categorized.d.ts +0 -4
- package/lib/dialogs/symbology/vector_layer/types/Categorized.js +0 -243
- package/lib/dialogs/symbology/vector_layer/types/Graduated.d.ts +0 -4
- package/lib/dialogs/symbology/vector_layer/types/Graduated.js +0 -362
- package/lib/dialogs/symbology/vector_layer/types/Heatmap.d.ts +0 -4
- package/lib/dialogs/symbology/vector_layer/types/Heatmap.js +0 -89
- package/lib/dialogs/symbology/vector_layer/types/SimpleSymbol.d.ts +0 -4
- package/lib/dialogs/symbology/vector_layer/types/SimpleSymbol.js +0 -120
- package/lib/formbuilder/index.d.ts +0 -4
- package/lib/formbuilder/index.js +0 -4
- package/lib/formbuilder/objectform/layer/heatmapLayerForm.js +0 -96
- package/lib/formbuilder/objectform/layer/storySegmentLayerForm.js +0 -95
- package/lib/formbuilder/objectform/layer/webGlLayerForm.d.ts +0 -3
- package/lib/formbuilder/objectform/process/index.d.ts +0 -1
- package/lib/formbuilder/objectform/process/index.js +0 -1
- package/lib/panelview/components/legendItem.js +0 -210
- package/lib/panelview/identify-panel/IdentifyPanel.js +0 -102
- package/lib/panelview/leftpanel.js +0 -139
- package/lib/panelview/story-maps/StoryViewerPanel.js +0 -116
- package/lib/panelview/story-maps/components/SpectaDesktopView.js +0 -49
- package/lib/processing/index.js +0 -200
- package/lib/processing/processingCommands.js +0 -67
- /package/lib/{panelview/annotationPanel.d.ts → features/annotations/AnnotationsPanel.d.ts} +0 -0
- /package/lib/{annotations → features/annotations}/components/Annotation.d.ts +0 -0
- /package/lib/{annotations → features/annotations}/components/Annotation.js +0 -0
- /package/lib/{annotations → features/annotations}/components/AnnotationFloater.d.ts +0 -0
- /package/lib/{annotations → features/annotations}/components/AnnotationFloater.js +0 -0
- /package/lib/{annotations → features/annotations}/components/Message.d.ts +0 -0
- /package/lib/{annotations → features/annotations}/components/Message.js +0 -0
- /package/lib/{annotations → features/annotations}/model.d.ts +0 -0
- /package/lib/{annotations → features/annotations}/model.js +0 -0
- /package/lib/{console → features/console}/consoleview.d.ts +0 -0
- /package/lib/{console → features/console}/index.d.ts +0 -0
- /package/lib/{console → features/console}/index.js +0 -0
- /package/lib/{panelview/filter-panel → features/filter}/Filter.d.ts +0 -0
- /package/lib/{stacBrowser/types/types.js → features/identify/types/editorTypes.js} +0 -0
- /package/lib/{dialogs/layerBrowserDialog.d.ts → features/layer-browser/index.d.ts} +0 -0
- /package/lib/{formbuilder/objectform → features/layers/forms}/layer/hillshadeLayerForm.d.ts +0 -0
- /package/lib/{formbuilder/objectform → features/layers/forms}/layer/storySegmentLayerForm.d.ts +0 -0
- /package/lib/{formbuilder/objectform → features/layers/forms}/layer/vectorlayerform.d.ts +0 -0
- /package/lib/{formbuilder/objectform → features/layers/forms}/source/geojsonsource.d.ts +0 -0
- /package/lib/{formbuilder/objectform → features/layers/forms}/source/geotiffsource.d.ts +0 -0
- /package/lib/{formbuilder/objectform → features/layers/forms}/source/pathbasedsource.d.ts +0 -0
- /package/lib/{formbuilder/objectform → features/layers/forms}/source/tilesourceform.d.ts +0 -0
- /package/lib/{dialogs → features/layers}/symbology/components/color_ramp/ColorRampSelectorEntry.d.ts +0 -0
- /package/lib/{dialogs → features/layers}/symbology/components/color_ramp/ColorRampSelectorEntry.js +0 -0
- /package/lib/{dialogs → features/layers}/symbology/components/color_ramp/ModeSelectRow.js +0 -0
- /package/lib/{dialogs → features/layers}/symbology/components/color_ramp/RgbaColorPicker.d.ts +0 -0
- /package/lib/{dialogs → features/layers}/symbology/components/color_ramp/cmocean.json +0 -0
- /package/lib/{dialogs → features/layers}/symbology/components/color_stops/StopContainer.d.ts +0 -0
- /package/lib/{dialogs → features/layers}/symbology/hooks/useEffectiveSymbologyParams.d.ts +0 -0
- /package/lib/{dialogs → features/layers}/symbology/hooks/useEffectiveSymbologyParams.js +0 -0
- /package/lib/{dialogs → features/layers}/symbology/hooks/useGetProperties.d.ts +0 -0
- /package/lib/{dialogs → features/layers}/symbology/hooks/useGetSymbology.d.ts +0 -0
- /package/lib/{dialogs → features/layers}/symbology/hooks/useOkSignal.d.ts +0 -0
- /package/lib/{dialogs → features/layers}/symbology/hooks/useOkSignal.js +0 -0
- /package/lib/{formbuilder/objectform/process → features/processing/forms}/dissolveProcessForm.d.ts +0 -0
- /package/lib/{processing → features/processing}/processingFormToParam.d.ts +0 -0
- /package/lib/{processing → features/processing}/processingFormToParam.js +0 -0
- /package/lib/{stacBrowser → features/stac-browser}/components/StacPanel.d.ts +0 -0
- /package/lib/{stacBrowser → features/stac-browser}/components/filter-extension/QueryableComboBox.d.ts +0 -0
- /package/lib/{stacBrowser → features/stac-browser}/components/filter-extension/QueryableRow.d.ts +0 -0
- /package/lib/{stacBrowser → features/stac-browser}/components/filter-extension/StacFilterExtensionPanel.d.ts +0 -0
- /package/lib/{stacBrowser → features/stac-browser}/components/filter-extension/StacQueryableFilters.d.ts +0 -0
- /package/lib/{stacBrowser → features/stac-browser}/components/geodes/StacFilterSection.d.ts +0 -0
- /package/lib/{stacBrowser → features/stac-browser}/components/geodes/StacGeodesFilterPanel.d.ts +0 -0
- /package/lib/{stacBrowser → features/stac-browser}/components/geodes/StacGeodesFilterPanel.js +0 -0
- /package/lib/{stacBrowser → features/stac-browser}/components/shared/StacPanelResults.d.ts +0 -0
- /package/lib/{stacBrowser → features/stac-browser}/components/shared/StacSpatialExtent.d.ts +0 -0
- /package/lib/{stacBrowser → features/stac-browser}/components/shared/StacTemporalExtent.d.ts +0 -0
- /package/lib/{stacBrowser → features/stac-browser}/constants.d.ts +0 -0
- /package/lib/{stacBrowser → features/stac-browser}/constants.js +0 -0
- /package/lib/{stacBrowser → features/stac-browser}/context/StacResultsContext.d.ts +0 -0
- /package/lib/{stacBrowser → features/stac-browser}/hooks/useGeodesSearch.d.ts +0 -0
- /package/lib/{stacBrowser → features/stac-browser}/hooks/useStacFilterExtension.d.ts +0 -0
- /package/lib/{stacBrowser → features/stac-browser}/hooks/useStacSearch.d.ts +0 -0
- /package/lib/{stacBrowser → features/stac-browser}/index.d.ts +0 -0
- /package/lib/{stacBrowser → features/stac-browser}/index.js +0 -0
- /package/lib/{stacBrowser → features/stac-browser}/types/types.d.ts +0 -0
- /package/lib/{panelview/story-maps → features/story}/StoryEditorPanel.d.ts +0 -0
- /package/lib/{panelview/story-maps → features/story}/components/PreviewModeSwitch.d.ts +0 -0
- /package/lib/{panelview/story-maps → features/story}/components/PreviewModeSwitch.js +0 -0
- /package/lib/{panelview/story-maps → features/story}/components/StoryContentSection.d.ts +0 -0
- /package/lib/{panelview/story-maps → features/story}/components/StoryContentSection.js +0 -0
- /package/lib/{panelview/story-maps → features/story}/components/StoryImageSection.d.ts +0 -0
- /package/lib/{panelview/story-maps → features/story}/components/StoryImageSection.js +0 -0
- /package/lib/{panelview/story-maps → features/story}/components/StoryNavBar.d.ts +0 -0
- /package/lib/{panelview/story-maps → features/story}/components/StoryNavBar.js +0 -0
- /package/lib/{panelview/story-maps → features/story}/components/StorySubtitleSection.d.ts +0 -0
- /package/lib/{panelview/story-maps → features/story}/components/StorySubtitleSection.js +0 -0
- /package/lib/{panelview/story-maps → features/story}/components/StoryTitleSection.d.ts +0 -0
- /package/lib/{panelview/story-maps → features/story}/components/StoryTitleSection.js +0 -0
- /package/lib/{formbuilder → shared/formbuilder}/creationform.d.ts +0 -0
- /package/lib/{formbuilder → shared/formbuilder}/editform.d.ts +0 -0
- /package/lib/{formbuilder → shared/formbuilder}/objectform/SchemaForm.js +0 -0
- /package/lib/{formbuilder → shared/formbuilder}/objectform/components/LayerSelect.d.ts +0 -0
- /package/lib/{formbuilder → shared/formbuilder}/objectform/components/OpacitySlider.d.ts +0 -0
- /package/lib/{formbuilder → shared/formbuilder}/objectform/components/OpacitySlider.js +0 -0
- /package/lib/{formbuilder → shared/formbuilder}/objectform/components/SegmentFormSymbology.d.ts +0 -0
- /package/lib/{formbuilder → shared/formbuilder}/objectform/components/SourcePropertiesField.d.ts +0 -0
- /package/lib/{formbuilder → shared/formbuilder}/objectform/components/StorySegmentReset.d.ts +0 -0
- /package/lib/{formbuilder → shared/formbuilder}/objectform/components/WmsTileSourceUrlInput.d.ts +0 -0
- /package/lib/{formbuilder → shared/formbuilder}/objectform/fileselectorwidget.d.ts +0 -0
- /package/lib/{store.d.ts → shared/store.d.ts} +0 -0
- /package/lib/{store.js → shared/store.js} +0 -0
- /package/lib/{menus.d.ts → workspace/menus.d.ts} +0 -0
- /package/lib/{panelview → workspace/panels}/components/layers.d.ts +0 -0
- /package/lib/{panelview → workspace/panels}/components/legendItem.d.ts +0 -0
- /package/lib/{panelview → workspace/panels}/header.d.ts +0 -0
- /package/lib/{panelview → workspace/panels}/header.js +0 -0
- /package/lib/{statusbar → workspace/statusbar}/SpectaPresentationProgressBar.d.ts +0 -0
- /package/lib/{statusbar → workspace/statusbar}/SpectaPresentationProgressBar.js +0 -0
- /package/lib/{statusbar → workspace/statusbar}/StatusBar.d.ts +0 -0
- /package/lib/{toolbar → workspace/toolbar}/index.d.ts +0 -0
- /package/lib/{toolbar → workspace/toolbar}/index.js +0 -0
|
@@ -0,0 +1,1246 @@
|
|
|
1
|
+
import { processingList, } from '@jupytergis/schema';
|
|
2
|
+
import { Notification, showErrorMessage } from '@jupyterlab/apputils';
|
|
3
|
+
import { UUID } from '@lumino/coreutils';
|
|
4
|
+
import { ProcessingFormDialog } from './ProcessingFormDialog';
|
|
5
|
+
import { processingFormToParam } from './processingFormToParam';
|
|
6
|
+
import { isServerProcessingEnabled, runServerProcessing, runServerProcessingUrl, runServerProcessingUrlWithCutline, } from './serverProcessing';
|
|
7
|
+
import { getGdal } from '../../gdal';
|
|
8
|
+
import { getGeoJSONDataFromLayerSource } from '../../tools';
|
|
9
|
+
/**
|
|
10
|
+
* Get the currently selected layer from the shared model. Returns null if there is no selection or multiple layer is selected.
|
|
11
|
+
*/
|
|
12
|
+
export function getSingleSelectedLayer(tracker) {
|
|
13
|
+
var _a, _b, _c;
|
|
14
|
+
const model = (_a = tracker.currentWidget) === null || _a === void 0 ? void 0 : _a.model;
|
|
15
|
+
if (!model) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
const localState = model.sharedModel.awareness.getLocalState();
|
|
19
|
+
if (!localState || !((_b = localState['selected']) === null || _b === void 0 ? void 0 : _b.value)) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
const selectedLayers = Object.keys(localState['selected'].value);
|
|
23
|
+
// Ensure only one layer is selected
|
|
24
|
+
if (selectedLayers.length !== 1) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
const selectedLayerId = selectedLayers[0];
|
|
28
|
+
const layers = (_c = model.sharedModel.layers) !== null && _c !== void 0 ? _c : {};
|
|
29
|
+
const selectedLayer = layers[selectedLayerId];
|
|
30
|
+
return selectedLayer && selectedLayer.parameters ? selectedLayer : null;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Check if the selected layer is of one of the specified types
|
|
34
|
+
*/
|
|
35
|
+
export function selectedLayerIsOfType(allowedTypes, tracker) {
|
|
36
|
+
const selectedLayer = getSingleSelectedLayer(tracker);
|
|
37
|
+
return selectedLayer ? allowedTypes.includes(selectedLayer.type) : false;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Extract GeoJSON from selected layer's source
|
|
41
|
+
*/
|
|
42
|
+
export async function getLayerGeoJSON(layer, sources, model) {
|
|
43
|
+
if (!layer.parameters || !layer.parameters.source) {
|
|
44
|
+
console.error('Selected layer does not have a valid source.');
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
const source = sources[layer.parameters.source];
|
|
48
|
+
if (!source || !source.parameters) {
|
|
49
|
+
console.error(`Source with ID ${layer.parameters.source} not found or missing path.`);
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
return await getGeoJSONDataFromLayerSource(source, model);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Generalized processing function for Buffer & Dissolve
|
|
56
|
+
*/
|
|
57
|
+
export async function processLayer(tracker, formSchemaRegistry, processingType, processingOptions, app, filePath, processingInputs) {
|
|
58
|
+
var _a, _b, _c, _d;
|
|
59
|
+
// Resolve widget
|
|
60
|
+
const widget = filePath
|
|
61
|
+
? tracker.find(w => w.model.filePath === filePath)
|
|
62
|
+
: tracker.currentWidget;
|
|
63
|
+
if (!widget) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const model = widget.model;
|
|
67
|
+
const sources = (_a = model.sharedModel.sources) !== null && _a !== void 0 ? _a : {};
|
|
68
|
+
const layers = (_b = model.sharedModel.layers) !== null && _b !== void 0 ? _b : {};
|
|
69
|
+
// Resolve layer
|
|
70
|
+
let selected = null;
|
|
71
|
+
if (processingInputs === null || processingInputs === void 0 ? void 0 : processingInputs.inputLayer) {
|
|
72
|
+
selected = layers[processingInputs.inputLayer];
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
selected = getSingleSelectedLayer(tracker);
|
|
76
|
+
}
|
|
77
|
+
if (!selected) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const geojsonString = await getLayerGeoJSON(selected, sources, model);
|
|
81
|
+
if (!geojsonString) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
// Resolve params
|
|
85
|
+
let processParam;
|
|
86
|
+
let embedOutputLayer = true;
|
|
87
|
+
let outputLayerName = selected.name;
|
|
88
|
+
if (processingInputs) {
|
|
89
|
+
processParam = processingInputs;
|
|
90
|
+
outputLayerName = `${processingType} Layer`;
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
const schema = Object.assign({}, formSchemaRegistry.getSchemas().get(processingType));
|
|
94
|
+
const selectedLayerId = Object.keys(((_d = (_c = model.sharedModel.awareness.getLocalState()) === null || _c === void 0 ? void 0 : _c.selected) === null || _d === void 0 ? void 0 : _d.value) || {})[0];
|
|
95
|
+
// Open ProcessingFormDialog
|
|
96
|
+
const formValues = await new Promise(resolve => {
|
|
97
|
+
const dialog = new ProcessingFormDialog({
|
|
98
|
+
title: processingType.charAt(0).toUpperCase() + processingType.slice(1),
|
|
99
|
+
schema,
|
|
100
|
+
model,
|
|
101
|
+
sourceData: {
|
|
102
|
+
inputLayer: selectedLayerId,
|
|
103
|
+
outputLayerName: selected.name,
|
|
104
|
+
},
|
|
105
|
+
formContext: 'create',
|
|
106
|
+
processingType,
|
|
107
|
+
syncData: (props) => {
|
|
108
|
+
resolve(props);
|
|
109
|
+
dialog.dispose();
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
dialog.launch();
|
|
113
|
+
});
|
|
114
|
+
if (!formValues) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (!processingList.includes(processingType)) {
|
|
118
|
+
console.error(`Unsupported processing type: ${processingType}`);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
processParam = processingFormToParam(formValues, processingType);
|
|
122
|
+
embedOutputLayer = formValues.embedOutputLayer;
|
|
123
|
+
outputLayerName = formValues.outputLayerName;
|
|
124
|
+
}
|
|
125
|
+
// GDAL pre-processing
|
|
126
|
+
const fileBlob = new Blob([geojsonString], {
|
|
127
|
+
type: 'application/geo+json',
|
|
128
|
+
});
|
|
129
|
+
const geoFile = new File([fileBlob], 'data.geojson', {
|
|
130
|
+
type: 'application/geo+json',
|
|
131
|
+
});
|
|
132
|
+
const Gdal = await getGdal();
|
|
133
|
+
const result = await Gdal.open(geoFile);
|
|
134
|
+
const dataset = result.datasets[0];
|
|
135
|
+
const layerName = dataset.info.layers[0].name;
|
|
136
|
+
const sqlQuery = processingOptions.sqlQueryFn(layerName, processParam);
|
|
137
|
+
const fullOptions = processingOptions.options(sqlQuery);
|
|
138
|
+
await executeSQLProcessing(model, geojsonString, processingOptions.gdalFunction, fullOptions, outputLayerName, processingType, embedOutputLayer, tracker, app);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Rasterize a vector layer to a GeoTIFF on disk and add it as a WebGlLayer.
|
|
142
|
+
*/
|
|
143
|
+
export async function rasterizeLayer(tracker, formSchemaRegistry, processingType, gdalFunction, app, filePath, processingInputs) {
|
|
144
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
145
|
+
const widget = filePath
|
|
146
|
+
? tracker.find(w => w.model.filePath === filePath)
|
|
147
|
+
: tracker.currentWidget;
|
|
148
|
+
if (!widget) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const model = widget.model;
|
|
152
|
+
const sources = (_a = model.sharedModel.sources) !== null && _a !== void 0 ? _a : {};
|
|
153
|
+
const layers = (_b = model.sharedModel.layers) !== null && _b !== void 0 ? _b : {};
|
|
154
|
+
let selected = null;
|
|
155
|
+
if (processingInputs === null || processingInputs === void 0 ? void 0 : processingInputs.inputLayer) {
|
|
156
|
+
selected = layers[processingInputs.inputLayer];
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
selected = getSingleSelectedLayer(tracker);
|
|
160
|
+
}
|
|
161
|
+
if (!selected) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
const geojsonString = await getLayerGeoJSON(selected, sources, model);
|
|
165
|
+
if (!geojsonString) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
let processParam;
|
|
169
|
+
let outputFileName = 'rasterized.tif';
|
|
170
|
+
let embedOutputLayer = false;
|
|
171
|
+
if (processingInputs) {
|
|
172
|
+
processParam = processingInputs;
|
|
173
|
+
outputFileName = processingInputs.outputFileName || outputFileName;
|
|
174
|
+
embedOutputLayer = !!processingInputs.embedOutputLayer;
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
const schema = Object.assign({}, formSchemaRegistry.getSchemas().get(processingType));
|
|
178
|
+
const selectedLayerId = Object.keys(((_d = (_c = model.sharedModel.awareness.getLocalState()) === null || _c === void 0 ? void 0 : _c.selected) === null || _d === void 0 ? void 0 : _d.value) || {})[0];
|
|
179
|
+
const formValues = await new Promise(resolve => {
|
|
180
|
+
const dialog = new ProcessingFormDialog({
|
|
181
|
+
title: processingType.charAt(0).toUpperCase() + processingType.slice(1),
|
|
182
|
+
schema,
|
|
183
|
+
model,
|
|
184
|
+
sourceData: {
|
|
185
|
+
inputLayer: selectedLayerId,
|
|
186
|
+
outputFileName: `${selected.name.replace(/\s+/g, '_')}_rasterized.tif`,
|
|
187
|
+
},
|
|
188
|
+
formContext: 'create',
|
|
189
|
+
processingType,
|
|
190
|
+
syncData: (props) => {
|
|
191
|
+
resolve(props);
|
|
192
|
+
dialog.dispose();
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
dialog.launch();
|
|
196
|
+
});
|
|
197
|
+
if (!formValues) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
processParam = processingFormToParam(formValues, processingType);
|
|
201
|
+
outputFileName = formValues.outputFileName || outputFileName;
|
|
202
|
+
embedOutputLayer = !!formValues.embedOutputLayer;
|
|
203
|
+
}
|
|
204
|
+
const pixelSize = String((_e = processParam.pixelSize) !== null && _e !== void 0 ? _e : 0.01);
|
|
205
|
+
const noDataValue = String((_f = processParam.noDataValue) !== null && _f !== void 0 ? _f : 0);
|
|
206
|
+
const burnValue = String((_g = processParam.burnValue) !== null && _g !== void 0 ? _g : 1);
|
|
207
|
+
const attributeField = (processParam.attributeField || '').trim();
|
|
208
|
+
// Compute the min/max of the burned values so the GeoTIFF source can
|
|
209
|
+
// normalize correctly. gdal_rasterize doesn't write band statistics, so
|
|
210
|
+
// without this OL falls back to the data-type range (e.g. 0..255).
|
|
211
|
+
let bandMin;
|
|
212
|
+
let bandMax;
|
|
213
|
+
if (attributeField) {
|
|
214
|
+
const features = ((_j = (_h = JSON.parse(geojsonString)) === null || _h === void 0 ? void 0 : _h.features) !== null && _j !== void 0 ? _j : []);
|
|
215
|
+
const values = [];
|
|
216
|
+
for (const f of features) {
|
|
217
|
+
const v = (_k = f.properties) === null || _k === void 0 ? void 0 : _k[attributeField];
|
|
218
|
+
const n = typeof v === 'number' ? v : Number(v);
|
|
219
|
+
if (Number.isFinite(n)) {
|
|
220
|
+
values.push(n);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
bandMin = values.length ? Math.min(...values) : 0;
|
|
224
|
+
bandMax = values.length ? Math.max(...values) : 1;
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
// Burn mode: pixels are nodata or burnValue. min/max must be ordered and
|
|
228
|
+
// non-equal so normalization doesn't divide by zero.
|
|
229
|
+
const burn = Number(burnValue) || 1;
|
|
230
|
+
bandMin = Math.min(0, burn);
|
|
231
|
+
bandMax = Math.max(0, burn) || 1;
|
|
232
|
+
}
|
|
233
|
+
const options = [
|
|
234
|
+
'-of',
|
|
235
|
+
'GTiff',
|
|
236
|
+
'-at',
|
|
237
|
+
'-tr',
|
|
238
|
+
pixelSize,
|
|
239
|
+
pixelSize,
|
|
240
|
+
];
|
|
241
|
+
if (attributeField) {
|
|
242
|
+
options.push('-a', attributeField);
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
options.push('-burn', burnValue);
|
|
246
|
+
}
|
|
247
|
+
options.push('-a_nodata', noDataValue);
|
|
248
|
+
const outputName = 'output.tif';
|
|
249
|
+
const doRasterize = async () => {
|
|
250
|
+
if (isServerProcessingEnabled()) {
|
|
251
|
+
console.debug(`[JupyterGIS] Processing "${processingType}" via SERVER GDAL (${gdalFunction})`);
|
|
252
|
+
const t0 = performance.now();
|
|
253
|
+
const response = await runServerProcessing({
|
|
254
|
+
operation: gdalFunction,
|
|
255
|
+
options,
|
|
256
|
+
geojson: geojsonString,
|
|
257
|
+
outputName,
|
|
258
|
+
});
|
|
259
|
+
if (response.format !== 'base64') {
|
|
260
|
+
throw new Error('Expected base64 response for raster output');
|
|
261
|
+
}
|
|
262
|
+
const binary = atob(response.result);
|
|
263
|
+
const bytes = new Uint8Array(binary.length);
|
|
264
|
+
for (let i = 0; i < binary.length; i++) {
|
|
265
|
+
bytes[i] = binary.charCodeAt(i);
|
|
266
|
+
}
|
|
267
|
+
console.debug(`[JupyterGIS] SERVER GDAL "${processingType}" finished in ${(performance.now() - t0).toFixed(0)}ms`);
|
|
268
|
+
return bytes;
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
console.debug(`[JupyterGIS] Processing "${processingType}" via BROWSER WASM GDAL (${gdalFunction})`);
|
|
272
|
+
const t0 = performance.now();
|
|
273
|
+
const geoFile = new File([new Blob([geojsonString], { type: 'application/geo+json' })], 'data.geojson', { type: 'application/geo+json' });
|
|
274
|
+
const Gdal = await getGdal();
|
|
275
|
+
const result = await Gdal.open(geoFile);
|
|
276
|
+
const dataset = result.datasets[0];
|
|
277
|
+
const outputFilePath = await Gdal[gdalFunction](dataset, options, outputName);
|
|
278
|
+
const bytes = await Gdal.getFileBytes(outputFilePath);
|
|
279
|
+
Gdal.close(dataset);
|
|
280
|
+
console.debug(`[JupyterGIS] BROWSER WASM GDAL "${processingType}" finished in ${(performance.now() - t0).toFixed(0)}ms`);
|
|
281
|
+
return bytes;
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
const rasterizePromise = doRasterize();
|
|
285
|
+
Notification.promise(rasterizePromise.then(() => null), {
|
|
286
|
+
pending: { message: 'Rasterizing…', options: { autoClose: false } },
|
|
287
|
+
success: {
|
|
288
|
+
message: () => `${processingType} completed.`,
|
|
289
|
+
options: { autoClose: 3000 },
|
|
290
|
+
},
|
|
291
|
+
error: {
|
|
292
|
+
message: (err) => { var _a; return `${processingType} failed: ${(_a = err === null || err === void 0 ? void 0 : err.message) !== null && _a !== void 0 ? _a : err}`; },
|
|
293
|
+
},
|
|
294
|
+
});
|
|
295
|
+
let tiffBytes;
|
|
296
|
+
try {
|
|
297
|
+
tiffBytes = await rasterizePromise;
|
|
298
|
+
}
|
|
299
|
+
catch (_l) {
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
const base64Content = await new Promise((resolve, reject) => {
|
|
303
|
+
const reader = new FileReader();
|
|
304
|
+
reader.onload = () => {
|
|
305
|
+
const dataUrl = reader.result;
|
|
306
|
+
const comma = dataUrl.indexOf(',');
|
|
307
|
+
resolve(comma >= 0 ? dataUrl.slice(comma + 1) : '');
|
|
308
|
+
};
|
|
309
|
+
reader.onerror = () => reject(reader.error);
|
|
310
|
+
reader.readAsDataURL(new Blob([tiffBytes], { type: 'image/tiff' }));
|
|
311
|
+
});
|
|
312
|
+
let sourceUrl;
|
|
313
|
+
if (embedOutputLayer) {
|
|
314
|
+
// Embed the GeoTIFF as a data URL inside the .jGIS document.
|
|
315
|
+
sourceUrl = `data:image/tiff;base64,${base64Content}`;
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
// Save .tif to disk next to the .jGIS project file. If a file already
|
|
319
|
+
// exists at the chosen path, append `_1`, `_2`, ... so repeated runs don't
|
|
320
|
+
// overwrite previous outputs.
|
|
321
|
+
const jgisFilePath = widget.model.filePath;
|
|
322
|
+
const jgisDir = jgisFilePath
|
|
323
|
+
? jgisFilePath.substring(0, jgisFilePath.lastIndexOf('/'))
|
|
324
|
+
: '';
|
|
325
|
+
const dotIdx = outputFileName.lastIndexOf('.');
|
|
326
|
+
const baseName = dotIdx > 0 ? outputFileName.slice(0, dotIdx) : outputFileName;
|
|
327
|
+
const ext = dotIdx > 0 ? outputFileName.slice(dotIdx) : '';
|
|
328
|
+
const candidatePath = (name) => jgisDir ? `${jgisDir}/${name}` : name;
|
|
329
|
+
const pathExists = async (path) => {
|
|
330
|
+
try {
|
|
331
|
+
await app.serviceManager.contents.get(path, { content: false });
|
|
332
|
+
return true;
|
|
333
|
+
}
|
|
334
|
+
catch (_a) {
|
|
335
|
+
return false;
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
let suffix = 0;
|
|
339
|
+
while (await pathExists(candidatePath(suffix === 0 ? outputFileName : `${baseName}_${suffix}${ext}`))) {
|
|
340
|
+
suffix += 1;
|
|
341
|
+
}
|
|
342
|
+
if (suffix > 0) {
|
|
343
|
+
outputFileName = `${baseName}_${suffix}${ext}`;
|
|
344
|
+
}
|
|
345
|
+
const savePath = candidatePath(outputFileName);
|
|
346
|
+
await app.serviceManager.contents.save(savePath, {
|
|
347
|
+
type: 'file',
|
|
348
|
+
format: 'base64',
|
|
349
|
+
content: base64Content,
|
|
350
|
+
});
|
|
351
|
+
sourceUrl = outputFileName;
|
|
352
|
+
}
|
|
353
|
+
const newSourceId = UUID.uuid4();
|
|
354
|
+
const sourceModel = {
|
|
355
|
+
type: 'GeoTiffSource',
|
|
356
|
+
name: `${selected.name} Rasterized Source`,
|
|
357
|
+
parameters: {
|
|
358
|
+
urls: [
|
|
359
|
+
{ url: sourceUrl, min: bandMin, max: bandMax, nodata: noDataValue },
|
|
360
|
+
],
|
|
361
|
+
normalize: true,
|
|
362
|
+
wrapX: false,
|
|
363
|
+
interpolate: false,
|
|
364
|
+
},
|
|
365
|
+
};
|
|
366
|
+
const layerModel = {
|
|
367
|
+
type: 'GeoTiffLayer',
|
|
368
|
+
parameters: { source: newSourceId },
|
|
369
|
+
visible: true,
|
|
370
|
+
name: `${selected.name} Rasterized`,
|
|
371
|
+
};
|
|
372
|
+
model.sharedModel.addSource(newSourceId, sourceModel);
|
|
373
|
+
model.addLayer(UUID.uuid4(), layerModel);
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Load raw bytes for a GeoTiffSource — handles both embedded data URLs and
|
|
377
|
+
* file paths resolved relative to the .jGIS document.
|
|
378
|
+
*/
|
|
379
|
+
async function getRasterBytes(source, model, app) {
|
|
380
|
+
var _a, _b;
|
|
381
|
+
const params = source.parameters;
|
|
382
|
+
const url = (_b = (_a = params === null || params === void 0 ? void 0 : params.urls) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.url;
|
|
383
|
+
if (!url) {
|
|
384
|
+
return null;
|
|
385
|
+
}
|
|
386
|
+
// Embedded data URL
|
|
387
|
+
if (url.startsWith('data:')) {
|
|
388
|
+
const commaIdx = url.indexOf(',');
|
|
389
|
+
if (commaIdx < 0) {
|
|
390
|
+
return null;
|
|
391
|
+
}
|
|
392
|
+
const binary = atob(url.slice(commaIdx + 1));
|
|
393
|
+
const bytes = new Uint8Array(binary.length);
|
|
394
|
+
for (let i = 0; i < binary.length; i++) {
|
|
395
|
+
bytes[i] = binary.charCodeAt(i);
|
|
396
|
+
}
|
|
397
|
+
return bytes;
|
|
398
|
+
}
|
|
399
|
+
// Remote URL — fetch directly
|
|
400
|
+
if (url.startsWith('http://') || url.startsWith('https://')) {
|
|
401
|
+
const response = await fetch(url);
|
|
402
|
+
if (!response.ok) {
|
|
403
|
+
console.error(`Failed to fetch raster from ${url}: ${response.statusText}`);
|
|
404
|
+
return null;
|
|
405
|
+
}
|
|
406
|
+
const buffer = await response.arrayBuffer();
|
|
407
|
+
return new Uint8Array(buffer);
|
|
408
|
+
}
|
|
409
|
+
// Local file path — resolve relative to the .jGIS document
|
|
410
|
+
const jgisDir = model.filePath
|
|
411
|
+
? model.filePath.substring(0, model.filePath.lastIndexOf('/'))
|
|
412
|
+
: '';
|
|
413
|
+
const absolutePath = jgisDir ? `${jgisDir}/${url}` : url;
|
|
414
|
+
const file = await app.serviceManager.contents.get(absolutePath, {
|
|
415
|
+
content: true,
|
|
416
|
+
format: 'base64',
|
|
417
|
+
});
|
|
418
|
+
if (!(file === null || file === void 0 ? void 0 : file.content)) {
|
|
419
|
+
return null;
|
|
420
|
+
}
|
|
421
|
+
const binary = atob(file.content);
|
|
422
|
+
const bytes = new Uint8Array(binary.length);
|
|
423
|
+
for (let i = 0; i < binary.length; i++) {
|
|
424
|
+
bytes[i] = binary.charCodeAt(i);
|
|
425
|
+
}
|
|
426
|
+
return bytes;
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Clip a GeoTiff raster layer to a bounding-box extent using gdal_translate -projwin.
|
|
430
|
+
*/
|
|
431
|
+
export async function clipRasterByExtent(tracker, formSchemaRegistry, app, filePath, processingInputs) {
|
|
432
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
|
|
433
|
+
const widget = filePath
|
|
434
|
+
? tracker.find(w => w.model.filePath === filePath)
|
|
435
|
+
: tracker.currentWidget;
|
|
436
|
+
if (!widget) {
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
const model = widget.model;
|
|
440
|
+
const sources = (_a = model.sharedModel.sources) !== null && _a !== void 0 ? _a : {};
|
|
441
|
+
const layers = (_b = model.sharedModel.layers) !== null && _b !== void 0 ? _b : {};
|
|
442
|
+
let selected = null;
|
|
443
|
+
if (processingInputs === null || processingInputs === void 0 ? void 0 : processingInputs.inputLayer) {
|
|
444
|
+
selected = layers[processingInputs.inputLayer];
|
|
445
|
+
}
|
|
446
|
+
else {
|
|
447
|
+
selected = getSingleSelectedLayer(tracker);
|
|
448
|
+
}
|
|
449
|
+
if (!selected) {
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
if (!((_c = selected.parameters) === null || _c === void 0 ? void 0 : _c.source)) {
|
|
453
|
+
await showErrorMessage('Clip failed', 'Selected layer has no source.');
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
let source = sources[selected.parameters.source];
|
|
457
|
+
if (!source || source.type !== 'GeoTiffSource') {
|
|
458
|
+
await showErrorMessage('Clip failed', 'Selected layer is not a GeoTiff raster layer.');
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
let xMin;
|
|
462
|
+
let yMin;
|
|
463
|
+
let xMax;
|
|
464
|
+
let yMax;
|
|
465
|
+
let outputFileName = `${selected.name.replace(/\s+/g, '_')}_clipped.tif`;
|
|
466
|
+
let embedOutputLayer = false;
|
|
467
|
+
if (processingInputs) {
|
|
468
|
+
xMin = processingInputs.xMin;
|
|
469
|
+
yMin = processingInputs.yMin;
|
|
470
|
+
xMax = processingInputs.xMax;
|
|
471
|
+
yMax = processingInputs.yMax;
|
|
472
|
+
outputFileName = (_d = processingInputs.outputFileName) !== null && _d !== void 0 ? _d : outputFileName;
|
|
473
|
+
embedOutputLayer = !!processingInputs.embedOutputLayer;
|
|
474
|
+
}
|
|
475
|
+
else {
|
|
476
|
+
const schema = Object.assign({}, formSchemaRegistry.getSchemas().get('ClipRasterByExtent'));
|
|
477
|
+
const selectedLayerId = Object.keys(((_f = (_e = model.sharedModel.awareness.getLocalState()) === null || _e === void 0 ? void 0 : _e.selected) === null || _f === void 0 ? void 0 : _f.value) || {})[0];
|
|
478
|
+
const formValues = await new Promise(resolve => {
|
|
479
|
+
const dialog = new ProcessingFormDialog({
|
|
480
|
+
title: 'Clip Raster by Extent',
|
|
481
|
+
schema,
|
|
482
|
+
model,
|
|
483
|
+
sourceData: {
|
|
484
|
+
inputLayer: selectedLayerId,
|
|
485
|
+
outputFileName,
|
|
486
|
+
},
|
|
487
|
+
formContext: 'create',
|
|
488
|
+
processingType: 'ClipRasterByExtent',
|
|
489
|
+
syncData: (props) => {
|
|
490
|
+
resolve(props);
|
|
491
|
+
dialog.dispose();
|
|
492
|
+
},
|
|
493
|
+
});
|
|
494
|
+
dialog.launch();
|
|
495
|
+
});
|
|
496
|
+
if (!formValues) {
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
// Re-resolve selected/source in case the user changed inputLayer in the form
|
|
500
|
+
const resolvedLayerId = (_g = formValues.inputLayer) !== null && _g !== void 0 ? _g : selectedLayerId;
|
|
501
|
+
if (resolvedLayerId && resolvedLayerId !== selectedLayerId) {
|
|
502
|
+
const resolvedLayer = layers[resolvedLayerId];
|
|
503
|
+
if (!((_h = resolvedLayer === null || resolvedLayer === void 0 ? void 0 : resolvedLayer.parameters) === null || _h === void 0 ? void 0 : _h.source)) {
|
|
504
|
+
await showErrorMessage('Clip failed', 'Selected layer has no source.');
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
const resolvedSource = sources[resolvedLayer.parameters.source];
|
|
508
|
+
if (!resolvedSource || resolvedSource.type !== 'GeoTiffSource') {
|
|
509
|
+
await showErrorMessage('Clip failed', 'Selected layer is not a GeoTiff raster layer.');
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
selected = resolvedLayer;
|
|
513
|
+
source = resolvedSource;
|
|
514
|
+
}
|
|
515
|
+
xMin = formValues.xMin;
|
|
516
|
+
yMin = formValues.yMin;
|
|
517
|
+
xMax = formValues.xMax;
|
|
518
|
+
yMax = formValues.yMax;
|
|
519
|
+
outputFileName = (_j = formValues.outputFileName) !== null && _j !== void 0 ? _j : outputFileName;
|
|
520
|
+
embedOutputLayer = !!formValues.embedOutputLayer;
|
|
521
|
+
}
|
|
522
|
+
const sourceParams = source.parameters;
|
|
523
|
+
const firstUrl = sourceParams.urls[0];
|
|
524
|
+
const bandMin = (_k = firstUrl.min) !== null && _k !== void 0 ? _k : 0;
|
|
525
|
+
const bandMax = (_l = firstUrl.max) !== null && _l !== void 0 ? _l : 1;
|
|
526
|
+
const nodata = firstUrl.nodata !== undefined ? String(firstUrl.nodata) : undefined;
|
|
527
|
+
// gdal_translate -projwin: upper-left (xmin, ymax) → lower-right (xmax, ymin)
|
|
528
|
+
// -projwin_srs EPSG:4326 tells GDAL the coordinates are in WGS84 so it
|
|
529
|
+
// reprojects them to the raster's native CRS before computing the pixel window.
|
|
530
|
+
const options = [
|
|
531
|
+
'-of',
|
|
532
|
+
'GTiff',
|
|
533
|
+
'-projwin',
|
|
534
|
+
String(xMin),
|
|
535
|
+
String(yMax),
|
|
536
|
+
String(xMax),
|
|
537
|
+
String(yMin),
|
|
538
|
+
'-projwin_srs',
|
|
539
|
+
'EPSG:4326',
|
|
540
|
+
];
|
|
541
|
+
if (nodata !== undefined) {
|
|
542
|
+
options.push('-a_nodata', nodata);
|
|
543
|
+
}
|
|
544
|
+
const outputName = 'output.tif';
|
|
545
|
+
const rasterUrl = (_m = firstUrl === null || firstUrl === void 0 ? void 0 : firstUrl.url) !== null && _m !== void 0 ? _m : '';
|
|
546
|
+
const isRemoteUrl = rasterUrl.startsWith('http://') || rasterUrl.startsWith('https://');
|
|
547
|
+
const doClip = async () => {
|
|
548
|
+
if (isRemoteUrl && isServerProcessingEnabled()) {
|
|
549
|
+
console.debug('[JupyterGIS] Clipping raster by extent via SERVER GDAL (vsicurl)');
|
|
550
|
+
const t0 = performance.now();
|
|
551
|
+
const response = await runServerProcessingUrl({
|
|
552
|
+
operation: 'gdal_translate',
|
|
553
|
+
options,
|
|
554
|
+
url: rasterUrl,
|
|
555
|
+
outputName,
|
|
556
|
+
});
|
|
557
|
+
if (response.format !== 'base64') {
|
|
558
|
+
throw new Error('Expected base64 response for raster output');
|
|
559
|
+
}
|
|
560
|
+
const binary = atob(response.result);
|
|
561
|
+
const bytes = new Uint8Array(binary.length);
|
|
562
|
+
for (let i = 0; i < binary.length; i++) {
|
|
563
|
+
bytes[i] = binary.charCodeAt(i);
|
|
564
|
+
}
|
|
565
|
+
console.debug(`[JupyterGIS] SERVER GDAL raster clip finished in ${(performance.now() - t0).toFixed(0)}ms`);
|
|
566
|
+
return bytes;
|
|
567
|
+
}
|
|
568
|
+
else {
|
|
569
|
+
const tiffBytes = await getRasterBytes(source, model, app);
|
|
570
|
+
if (!tiffBytes) {
|
|
571
|
+
throw new Error('Could not load raster data from source.');
|
|
572
|
+
}
|
|
573
|
+
console.debug('[JupyterGIS] Clipping raster by extent via BROWSER WASM GDAL');
|
|
574
|
+
const t0 = performance.now();
|
|
575
|
+
const Gdal = await getGdal();
|
|
576
|
+
const tiffFile = new File([new Blob([tiffBytes], { type: 'image/tiff' })], 'input.tif', { type: 'image/tiff' });
|
|
577
|
+
const result = await Gdal.open(tiffFile);
|
|
578
|
+
if (result.datasets.length === 0) {
|
|
579
|
+
throw new Error('Failed to open raster in GDAL.');
|
|
580
|
+
}
|
|
581
|
+
const dataset = result.datasets[0];
|
|
582
|
+
const outputPath = await Gdal.gdal_translate(dataset, options, outputName);
|
|
583
|
+
const bytes = new Uint8Array(await Gdal.getFileBytes(outputPath));
|
|
584
|
+
Gdal.close(dataset);
|
|
585
|
+
console.debug(`[JupyterGIS] BROWSER WASM GDAL raster clip finished in ${(performance.now() - t0).toFixed(0)}ms`);
|
|
586
|
+
return bytes;
|
|
587
|
+
}
|
|
588
|
+
};
|
|
589
|
+
const clipPromise = doClip();
|
|
590
|
+
Notification.promise(clipPromise.then(() => null), {
|
|
591
|
+
pending: { message: 'Clipping raster…', options: { autoClose: false } },
|
|
592
|
+
success: {
|
|
593
|
+
message: () => 'Raster clipped successfully.',
|
|
594
|
+
options: { autoClose: 3000 },
|
|
595
|
+
},
|
|
596
|
+
error: {
|
|
597
|
+
message: (err) => { var _a; return `Raster clip failed: ${(_a = err === null || err === void 0 ? void 0 : err.message) !== null && _a !== void 0 ? _a : err}`; },
|
|
598
|
+
},
|
|
599
|
+
});
|
|
600
|
+
let outputTiffBytes;
|
|
601
|
+
try {
|
|
602
|
+
outputTiffBytes = await clipPromise;
|
|
603
|
+
}
|
|
604
|
+
catch (_r) {
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
const base64Content = await new Promise((resolve, reject) => {
|
|
608
|
+
const reader = new FileReader();
|
|
609
|
+
reader.onload = () => {
|
|
610
|
+
const dataUrl = reader.result;
|
|
611
|
+
const comma = dataUrl.indexOf(',');
|
|
612
|
+
resolve(comma >= 0 ? dataUrl.slice(comma + 1) : '');
|
|
613
|
+
};
|
|
614
|
+
reader.onerror = () => reject(reader.error);
|
|
615
|
+
reader.readAsDataURL(new Blob([outputTiffBytes], { type: 'image/tiff' }));
|
|
616
|
+
});
|
|
617
|
+
let sourceUrl;
|
|
618
|
+
if (embedOutputLayer) {
|
|
619
|
+
sourceUrl = `data:image/tiff;base64,${base64Content}`;
|
|
620
|
+
}
|
|
621
|
+
else {
|
|
622
|
+
const jgisFilePath = widget.model.filePath;
|
|
623
|
+
const jgisDir = jgisFilePath
|
|
624
|
+
? jgisFilePath.substring(0, jgisFilePath.lastIndexOf('/'))
|
|
625
|
+
: '';
|
|
626
|
+
const dotIdx = outputFileName.lastIndexOf('.');
|
|
627
|
+
const baseName = dotIdx > 0 ? outputFileName.slice(0, dotIdx) : outputFileName;
|
|
628
|
+
const ext = dotIdx > 0 ? outputFileName.slice(dotIdx) : '';
|
|
629
|
+
const candidatePath = (name) => jgisDir ? `${jgisDir}/${name}` : name;
|
|
630
|
+
const pathExists = async (path) => {
|
|
631
|
+
try {
|
|
632
|
+
await app.serviceManager.contents.get(path, { content: false });
|
|
633
|
+
return true;
|
|
634
|
+
}
|
|
635
|
+
catch (_a) {
|
|
636
|
+
return false;
|
|
637
|
+
}
|
|
638
|
+
};
|
|
639
|
+
let suffix = 0;
|
|
640
|
+
while (await pathExists(candidatePath(suffix === 0 ? outputFileName : `${baseName}_${suffix}${ext}`))) {
|
|
641
|
+
suffix += 1;
|
|
642
|
+
}
|
|
643
|
+
if (suffix > 0) {
|
|
644
|
+
outputFileName = `${baseName}_${suffix}${ext}`;
|
|
645
|
+
}
|
|
646
|
+
const savePath = candidatePath(outputFileName);
|
|
647
|
+
await app.serviceManager.contents.save(savePath, {
|
|
648
|
+
type: 'file',
|
|
649
|
+
format: 'base64',
|
|
650
|
+
content: base64Content,
|
|
651
|
+
});
|
|
652
|
+
sourceUrl = outputFileName;
|
|
653
|
+
}
|
|
654
|
+
const newSourceId = UUID.uuid4();
|
|
655
|
+
const newSource = {
|
|
656
|
+
type: 'GeoTiffSource',
|
|
657
|
+
name: `${selected.name} Clipped Source`,
|
|
658
|
+
parameters: {
|
|
659
|
+
urls: [{ url: sourceUrl, min: bandMin, max: bandMax, nodata }],
|
|
660
|
+
normalize: (_o = sourceParams.normalize) !== null && _o !== void 0 ? _o : true,
|
|
661
|
+
wrapX: (_p = sourceParams.wrapX) !== null && _p !== void 0 ? _p : false,
|
|
662
|
+
interpolate: (_q = sourceParams.interpolate) !== null && _q !== void 0 ? _q : false,
|
|
663
|
+
},
|
|
664
|
+
};
|
|
665
|
+
const newLayer = {
|
|
666
|
+
type: 'GeoTiffLayer',
|
|
667
|
+
parameters: { source: newSourceId },
|
|
668
|
+
visible: true,
|
|
669
|
+
name: `${selected.name} Clipped`,
|
|
670
|
+
};
|
|
671
|
+
model.sharedModel.addSource(newSourceId, newSource);
|
|
672
|
+
model.addLayer(UUID.uuid4(), newLayer);
|
|
673
|
+
}
|
|
674
|
+
/**
|
|
675
|
+
* Clip a GeoTiff raster layer using a vector layer as the cutline.
|
|
676
|
+
* Uses gdalwarp with -cutline (and optionally -crop_to_cutline).
|
|
677
|
+
*/
|
|
678
|
+
export async function clipRasterByVector(tracker, formSchemaRegistry, app, filePath, processingInputs) {
|
|
679
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t;
|
|
680
|
+
const widget = filePath
|
|
681
|
+
? tracker.find(w => w.model.filePath === filePath)
|
|
682
|
+
: tracker.currentWidget;
|
|
683
|
+
if (!widget) {
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
const model = widget.model;
|
|
687
|
+
const sources = (_a = model.sharedModel.sources) !== null && _a !== void 0 ? _a : {};
|
|
688
|
+
const layers = (_b = model.sharedModel.layers) !== null && _b !== void 0 ? _b : {};
|
|
689
|
+
let selected = null;
|
|
690
|
+
let inputLayerId;
|
|
691
|
+
if (processingInputs === null || processingInputs === void 0 ? void 0 : processingInputs.inputLayer) {
|
|
692
|
+
inputLayerId = processingInputs.inputLayer;
|
|
693
|
+
selected = (_c = layers[inputLayerId]) !== null && _c !== void 0 ? _c : null;
|
|
694
|
+
}
|
|
695
|
+
else {
|
|
696
|
+
selected = getSingleSelectedLayer(tracker);
|
|
697
|
+
inputLayerId = Object.keys(((_e = (_d = model.sharedModel.awareness.getLocalState()) === null || _d === void 0 ? void 0 : _d.selected) === null || _e === void 0 ? void 0 : _e.value) || {})[0];
|
|
698
|
+
}
|
|
699
|
+
if (!selected) {
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
if (!((_f = selected.parameters) === null || _f === void 0 ? void 0 : _f.source)) {
|
|
703
|
+
await showErrorMessage('Clip failed', 'Selected layer has no source.');
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
let source = sources[selected.parameters.source];
|
|
707
|
+
if (!source || source.type !== 'GeoTiffSource') {
|
|
708
|
+
await showErrorMessage('Clip failed', 'Selected layer is not a GeoTiff raster layer.');
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
let clipLayerId;
|
|
712
|
+
let cropToCutline = true;
|
|
713
|
+
let outputFileName = `${selected.name.replace(/\s+/g, '_')}_clipped.tif`;
|
|
714
|
+
let embedOutputLayer = false;
|
|
715
|
+
if (processingInputs) {
|
|
716
|
+
clipLayerId = processingInputs.clipLayer;
|
|
717
|
+
cropToCutline = (_g = processingInputs.cropToCutline) !== null && _g !== void 0 ? _g : true;
|
|
718
|
+
outputFileName = (_h = processingInputs.outputFileName) !== null && _h !== void 0 ? _h : outputFileName;
|
|
719
|
+
embedOutputLayer = !!processingInputs.embedOutputLayer;
|
|
720
|
+
}
|
|
721
|
+
else {
|
|
722
|
+
const schema = Object.assign({}, formSchemaRegistry.getSchemas().get('ClipRasterByVector'));
|
|
723
|
+
const formValues = await new Promise(resolve => {
|
|
724
|
+
const dialog = new ProcessingFormDialog({
|
|
725
|
+
title: 'Clip Raster by Vector',
|
|
726
|
+
schema,
|
|
727
|
+
model,
|
|
728
|
+
sourceData: {
|
|
729
|
+
inputLayer: inputLayerId,
|
|
730
|
+
outputFileName,
|
|
731
|
+
cropToCutline: true,
|
|
732
|
+
},
|
|
733
|
+
formContext: 'create',
|
|
734
|
+
processingType: 'ClipRasterByVector',
|
|
735
|
+
syncData: (props) => {
|
|
736
|
+
resolve(props);
|
|
737
|
+
dialog.dispose();
|
|
738
|
+
},
|
|
739
|
+
});
|
|
740
|
+
dialog.launch();
|
|
741
|
+
});
|
|
742
|
+
if (!formValues) {
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
// Re-resolve the selected raster if the user changed inputLayer in the form
|
|
746
|
+
const resolvedLayerId = (_j = formValues.inputLayer) !== null && _j !== void 0 ? _j : inputLayerId;
|
|
747
|
+
if (resolvedLayerId && resolvedLayerId !== inputLayerId) {
|
|
748
|
+
const resolvedLayer = layers[resolvedLayerId];
|
|
749
|
+
if (!((_k = resolvedLayer === null || resolvedLayer === void 0 ? void 0 : resolvedLayer.parameters) === null || _k === void 0 ? void 0 : _k.source)) {
|
|
750
|
+
await showErrorMessage('Clip failed', 'Selected layer has no source.');
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
const resolvedSource = sources[resolvedLayer.parameters.source];
|
|
754
|
+
if (!resolvedSource || resolvedSource.type !== 'GeoTiffSource') {
|
|
755
|
+
await showErrorMessage('Clip failed', 'Selected layer is not a GeoTiff raster layer.');
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
selected = resolvedLayer;
|
|
759
|
+
source = resolvedSource;
|
|
760
|
+
}
|
|
761
|
+
clipLayerId = formValues.clipLayer;
|
|
762
|
+
cropToCutline = (_l = formValues.cropToCutline) !== null && _l !== void 0 ? _l : true;
|
|
763
|
+
outputFileName = (_m = formValues.outputFileName) !== null && _m !== void 0 ? _m : outputFileName;
|
|
764
|
+
embedOutputLayer = !!formValues.embedOutputLayer;
|
|
765
|
+
}
|
|
766
|
+
const clipLayer = layers[clipLayerId];
|
|
767
|
+
if (!clipLayer) {
|
|
768
|
+
await showErrorMessage('Clip failed', 'Clip layer not found.');
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
const clipGeoJSON = await getLayerGeoJSON(clipLayer, sources, model);
|
|
772
|
+
if (!clipGeoJSON) {
|
|
773
|
+
await showErrorMessage('Clip failed', 'Could not read the clip layer geometry.');
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
const sourceParams = source.parameters;
|
|
777
|
+
const firstUrl = sourceParams.urls[0];
|
|
778
|
+
const bandMin = (_o = firstUrl.min) !== null && _o !== void 0 ? _o : 0;
|
|
779
|
+
const bandMax = (_p = firstUrl.max) !== null && _p !== void 0 ? _p : 1;
|
|
780
|
+
const nodata = firstUrl.nodata !== undefined ? String(firstUrl.nodata) : undefined;
|
|
781
|
+
const outputName = 'output.tif';
|
|
782
|
+
const rasterUrl = (_q = firstUrl === null || firstUrl === void 0 ? void 0 : firstUrl.url) !== null && _q !== void 0 ? _q : '';
|
|
783
|
+
const isRemoteUrl = rasterUrl.startsWith('http://') || rasterUrl.startsWith('https://');
|
|
784
|
+
// Cutline GeoJSON is assumed to be in EPSG:4326 (the canonical projection
|
|
785
|
+
// for JupyterGIS sources). gdalwarp will reproject it to the raster's CRS
|
|
786
|
+
// when -cutline_srs is provided.
|
|
787
|
+
//
|
|
788
|
+
// Output is written as a Cloud-Optimized GeoTIFF so the clipped layer
|
|
789
|
+
// renders fast in OpenLayers via pyramid overviews instead of forcing the
|
|
790
|
+
// browser to read every full-resolution strip. Multi-threaded warping
|
|
791
|
+
// (-wo NUM_THREADS=ALL_CPUS, -multi) saturates available CPUs and overlaps
|
|
792
|
+
// I/O with compute, which dominates runtime for /vsicurl/ COG sources.
|
|
793
|
+
const buildOptions = (cutlinePath) => {
|
|
794
|
+
const opts = [
|
|
795
|
+
// /vsicurl/ tuning. Defaults are tiny (16 KB chunks, 16 MB cache) which
|
|
796
|
+
// forces hundreds of tiny range requests per warp. The flags below:
|
|
797
|
+
// - prefer HTTP/2 + multiplex so range requests run concurrently
|
|
798
|
+
// - merge nearby ranges into single fetches
|
|
799
|
+
// - skip directory listing / HEAD probes that S3 doesn't need
|
|
800
|
+
// - bump per-read chunk to 1 MB and the vsicurl cache to 512 MB
|
|
801
|
+
// No effect for non-URL sources, all harmless.
|
|
802
|
+
'--config',
|
|
803
|
+
'GDAL_HTTP_VERSION',
|
|
804
|
+
'2',
|
|
805
|
+
'--config',
|
|
806
|
+
'GDAL_HTTP_MULTIPLEX',
|
|
807
|
+
'YES',
|
|
808
|
+
'--config',
|
|
809
|
+
'GDAL_HTTP_MERGE_CONSECUTIVE_RANGES',
|
|
810
|
+
'YES',
|
|
811
|
+
'--config',
|
|
812
|
+
'GDAL_DISABLE_READDIR_ON_OPEN',
|
|
813
|
+
'EMPTY_DIR',
|
|
814
|
+
'--config',
|
|
815
|
+
'CPL_VSIL_CURL_USE_HEAD',
|
|
816
|
+
'NO',
|
|
817
|
+
'--config',
|
|
818
|
+
'CPL_VSIL_CURL_CHUNK_SIZE',
|
|
819
|
+
'1048576',
|
|
820
|
+
'--config',
|
|
821
|
+
'CPL_VSIL_CURL_CACHE_SIZE',
|
|
822
|
+
'536870912',
|
|
823
|
+
// Block-level cache for decoded raster data.
|
|
824
|
+
'--config',
|
|
825
|
+
'GDAL_CACHEMAX',
|
|
826
|
+
'1024',
|
|
827
|
+
'-of',
|
|
828
|
+
'COG',
|
|
829
|
+
// LZW compresses faster than DEFLATE (and decompresses fast on the
|
|
830
|
+
// client side too). Slightly larger output files, but the user's
|
|
831
|
+
// bottleneck is wall-clock time, not disk.
|
|
832
|
+
'-co',
|
|
833
|
+
'COMPRESS=LZW',
|
|
834
|
+
'-co',
|
|
835
|
+
'BIGTIFF=IF_SAFER',
|
|
836
|
+
// Parallel warp + 1 GB warp memory so each pass covers a larger output
|
|
837
|
+
// area, reducing the number of /vsicurl/ round-trips.
|
|
838
|
+
'-multi',
|
|
839
|
+
'-wo',
|
|
840
|
+
'NUM_THREADS=ALL_CPUS',
|
|
841
|
+
'-wm',
|
|
842
|
+
'1024',
|
|
843
|
+
'-cutline',
|
|
844
|
+
cutlinePath,
|
|
845
|
+
'-cutline_srs',
|
|
846
|
+
'EPSG:4326',
|
|
847
|
+
];
|
|
848
|
+
if (cropToCutline) {
|
|
849
|
+
opts.push('-crop_to_cutline');
|
|
850
|
+
}
|
|
851
|
+
if (nodata !== undefined) {
|
|
852
|
+
opts.push('-dstnodata', nodata);
|
|
853
|
+
}
|
|
854
|
+
return opts;
|
|
855
|
+
};
|
|
856
|
+
const doClip = async () => {
|
|
857
|
+
if (isRemoteUrl && isServerProcessingEnabled()) {
|
|
858
|
+
// {cutlinePath} is substituted by the server with the temp path of the
|
|
859
|
+
// cutline file it writes from `cutlineGeojson`.
|
|
860
|
+
const response = await runServerProcessingUrlWithCutline({
|
|
861
|
+
operation: 'gdalwarp',
|
|
862
|
+
options: buildOptions('{cutlinePath}'),
|
|
863
|
+
url: rasterUrl,
|
|
864
|
+
cutlineGeojson: clipGeoJSON,
|
|
865
|
+
outputName,
|
|
866
|
+
});
|
|
867
|
+
if (response.format !== 'base64') {
|
|
868
|
+
throw new Error('Expected base64 response for raster output');
|
|
869
|
+
}
|
|
870
|
+
const binary = atob(response.result);
|
|
871
|
+
const bytes = new Uint8Array(binary.length);
|
|
872
|
+
for (let i = 0; i < binary.length; i++) {
|
|
873
|
+
bytes[i] = binary.charCodeAt(i);
|
|
874
|
+
}
|
|
875
|
+
return bytes;
|
|
876
|
+
}
|
|
877
|
+
else {
|
|
878
|
+
const tiffBytes = await getRasterBytes(source, model, app);
|
|
879
|
+
if (!tiffBytes) {
|
|
880
|
+
throw new Error('Could not load raster data from source.');
|
|
881
|
+
}
|
|
882
|
+
console.debug('[JupyterGIS] Clipping raster by vector via BROWSER WASM GDAL');
|
|
883
|
+
const t0 = performance.now();
|
|
884
|
+
const Gdal = await getGdal();
|
|
885
|
+
// Open both the raster and the cutline. gdal3.js writes each into the
|
|
886
|
+
// WASM virtual filesystem; we reference the cutline by its VFS path.
|
|
887
|
+
const tiffFile = new File([new Blob([tiffBytes], { type: 'image/tiff' })], 'input.tif', { type: 'image/tiff' });
|
|
888
|
+
const cutlineFile = new File([new Blob([clipGeoJSON], { type: 'application/geo+json' })], 'cutline.geojson', { type: 'application/geo+json' });
|
|
889
|
+
const rasterOpen = await Gdal.open(tiffFile);
|
|
890
|
+
if (rasterOpen.datasets.length === 0) {
|
|
891
|
+
throw new Error('Failed to open raster in GDAL.');
|
|
892
|
+
}
|
|
893
|
+
const rasterDataset = rasterOpen.datasets[0];
|
|
894
|
+
const cutlineOpen = await Gdal.open(cutlineFile);
|
|
895
|
+
if (cutlineOpen.datasets.length === 0) {
|
|
896
|
+
Gdal.close(rasterDataset);
|
|
897
|
+
throw new Error('Failed to open cutline in GDAL.');
|
|
898
|
+
}
|
|
899
|
+
const cutlineDataset = cutlineOpen.datasets[0];
|
|
900
|
+
try {
|
|
901
|
+
const options = buildOptions(cutlineDataset.path);
|
|
902
|
+
const outputPath = await Gdal.gdalwarp(rasterDataset, options, outputName);
|
|
903
|
+
const bytes = new Uint8Array(await Gdal.getFileBytes(outputPath));
|
|
904
|
+
console.debug(`[JupyterGIS] BROWSER WASM GDAL raster clip-by-vector finished in ${(performance.now() - t0).toFixed(0)}ms`);
|
|
905
|
+
return bytes;
|
|
906
|
+
}
|
|
907
|
+
finally {
|
|
908
|
+
Gdal.close(rasterDataset);
|
|
909
|
+
Gdal.close(cutlineDataset);
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
};
|
|
913
|
+
const clipPromise = doClip();
|
|
914
|
+
Notification.promise(clipPromise.then(() => null), {
|
|
915
|
+
pending: { message: 'Clipping raster…', options: { autoClose: false } },
|
|
916
|
+
success: {
|
|
917
|
+
message: () => 'Raster clipped successfully.',
|
|
918
|
+
options: { autoClose: 3000 },
|
|
919
|
+
},
|
|
920
|
+
error: {
|
|
921
|
+
message: (err) => { var _a; return `Raster clip failed: ${(_a = err === null || err === void 0 ? void 0 : err.message) !== null && _a !== void 0 ? _a : err}`; },
|
|
922
|
+
},
|
|
923
|
+
});
|
|
924
|
+
let outputTiffBytes;
|
|
925
|
+
try {
|
|
926
|
+
outputTiffBytes = await clipPromise;
|
|
927
|
+
}
|
|
928
|
+
catch (_u) {
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
931
|
+
const base64Content = await new Promise((resolve, reject) => {
|
|
932
|
+
const reader = new FileReader();
|
|
933
|
+
reader.onload = () => {
|
|
934
|
+
const dataUrl = reader.result;
|
|
935
|
+
const comma = dataUrl.indexOf(',');
|
|
936
|
+
resolve(comma >= 0 ? dataUrl.slice(comma + 1) : '');
|
|
937
|
+
};
|
|
938
|
+
reader.onerror = () => reject(reader.error);
|
|
939
|
+
reader.readAsDataURL(new Blob([outputTiffBytes], { type: 'image/tiff' }));
|
|
940
|
+
});
|
|
941
|
+
let sourceUrl;
|
|
942
|
+
if (embedOutputLayer) {
|
|
943
|
+
sourceUrl = `data:image/tiff;base64,${base64Content}`;
|
|
944
|
+
}
|
|
945
|
+
else {
|
|
946
|
+
const jgisFilePath = widget.model.filePath;
|
|
947
|
+
const jgisDir = jgisFilePath
|
|
948
|
+
? jgisFilePath.substring(0, jgisFilePath.lastIndexOf('/'))
|
|
949
|
+
: '';
|
|
950
|
+
const dotIdx = outputFileName.lastIndexOf('.');
|
|
951
|
+
const baseName = dotIdx > 0 ? outputFileName.slice(0, dotIdx) : outputFileName;
|
|
952
|
+
const ext = dotIdx > 0 ? outputFileName.slice(dotIdx) : '';
|
|
953
|
+
const candidatePath = (name) => jgisDir ? `${jgisDir}/${name}` : name;
|
|
954
|
+
const pathExists = async (path) => {
|
|
955
|
+
try {
|
|
956
|
+
await app.serviceManager.contents.get(path, { content: false });
|
|
957
|
+
return true;
|
|
958
|
+
}
|
|
959
|
+
catch (_a) {
|
|
960
|
+
return false;
|
|
961
|
+
}
|
|
962
|
+
};
|
|
963
|
+
let suffix = 0;
|
|
964
|
+
while (await pathExists(candidatePath(suffix === 0 ? outputFileName : `${baseName}_${suffix}${ext}`))) {
|
|
965
|
+
suffix += 1;
|
|
966
|
+
}
|
|
967
|
+
if (suffix > 0) {
|
|
968
|
+
outputFileName = `${baseName}_${suffix}${ext}`;
|
|
969
|
+
}
|
|
970
|
+
const savePath = candidatePath(outputFileName);
|
|
971
|
+
await app.serviceManager.contents.save(savePath, {
|
|
972
|
+
type: 'file',
|
|
973
|
+
format: 'base64',
|
|
974
|
+
content: base64Content,
|
|
975
|
+
});
|
|
976
|
+
sourceUrl = outputFileName;
|
|
977
|
+
}
|
|
978
|
+
const newSourceId = UUID.uuid4();
|
|
979
|
+
const newSource = {
|
|
980
|
+
type: 'GeoTiffSource',
|
|
981
|
+
name: `${selected.name} Clipped Source`,
|
|
982
|
+
parameters: {
|
|
983
|
+
urls: [{ url: sourceUrl, min: bandMin, max: bandMax, nodata }],
|
|
984
|
+
normalize: (_r = sourceParams.normalize) !== null && _r !== void 0 ? _r : true,
|
|
985
|
+
wrapX: (_s = sourceParams.wrapX) !== null && _s !== void 0 ? _s : false,
|
|
986
|
+
interpolate: (_t = sourceParams.interpolate) !== null && _t !== void 0 ? _t : false,
|
|
987
|
+
},
|
|
988
|
+
};
|
|
989
|
+
const newLayer = {
|
|
990
|
+
type: 'GeoTiffLayer',
|
|
991
|
+
parameters: { source: newSourceId },
|
|
992
|
+
visible: true,
|
|
993
|
+
name: `${selected.name} Clipped`,
|
|
994
|
+
};
|
|
995
|
+
model.sharedModel.addSource(newSourceId, newSource);
|
|
996
|
+
model.addLayer(UUID.uuid4(), newLayer);
|
|
997
|
+
}
|
|
998
|
+
/**
|
|
999
|
+
* Compute the WKT of the union of all features in a GeoJSON string using WASM GDAL.
|
|
1000
|
+
*/
|
|
1001
|
+
async function computeUnionWkt(geojsonString) {
|
|
1002
|
+
const Gdal = await getGdal();
|
|
1003
|
+
const file = new File([new Blob([geojsonString], { type: 'application/geo+json' })], 'clip_data.geojson', { type: 'application/geo+json' });
|
|
1004
|
+
const result = await Gdal.open(file);
|
|
1005
|
+
if (result.datasets.length === 0) {
|
|
1006
|
+
return null;
|
|
1007
|
+
}
|
|
1008
|
+
const dataset = result.datasets[0];
|
|
1009
|
+
const layerName = dataset.info.layers[0].name;
|
|
1010
|
+
const options = [
|
|
1011
|
+
'-f',
|
|
1012
|
+
'CSV',
|
|
1013
|
+
'-dialect',
|
|
1014
|
+
'SQLITE',
|
|
1015
|
+
'-sql',
|
|
1016
|
+
`SELECT ST_AsText(ST_Union(geometry)) AS wkt FROM "${layerName}"`,
|
|
1017
|
+
'clip_union.csv',
|
|
1018
|
+
];
|
|
1019
|
+
const outputPath = await Gdal.ogr2ogr(dataset, options);
|
|
1020
|
+
const bytes = await Gdal.getFileBytes(outputPath);
|
|
1021
|
+
Gdal.close(dataset);
|
|
1022
|
+
const csv = new TextDecoder().decode(bytes);
|
|
1023
|
+
const lines = csv.split('\n').filter((l) => l.trim());
|
|
1024
|
+
if (lines.length < 2) {
|
|
1025
|
+
return null;
|
|
1026
|
+
}
|
|
1027
|
+
// ogr2ogr CSV wraps fields containing commas in double-quotes; strip them
|
|
1028
|
+
let wkt = lines[1].trim();
|
|
1029
|
+
if (wkt.startsWith('"') && wkt.endsWith('"')) {
|
|
1030
|
+
wkt = wkt.slice(1, -1).replace(/""/g, '"');
|
|
1031
|
+
}
|
|
1032
|
+
return wkt || null;
|
|
1033
|
+
}
|
|
1034
|
+
/**
|
|
1035
|
+
* Clip a vector layer by another vector layer using ST_Intersection.
|
|
1036
|
+
*/
|
|
1037
|
+
export async function clipVectorByMaskLayer(tracker, formSchemaRegistry, app, filePath, processingInputs) {
|
|
1038
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
1039
|
+
const widget = filePath
|
|
1040
|
+
? tracker.find(w => w.model.filePath === filePath)
|
|
1041
|
+
: tracker.currentWidget;
|
|
1042
|
+
if (!widget) {
|
|
1043
|
+
return;
|
|
1044
|
+
}
|
|
1045
|
+
const model = widget.model;
|
|
1046
|
+
const sources = (_a = model.sharedModel.sources) !== null && _a !== void 0 ? _a : {};
|
|
1047
|
+
const layers = (_b = model.sharedModel.layers) !== null && _b !== void 0 ? _b : {};
|
|
1048
|
+
let selected = null;
|
|
1049
|
+
let inputLayerId;
|
|
1050
|
+
if (processingInputs === null || processingInputs === void 0 ? void 0 : processingInputs.inputLayer) {
|
|
1051
|
+
inputLayerId = processingInputs.inputLayer;
|
|
1052
|
+
selected = (_c = layers[inputLayerId]) !== null && _c !== void 0 ? _c : null;
|
|
1053
|
+
}
|
|
1054
|
+
else {
|
|
1055
|
+
selected = getSingleSelectedLayer(tracker);
|
|
1056
|
+
inputLayerId = Object.keys(((_e = (_d = model.sharedModel.awareness.getLocalState()) === null || _d === void 0 ? void 0 : _d.selected) === null || _e === void 0 ? void 0 : _e.value) || {})[0];
|
|
1057
|
+
}
|
|
1058
|
+
if (!selected) {
|
|
1059
|
+
return;
|
|
1060
|
+
}
|
|
1061
|
+
const inputGeoJSON = await getLayerGeoJSON(selected, sources, model);
|
|
1062
|
+
if (!inputGeoJSON) {
|
|
1063
|
+
return;
|
|
1064
|
+
}
|
|
1065
|
+
let clipLayerId;
|
|
1066
|
+
let embedOutputLayer = true;
|
|
1067
|
+
let outputLayerName = `${selected.name} Clipped`;
|
|
1068
|
+
if (processingInputs) {
|
|
1069
|
+
clipLayerId = processingInputs.clipLayer;
|
|
1070
|
+
embedOutputLayer = (_f = processingInputs.embedOutputLayer) !== null && _f !== void 0 ? _f : true;
|
|
1071
|
+
outputLayerName =
|
|
1072
|
+
(_g = processingInputs.outputLayerName) !== null && _g !== void 0 ? _g : `${selected.name} Clipped`;
|
|
1073
|
+
}
|
|
1074
|
+
else {
|
|
1075
|
+
const schema = Object.assign({}, formSchemaRegistry
|
|
1076
|
+
.getSchemas()
|
|
1077
|
+
.get('ClipVectorByMaskLayer'));
|
|
1078
|
+
const formValues = await new Promise(resolve => {
|
|
1079
|
+
const dialog = new ProcessingFormDialog({
|
|
1080
|
+
title: 'Clip',
|
|
1081
|
+
schema,
|
|
1082
|
+
model,
|
|
1083
|
+
sourceData: {
|
|
1084
|
+
inputLayer: inputLayerId,
|
|
1085
|
+
outputLayerName: `${selected.name} Clipped`,
|
|
1086
|
+
},
|
|
1087
|
+
formContext: 'create',
|
|
1088
|
+
processingType: 'ClipVectorByMaskLayer',
|
|
1089
|
+
syncData: (props) => {
|
|
1090
|
+
resolve(props);
|
|
1091
|
+
dialog.dispose();
|
|
1092
|
+
},
|
|
1093
|
+
});
|
|
1094
|
+
dialog.launch();
|
|
1095
|
+
});
|
|
1096
|
+
if (!formValues) {
|
|
1097
|
+
return;
|
|
1098
|
+
}
|
|
1099
|
+
clipLayerId = formValues.clipLayer;
|
|
1100
|
+
embedOutputLayer = formValues.embedOutputLayer;
|
|
1101
|
+
outputLayerName = formValues.outputLayerName;
|
|
1102
|
+
}
|
|
1103
|
+
if (clipLayerId === inputLayerId) {
|
|
1104
|
+
await showErrorMessage('Clip failed', 'The clip layer and input layer must be different.');
|
|
1105
|
+
return;
|
|
1106
|
+
}
|
|
1107
|
+
const clipLayer = layers[clipLayerId];
|
|
1108
|
+
if (!clipLayer) {
|
|
1109
|
+
await showErrorMessage('Clip failed', 'Clip layer not found.');
|
|
1110
|
+
return;
|
|
1111
|
+
}
|
|
1112
|
+
const clipGeoJSON = await getLayerGeoJSON(clipLayer, sources, model);
|
|
1113
|
+
if (!clipGeoJSON) {
|
|
1114
|
+
await showErrorMessage('Clip failed', 'Could not read the clip layer geometry.');
|
|
1115
|
+
return;
|
|
1116
|
+
}
|
|
1117
|
+
const clipWkt = await computeUnionWkt(clipGeoJSON);
|
|
1118
|
+
if (!clipWkt) {
|
|
1119
|
+
await showErrorMessage('Clip failed', 'Could not compute clip boundary geometry. The clip layer may be empty.');
|
|
1120
|
+
return;
|
|
1121
|
+
}
|
|
1122
|
+
const Gdal = await getGdal();
|
|
1123
|
+
const inputFile = new File([new Blob([inputGeoJSON], { type: 'application/geo+json' })], 'data.geojson', { type: 'application/geo+json' });
|
|
1124
|
+
const openResult = await Gdal.open(inputFile);
|
|
1125
|
+
const dataset = openResult.datasets[0];
|
|
1126
|
+
const inputLayerName = dataset.info.layers[0].name;
|
|
1127
|
+
Gdal.close(dataset);
|
|
1128
|
+
const escapedWkt = clipWkt.replace(/'/g, "''");
|
|
1129
|
+
const sql = `SELECT ST_Intersection(geometry, ST_GeomFromText('${escapedWkt}')) AS geometry, * FROM "${inputLayerName}" WHERE ST_Intersects(geometry, ST_GeomFromText('${escapedWkt}'))`;
|
|
1130
|
+
const options = [
|
|
1131
|
+
'-f',
|
|
1132
|
+
'GeoJSON',
|
|
1133
|
+
'-dialect',
|
|
1134
|
+
'SQLITE',
|
|
1135
|
+
'-sql',
|
|
1136
|
+
sql,
|
|
1137
|
+
'{outputName}',
|
|
1138
|
+
];
|
|
1139
|
+
await executeSQLProcessing(model, inputGeoJSON, 'ogr2ogr', options, outputLayerName, 'ClipVectorByMaskLayer', embedOutputLayer, tracker, app, outputLayerName);
|
|
1140
|
+
}
|
|
1141
|
+
export async function executeSQLProcessing(model, geojsonString, gdalFunction, options, layerNamePrefix, processingType, embedOutputLayer, tracker, app, exactLayerName) {
|
|
1142
|
+
var _a;
|
|
1143
|
+
const doProcessing = async () => {
|
|
1144
|
+
if (isServerProcessingEnabled()) {
|
|
1145
|
+
console.debug(`[JupyterGIS] Processing "${processingType}" via SERVER GDAL (${gdalFunction})`);
|
|
1146
|
+
const t0 = performance.now();
|
|
1147
|
+
const outputName = 'output.geojson';
|
|
1148
|
+
const response = await runServerProcessing({
|
|
1149
|
+
operation: gdalFunction,
|
|
1150
|
+
options,
|
|
1151
|
+
geojson: geojsonString,
|
|
1152
|
+
outputName,
|
|
1153
|
+
});
|
|
1154
|
+
console.debug(`[JupyterGIS] SERVER GDAL "${processingType}" finished in ${(performance.now() - t0).toFixed(0)}ms`);
|
|
1155
|
+
return response.result;
|
|
1156
|
+
}
|
|
1157
|
+
else {
|
|
1158
|
+
console.debug(`[JupyterGIS] Processing "${processingType}" via BROWSER WASM GDAL (${gdalFunction})`);
|
|
1159
|
+
const t0 = performance.now();
|
|
1160
|
+
const geoFile = new File([new Blob([geojsonString], { type: 'application/geo+json' })], 'data.geojson', { type: 'application/geo+json' });
|
|
1161
|
+
const Gdal = await getGdal();
|
|
1162
|
+
const result = await Gdal.open(geoFile);
|
|
1163
|
+
if (result.datasets.length === 0) {
|
|
1164
|
+
throw new Error('Could not open layer in GDAL.');
|
|
1165
|
+
}
|
|
1166
|
+
const dataset = result.datasets[0];
|
|
1167
|
+
const wasmOptions = options.map(o => o.replace('{outputName}', 'output.geojson'));
|
|
1168
|
+
const outputFilePath = await Gdal[gdalFunction](dataset, wasmOptions);
|
|
1169
|
+
const processedBytes = await Gdal.getFileBytes(outputFilePath);
|
|
1170
|
+
const output = new TextDecoder().decode(processedBytes);
|
|
1171
|
+
Gdal.close(dataset);
|
|
1172
|
+
console.debug(`[JupyterGIS] BROWSER WASM GDAL "${processingType}" finished in ${(performance.now() - t0).toFixed(0)}ms`);
|
|
1173
|
+
return output;
|
|
1174
|
+
}
|
|
1175
|
+
};
|
|
1176
|
+
const processingPromise = doProcessing();
|
|
1177
|
+
Notification.promise(processingPromise.then(() => null), {
|
|
1178
|
+
pending: {
|
|
1179
|
+
message: `Running ${processingType}…`,
|
|
1180
|
+
options: { autoClose: false },
|
|
1181
|
+
},
|
|
1182
|
+
success: {
|
|
1183
|
+
message: () => `${processingType} completed.`,
|
|
1184
|
+
options: { autoClose: 3000 },
|
|
1185
|
+
},
|
|
1186
|
+
error: {
|
|
1187
|
+
message: (err) => { var _a; return `${processingType} failed: ${(_a = err === null || err === void 0 ? void 0 : err.message) !== null && _a !== void 0 ? _a : err}`; },
|
|
1188
|
+
},
|
|
1189
|
+
});
|
|
1190
|
+
let processedGeoJSONString;
|
|
1191
|
+
try {
|
|
1192
|
+
processedGeoJSONString = await processingPromise;
|
|
1193
|
+
}
|
|
1194
|
+
catch (_b) {
|
|
1195
|
+
return;
|
|
1196
|
+
}
|
|
1197
|
+
const layerName = exactLayerName !== null && exactLayerName !== void 0 ? exactLayerName : `${layerNamePrefix} ${processingType.charAt(0).toUpperCase() + processingType.slice(1)}`;
|
|
1198
|
+
if (!embedOutputLayer) {
|
|
1199
|
+
// Save the output as a file
|
|
1200
|
+
const jgisFilePath = (_a = tracker.currentWidget) === null || _a === void 0 ? void 0 : _a.model.filePath;
|
|
1201
|
+
const jgisDir = jgisFilePath
|
|
1202
|
+
? jgisFilePath.substring(0, jgisFilePath.lastIndexOf('/'))
|
|
1203
|
+
: '';
|
|
1204
|
+
const outputFileName = `${layerNamePrefix}_${processingType}.json`;
|
|
1205
|
+
const savePath = jgisDir ? `${jgisDir}/${outputFileName}` : outputFileName;
|
|
1206
|
+
await app.serviceManager.contents.save(savePath, {
|
|
1207
|
+
type: 'file',
|
|
1208
|
+
format: 'text',
|
|
1209
|
+
content: processedGeoJSONString,
|
|
1210
|
+
});
|
|
1211
|
+
const newSourceId = UUID.uuid4();
|
|
1212
|
+
const sourceModel = {
|
|
1213
|
+
type: 'GeoJSONSource',
|
|
1214
|
+
name: outputFileName,
|
|
1215
|
+
parameters: {
|
|
1216
|
+
path: outputFileName,
|
|
1217
|
+
},
|
|
1218
|
+
};
|
|
1219
|
+
const layerModel = {
|
|
1220
|
+
type: 'VectorLayer',
|
|
1221
|
+
parameters: { source: newSourceId },
|
|
1222
|
+
visible: true,
|
|
1223
|
+
name: layerName,
|
|
1224
|
+
};
|
|
1225
|
+
model.sharedModel.addSource(newSourceId, sourceModel);
|
|
1226
|
+
model.addLayer(UUID.uuid4(), layerModel);
|
|
1227
|
+
}
|
|
1228
|
+
else {
|
|
1229
|
+
// Embed the output directly into the model
|
|
1230
|
+
const processedGeoJSON = JSON.parse(processedGeoJSONString);
|
|
1231
|
+
const newSourceId = UUID.uuid4();
|
|
1232
|
+
const sourceModel = {
|
|
1233
|
+
type: 'GeoJSONSource',
|
|
1234
|
+
name: `${layerName} Source`,
|
|
1235
|
+
parameters: { data: processedGeoJSON },
|
|
1236
|
+
};
|
|
1237
|
+
const layerModel = {
|
|
1238
|
+
type: 'VectorLayer',
|
|
1239
|
+
parameters: { source: newSourceId },
|
|
1240
|
+
visible: true,
|
|
1241
|
+
name: layerName,
|
|
1242
|
+
};
|
|
1243
|
+
model.sharedModel.addSource(newSourceId, sourceModel);
|
|
1244
|
+
model.addLayer(UUID.uuid4(), layerModel);
|
|
1245
|
+
}
|
|
1246
|
+
}
|