From 02118e0c84cb04d1e4bd10ffe955d6cc168cc8db Mon Sep 17 00:00:00 2001 From: jakimowb <benjamin.jakimow@geo.hu-berlin.de> Date: Fri, 9 Oct 2020 01:19:02 +0200 Subject: [PATCH] Enables the EOTSV to open multiple Spectral Libraries --- eotimeseriesviewer/docks.py | 59 ++++ .../externals/qps/speclib/core.py | 2 - .../externals/qps/speclib/gui.py | 4 +- eotimeseriesviewer/externals/qps/utils.py | 21 +- eotimeseriesviewer/labeling.py | 89 +++--- eotimeseriesviewer/main.py | 260 ++++++++++-------- eotimeseriesviewer/mapvisualization.py | 23 +- eotimeseriesviewer/ui/timeseriesviewer.ui | 63 +++-- 8 files changed, 312 insertions(+), 209 deletions(-) create mode 100644 eotimeseriesviewer/docks.py diff --git a/eotimeseriesviewer/docks.py b/eotimeseriesviewer/docks.py new file mode 100644 index 00000000..2eb6a1bd --- /dev/null +++ b/eotimeseriesviewer/docks.py @@ -0,0 +1,59 @@ +from qgis.PyQt.QtGui import QIcon +from qgis.PyQt.QtWidgets import QAction, QToolBar +from qgis.core import QgsVectorLayer, QgsVectorLayerTools +from qgis.gui import QgsDockWidget +from eotimeseriesviewer.externals.qps.vectorlayertools import VectorLayerTools +from eotimeseriesviewer.externals.qps.speclib.core import SpectralLibrary +from eotimeseriesviewer.externals.qps.speclib.gui import SpectralLibraryWidget, SpectralLibraryPanel +from eotimeseriesviewer.labeling import LabelWidget, gotoNextFeature, gotoPreviousFeature + + +class SpectralLibraryDockWidget(SpectralLibraryPanel): + def __init__(self, speclib: SpectralLibrary, *args, **kwds): + super().__init__(*args, **kwds) + assert isinstance(self.SLW, SpectralLibraryWidget) + self.mActionNextFeature = QAction('Next Feature', parent=self) + self.mActionNextFeature.setIcon(QIcon(':/images/themes/default/mActionAtlasNext.svg')) + self.mActionNextFeature.triggered.connect( + lambda *args, + lyr=self.speclib(), + vlt=self.SLW.vectorLayerTools(): + gotoNextFeature(lyr, vlt) + ) + + self.mActionPreviousFeature = QAction('Previous Feature', parent=self) + self.mActionPreviousFeature.setIcon(QIcon(':/images/themes/default/mActionAtlasPrev.svg')) + self.mActionPreviousFeature.triggered.connect( + lambda *args, + lyr=self.speclib(), + vlt=self.SLW.vectorLayerTools(): + gotoPreviousFeature(lyr, vlt)) + + self.SLW.mToolbar: QToolBar + self.SLW.mToolbar.insertActions(self.SLW.mActionToggleEditing, + [self.mActionPreviousFeature, self.mActionNextFeature]) + self.SLW.mToolbar.insertSeparator(self.SLW.mActionToggleEditing) + + def setVectorLayerTools(self, tools: QgsVectorLayerTools): + self.SLW.setVectorLayerTools(tools) + + def spectralLibrary(self) -> SpectralLibrary: + return self.SLW.spectralLibrary() + + +class LabelDockWidget(QgsDockWidget): + + def __init__(self, layer, *args, **kwds): + super().__init__(*args, **kwds) + self.mLabelWidget = LabelWidget(layer) + self.setWidget(self.mLabelWidget) + self.setWindowTitle(self.mLabelWidget.windowTitle()) + self.mLabelWidget.windowTitleChanged.connect(self.setWindowTitle) + + def setVectorLayerTools(self, tools: QgsVectorLayerTools): + self.mLabelWidget.setVectorLayerTools(tools) + + def vectorLayer(self) -> QgsVectorLayer: + if isinstance(self.mLabelWidget.mLayer, QgsVectorLayer): + return self.mLabelWidget.mLayer + return None diff --git a/eotimeseriesviewer/externals/qps/speclib/core.py b/eotimeseriesviewer/externals/qps/speclib/core.py index 5c7987d5..21032653 100644 --- a/eotimeseriesviewer/externals/qps/speclib/core.py +++ b/eotimeseriesviewer/externals/qps/speclib/core.py @@ -2143,8 +2143,6 @@ class SpectralLibrary(QgsVectorLayer): s = "" return None - sigNameChanged = pyqtSignal(str) - @classmethod def instances(cls) -> list: warnings.warn('SpectraLibrary.instances() Will be removed', DeprecationWarning) diff --git a/eotimeseriesviewer/externals/qps/speclib/gui.py b/eotimeseriesviewer/externals/qps/speclib/gui.py index dcfd6022..47187307 100644 --- a/eotimeseriesviewer/externals/qps/speclib/gui.py +++ b/eotimeseriesviewer/externals/qps/speclib/gui.py @@ -2681,8 +2681,10 @@ class SpectralLibraryPanel(QgsDockWidget): def __init__(self, *args, speclib: SpectralLibrary = None, **kwds): super(SpectralLibraryPanel, self).__init__(*args, **kwds) self.setObjectName('spectralLibraryPanel') - self.setWindowTitle('Spectral Library') + self.SLW = SpectralLibraryWidget(speclib=speclib) + self.setWindowTitle(self.speclib().name()) + self.speclib().nameChanged.connect(lambda *args: self.setWindowTitle(self.speclib().name())) self.setWidget(self.SLW) def spectralLibraryWidget(self) -> SpectralLibraryWidget: diff --git a/eotimeseriesviewer/externals/qps/utils.py b/eotimeseriesviewer/externals/qps/utils.py index fd8ab781..57eda376 100644 --- a/eotimeseriesviewer/externals/qps/utils.py +++ b/eotimeseriesviewer/externals/qps/utils.py @@ -130,6 +130,7 @@ def cleanDir(d): for p in dirs + files: rm(jp(root, p)) break + # a QPS internal map layer store QPS_MAPLAYER_STORE = QgsMapLayerStore() @@ -455,7 +456,6 @@ LUT_WAVELENGTH = dict({'B': 480, 'SWIR2': 2150 }) - NEXT_COLOR_HUE_DELTA_CON = 10 NEXT_COLOR_HUE_DELTA_CAT = 100 @@ -681,9 +681,11 @@ def gdalDataset(dataset: typing.Union[str, pathlib.Path, QgsRasterLayer, QgsRasterDataProvider, - gdal.Dataset], eAccess=gdal.GA_ReadOnly) -> gdal.Dataset: + gdal.Dataset], + eAccess:int = gdal.GA_ReadOnly) -> gdal.Dataset: """ Returns a gdal.Dataset object instance + :param dataset: :param pathOrDataset: path | gdal.Dataset | QgsRasterLayer | QgsRasterDataProvider :return: gdal.Dataset """ @@ -921,6 +923,7 @@ def qgsMapLayer(value: typing.Any) -> QgsMapLayer: return None + def loadUi(uifile, baseinstance=None, package='', resource_suffix='_rc', remove_resource_references=True, loadUiType=False): """ @@ -1393,9 +1396,9 @@ def defaultBands(dataset) -> list: elif isinstance(dataset, QgsRasterDataProvider): return defaultBands(dataset.dataSourceUri()) elif isinstance(dataset, QgsRasterLayer) and \ - isinstance(dataset.dataProvider(), QgsRasterDataProvider) and \ - dataset.dataProvider().name() == 'gdal': - return defaultBands(dataset.source()) + isinstance(dataset.dataProvider(), QgsRasterDataProvider) and \ + dataset.dataProvider().name() == 'gdal': + return defaultBands(dataset.source()) elif isinstance(dataset, gdal.Dataset): # check ENVI style metadata default band definition @@ -1518,7 +1521,7 @@ def parseFWHM(dataset) -> typing.Tuple[np.ndarray]: # search band by band values = [] for b in range(dataset.RasterCount): - band: gdal.Band = dataset.GetRasterBand(b+1) + band: gdal.Band = dataset.GetRasterBand(b + 1) for key, domain in key_positions: value = dataset.GetMetadataItem(key, domain) if value not in ['', None]: @@ -1528,6 +1531,7 @@ def parseFWHM(dataset) -> typing.Tuple[np.ndarray]: return np.asarray(values) return None + def parseWavelength(dataset) -> typing.Tuple[np.ndarray, str]: """ Returns the wavelength + wavelength unit of a raster @@ -1895,6 +1899,7 @@ class SpatialPoint(QgsPointXY): """ Object to keep QgsPoint and QgsCoordinateReferenceSystem together """ + @staticmethod def readXml(node: QDomNode): wkt = node.firstChildElement('SpatialPointCrs').text() @@ -2087,6 +2092,7 @@ class SpatialExtent(QgsRectangle): """ Object that combines a QgsRectangle and QgsCoordinateReferenceSystem """ + @staticmethod def readXml(node: QDomNode): wkt = node.firstChildElement('SpatialExtentCrs').text() @@ -2095,7 +2101,7 @@ class SpatialExtent(QgsRectangle): return SpatialExtent(crs, rectangle) @staticmethod - def fromMapCanvas(mapCanvas, fullExtent:bool=False): + def fromMapCanvas(mapCanvas, fullExtent: bool = False): assert isinstance(mapCanvas, QgsMapCanvas) if fullExtent: @@ -2341,6 +2347,7 @@ def setToolButtonDefaultActionMenu(toolButton: QToolButton, actions: list): menu.triggered.connect(toolButton.setDefaultAction) toolButton.setMenu(menu) + class SelectMapLayersDialog(QgsDialog): class LayerDescription(object): diff --git a/eotimeseriesviewer/labeling.py b/eotimeseriesviewer/labeling.py index ca64d401..5ddcf48a 100644 --- a/eotimeseriesviewer/labeling.py +++ b/eotimeseriesviewer/labeling.py @@ -534,6 +534,35 @@ class LabelAttributeTypeWidgetDelegate(QStyledItemDelegate): model.setData(index, w.currentData(Qt.UserRole), Qt.EditRole) +def gotoNextFeature(layer: QgsVectorLayer, tools: QgsVectorLayerTools): + assert isinstance(tools, QgsVectorLayerTools) + if isinstance(layer, QgsVectorLayer) and layer.hasFeatures(): + + allIDs = sorted(layer.allFeatureIds()) + fids = layer.selectedFeatureIds() + if len(fids) == 0: + nextFID = allIDs[0] + else: + i = min(allIDs.index(max(fids)) + 1, len(allIDs) - 1) + nextFID = allIDs[i] + layer.selectByIds([nextFID]) + tools.panToSelected(layer) + + +def gotoPreviousFeature(layer: QgsVectorLayer, tools: QgsVectorLayerTools): + assert isinstance(tools, QgsVectorLayerTools) + if isinstance(layer, QgsVectorLayer) and layer.hasFeatures(): + allIDs = sorted(layer.allFeatureIds()) + fids = layer.selectedFeatureIds() + if len(fids) == 0: + nextFID = allIDs[0] + else: + i = max(allIDs.index(min(fids)) - 1, 0) + nextFID = allIDs[i] + layer.selectByIds([nextFID]) + tools.panToSelected(layer) + + class LabelWidget(AttributeTableWidget): def __init__(self, *args, **kwds): @@ -542,64 +571,19 @@ class LabelWidget(AttributeTableWidget): self.mActionNextFeature = QAction('Next Feature', parent=self) self.mActionNextFeature.setIcon(QIcon(':/images/themes/default/mActionAtlasNext.svg')) - self.mActionNextFeature.triggered.connect(self.nextFeature) + self.mActionNextFeature.triggered.connect( + lambda *args, lyr=self.mLayer, vlt=self.vectorLayerTools(): gotoNextFeature(lyr, vlt) + ) self.mActionPreviousFeature = QAction('Previous Feature', parent=self) self.mActionPreviousFeature.setIcon(QIcon(':/images/themes/default/mActionAtlasPrev.svg')) - self.mActionPreviousFeature.triggered.connect(self.previousFeature) + self.mActionPreviousFeature.triggered.connect( + lambda *args, lyr=self.mLayer, vlt=self.vectorLayerTools(): gotoPreviousFeature(lyr, vlt)) self.mToolbar: QToolBar self.mToolbar.insertActions(self.mActionToggleEditing, [self.mActionPreviousFeature, self.mActionNextFeature]) self.mToolbar.insertSeparator(self.mActionToggleEditing) - def nextFeature(self): - """ - Selects the next feature and moves the map extent to. - """ - if isinstance(self.mLayer, QgsVectorLayer) and self.mLayer.hasFeatures(): - allIDs = sorted(self.mLayer.allFeatureIds()) - fids = self.mLayer.selectedFeatureIds() - if len(fids) == 0: - nextFID = allIDs[0] - else: - i = min(allIDs.index(max(fids)) + 1, len(allIDs) - 1) - nextFID = allIDs[i] - self.mLayer.selectByIds([nextFID]) - self.mVectorLayerTools.panToSelected(self.mLayer) - - def previousFeature(self): - """ - Selects the previous feature and moves the map extent to. - """ - if isinstance(self.mLayer, QgsVectorLayer) and self.mLayer.hasFeatures(): - allIDs = sorted(self.mLayer.allFeatureIds()) - fids = self.mLayer.selectedFeatureIds() - if len(fids) == 0: - nextFID = allIDs[0] - else: - i = max(allIDs.index(min(fids)) - 1, 0) - nextFID = allIDs[i] - self.mLayer.selectByIds([nextFID]) - self.mVectorLayerTools.panToSelected(self.mLayer) - - -class LabelDockWidget(QgsDockWidget): - - def __init__(self, layer, *args, **kwds): - super().__init__(*args, **kwds) - self.mLabelWidget = LabelWidget(layer) - self.setWidget(self.mLabelWidget) - self.setWindowTitle(self.mLabelWidget.windowTitle()) - self.mLabelWidget.windowTitleChanged.connect(self.setWindowTitle) - - def setVectorLayerTools(self, tools: QgsVectorLayerTools): - self.mLabelWidget.setVectorLayerTools(tools) - - def vectorLayer(self) -> QgsVectorLayer: - if isinstance(self.mLabelWidget.mLayer, QgsVectorLayer): - return self.mLabelWidget.mLayer - return None - class LabelShortcutEditorConfigWidget(QgsEditorConfigWidget): @@ -682,10 +666,6 @@ class LabelShortcutEditorWidgetWrapper(QgsEditorWidgetWrapper): def __init__(self, vl: QgsVectorLayer, fieldIdx: int, editor: QWidget, parent: QWidget): super(LabelShortcutEditorWidgetWrapper, self).__init__(vl, fieldIdx, editor, parent) - #self.mEditor = None - - #self.mValidator = None - def configLabelType(self) -> LabelShortcutType: return self.config().get(CONFKEY_LABELTYPE) @@ -729,7 +709,6 @@ class LabelShortcutEditorWidgetWrapper(QgsEditorWidgetWrapper): editor.valueChanged.connect(self.onValueChanged) else: s = "" - #self.mEditor = editor def onValueChanged(self, *args): self.valueChanged.emit(self.value()) diff --git a/eotimeseriesviewer/main.py b/eotimeseriesviewer/main.py index eed166ae..7ae4e186 100644 --- a/eotimeseriesviewer/main.py +++ b/eotimeseriesviewer/main.py @@ -19,48 +19,40 @@ ***************************************************************************/ """ # noinspection PyPep8Naming - - -r""" -File "D:\Programs\OSGeo4W\apps\Python27\lib\multiprocessing\managers.py", line -528, in start -self._address = reader.recv() -EOFError - -see https://github.com/pyinstaller/pyinstaller/wiki/Recipe-Multiprocessing -see https://github.com/CleanCut/green/issues/103 - -""" -""" -path = os.path.abspath(os.path.join(sys.exec_prefix, '../../bin/pythonw.exe')) -if os.path.exists(path): - multiprocessing.set_executable(path) - sys.argv = [ None ] -""" import sys -import qgis.utils - +import os +import re +import typing +import pathlib +import numpy as np +from qgis.PyQt.QtCore import pyqtSignal, pyqtSlot, QObject, QFile, Qt, QSize, QCoreApplication, QVariant +from qgis.PyQt.QtGui import QCloseEvent, QColor, QIcon +from qgis.PyQt.QtWidgets import QWidget, QHBoxLayout, QVBoxLayout, QGridLayout, QMainWindow, \ + QToolButton, QAction, QLabel, QProgressBar, QApplication, QSizePolicy, \ + QMenu, QDialogButtonBox, QProgressDialog, QToolBar, QComboBox, QDialog, QFileDialog, QDockWidget +from qgis.PyQt.QtXml import QDomDocument from qgis.core import QgsMapLayer, QgsRasterLayer, QgsVectorLayer, QgsMessageOutput, QgsCoordinateReferenceSystem, \ Qgis, QgsWkbTypes, QgsTask, QgsProviderRegistry, QgsMapLayerStore, QgsFeature, QgsField, \ QgsTextFormat, QgsProject, QgsSingleSymbolRenderer, QgsGeometry, QgsApplication, QgsFillSymbol, \ - QgsProjectArchive, QgsZipUtils, QgsPointXY + QgsProjectArchive, QgsZipUtils, QgsPointXY, QgsFields from qgis.gui import QgsMapCanvas, QgsStatusBar, QgsFileWidget, \ QgsMessageBar, QgsMessageViewer, QgsDockWidget, QgsTaskManagerWidget, QgisInterface - import qgis.utils -from eotimeseriesviewer import LOG_MESSAGE_TAG, settings -from eotimeseriesviewer.utils import SpatialPoint, datetime64, fixMenuButtons, file_search -from eotimeseriesviewer.timeseries import * -from eotimeseriesviewer.settings import Keys as SettingKeys +from eotimeseriesviewer import LOG_MESSAGE_TAG, DIR_UI, settings +from eotimeseriesviewer.utils import SpatialPoint, SpatialExtent, datetime64, fixMenuButtons, file_search, loadUi +from eotimeseriesviewer.timeseries import TimeSeries, TimeSeriesSource, TimeSeriesDate, TimeSeriesTreeView, \ + DateTimePrecision, SensorInstrument, SensorProxyLayer, SensorMatching, TimeSeriesFindOverlapTask, TimeSeriesDock +from eotimeseriesviewer.settings import Keys as SettingKeys, value as SettingValue +from eotimeseriesviewer.settings import defaultValues, setValue from eotimeseriesviewer.mapcanvas import MapCanvas +from eotimeseriesviewer.docks import LabelDockWidget, SpectralLibraryDockWidget from eotimeseriesviewer.profilevisualization import ProfileViewDock from eotimeseriesviewer.temporalprofiles import TemporalProfileLayer from eotimeseriesviewer.mapvisualization import MapView, MapWidget import eotimeseriesviewer.settings as eotsvSettings from .externals.qps.speclib.core import SpectralProfile, SpectralLibrary from .externals.qps.speclib.gui import SpectralLibraryPanel, SpectralLibraryWidget -from .externals.qps.maptools import MapTools, CursorLocationMapTool, QgsMapToolSelect, QgsMapToolSelectionHandler from .externals.qps.cursorlocationvalue import CursorLocationInfoModel, CursorLocationInfoDock from .externals.qps.vectorlayertools import VectorLayerTools from .externals.qps.maptools import MapTools @@ -69,11 +61,10 @@ from eotimeseriesviewer import debugLog DEBUG = False -EXTRA_SPECLIB_FIELDS = [ - QgsField('date', QVariant.String, 'varchar'), - QgsField('doy', QVariant.Int, 'int'), - QgsField('sensor', QVariant.String, 'varchar') -] +SPECTRA_PROFILE_FIELDS = SpectralProfile().fields() +SPECTRA_PROFILE_FIELDS.append(QgsField('date', QVariant.String, 'varchar')) +SPECTRA_PROFILE_FIELDS.append(QgsField('doy', QVariant.Int, 'int')) +SPECTRA_PROFILE_FIELDS.append(QgsField('sensor', QVariant.String, 'varchar')) class AboutDialogUI(QDialog): @@ -169,11 +160,11 @@ class EOTimeSeriesViewerUI(QMainWindow): # self.dockAdvancedDigitizingDockWidget.setVisible(False) area = Qt.BottomDockWidgetArea - panel = SpectralLibraryPanel(self) + #panel = SpectralLibraryPanel(self) - self.dockSpectralLibrary = self.addDockWidget(area, panel) + #self.dockSpectralLibrary = self.addDockWidget(area, panel) - self.tabifyDockWidget(self.dockTimeSeries, self.dockSpectralLibrary) + #self.tabifyDockWidget(self.dockTimeSeries, self.dockSpectralLibrary) self.tabifyDockWidget(self.dockTimeSeries, self.dockProfiles) # self.tabifyDockWidget(self.dockTimeSeries, self.dockLabeling) @@ -478,10 +469,10 @@ class EOTimeSeriesViewer(QgisInterface, QObject): self.ui.sigAboutToBeClosed.connect(onClosed) import qgis.utils assert isinstance(qgis.utils.iface, QgisInterface) - + QgsProject.instance().layersWillBeRemoved.connect(self.onLayersWillBeRemoved) QgsApplication.instance().messageLog().messageReceived.connect(self.logMessage) - self.mapLayerStore().addMapLayer(self.ui.dockSpectralLibrary.speclib()) + #self.mapLayerStore().addMapLayer(self.ui.dockSpectralLibrary.speclib()) self.mPostDataLoadingArgs: dict = dict() @@ -600,6 +591,7 @@ class EOTimeSeriesViewer(QgisInterface, QObject): self.ui.actionAddMapView.triggered.connect(mvd.createMapView) self.ui.actionAddTSD.triggered.connect(lambda: self.addTimeSeriesImages(None)) self.ui.actionAddVectorData.triggered.connect(lambda: self.addVectorData()) + self.ui.actionCreateSpectralLibrary.triggered.connect(self.onCreateSpectralLibrary) self.ui.actionAddSubDatasets.triggered.connect(self.openAddSubdatasetsDialog) # see https://sentinel.esa.int/web/sentinel/user-guides/sentinel-2-msi/data-formats/xsd @@ -625,8 +617,7 @@ class EOTimeSeriesViewer(QgisInterface, QObject): self.ui.actionSaveProject.triggered.connect(iface.actionSaveProject().trigger) self.profileDock.actionLoadProfileRequest.triggered.connect(self.activateIdentifyTemporalProfileMapTool) - self.ui.dockSpectralLibrary.SLW.actionSelectProfilesFromMap.triggered.connect( - self.activateIdentifySpectralProfileMapTool) + # connect buttons with actions self.ui.actionAbout.triggered.connect(lambda: AboutDialogUI(self.ui).exec_()) @@ -636,26 +627,15 @@ class EOTimeSeriesViewer(QgisInterface, QObject): from eotimeseriesviewer import DOCUMENTATION, SpectralLibrary, SpectralLibraryPanel, SpectralLibraryWidget self.ui.actionShowOnlineHelp.triggered.connect(lambda: webbrowser.open(DOCUMENTATION)) - SLW: SpectralLibraryWidget = self.ui.dockSpectralLibrary.spectralLibraryWidget() - assert isinstance(SLW, SpectralLibraryWidget) - SLW.actionSelectProfilesFromMap.setVisible(True) - SLW.sigLoadFromMapRequest.connect(lambda *args: self.setMapTool(MapTools.SpectralProfile)) - # SLW.setMapInteraction(True) - # SLW.setCurrentProfilesMode(SpectralLibraryWidget.CurrentProfilesMode.automatically) - # SLW.sigMapExtentRequested.connect(self.setSpatialExtent) - # SLW.sigMapCenterRequested.connect(self.setSpatialCenter) - SLW.setVectorLayerTools(self.mVectorLayerTools) + #SLW: SpectralLibraryWidget = self.ui.dockSpectralLibrary.spectralLibraryWidget() + #assert isinstance(SLW, SpectralLibraryWidget) + #SLW.setVectorLayerTools(self.mVectorLayerTools) # add time-specific fields - sl = self.spectralLibrary() + #sl = self.spectralLibrary() + - assert isinstance(sl, SpectralLibrary) - sl.setName('EOTS Spectral Library') - sl.startEditing() - for field in EXTRA_SPECLIB_FIELDS: - sl.addAttribute(field) - assert sl.commitChanges() - self.mMapLayerStore.addMapLayer(sl) + #self.mMapLayerStore.addMapLayer(sl) temporalProfileLayer = self.profileDock.temporalProfileLayer() assert isinstance(temporalProfileLayer, QgsVectorLayer) @@ -712,6 +692,29 @@ class EOTimeSeriesViewer(QgisInterface, QObject): if isinstance(archive, QgsProjectArchive): archive.clearProjectFile() + def onLayersWillBeRemoved(self, layers): + ids = [] + for l in layers: + if isinstance(l, QgsMapLayer): + ids.append(l.id()) + elif isinstance(l, str): + ids.append(l) + + to_remove = [] + for d in self.ui.findChildren(SpectralLibraryDockWidget): + assert isinstance(d, SpectralLibraryDockWidget) + if d.speclib().id() in ids: + to_remove.append(d) + + for d in self.ui.findChildren(LabelDockWidget): + assert isinstance(d, LabelDockWidget) + if d.vectorLayer().id() in ids: + to_remove.append(d) + + for d in to_remove: + self.ui.removeDockWidget(d) + + def onReadProject(self, doc: QDomDocument) -> bool: """ Reads images and visualization settings from a QgsProject QDomDocument @@ -817,15 +820,12 @@ class EOTimeSeriesViewer(QgisInterface, QObject): :param path: directory to save the images in :param format: raster format, e.g. 'PNG' or 'JPG' """ - from .mapcanvas import MapCanvas - from .mapvisualization import MapView - from .settings import Keys, setValue, value import string if path is None: d = SaveAllMapsDialog() - path = value(Keys.MapImageExportDirectory, default=None) + path = SettingValue(SettingKeys.MapImageExportDirectory, default=None) if isinstance(path, str): d.setDirectory(path) @@ -871,7 +871,7 @@ class EOTimeSeriesViewer(QgisInterface, QObject): if progressDialog.wasCanceled(): return - setValue(Keys.MapImageExportDirectory, path) + setValue(SettingKeys.MapImageExportDirectory, path) def onMapViewAdded(self, mapView: MapView): """ @@ -880,7 +880,7 @@ class EOTimeSeriesViewer(QgisInterface, QObject): :return: """ mapView.addLayer(self.profileDock.temporalProfileLayer()) - mapView.addLayer(self.spectralLibrary()) + mapView.addSpectralProfileLayers() def temporalProfileLayer(self) -> TemporalProfileLayer: """ @@ -890,20 +890,14 @@ class EOTimeSeriesViewer(QgisInterface, QObject): return self.profileDock.temporalProfileLayer() def spectralLibraryWidgets(self) -> typing.List[SpectralLibraryWidget]: - w = [] - w.append(self.ui.dockSpectralLibrary.SLW) - return w + return [dw.spectralLibraryWidget() for dw in self.ui.findChildren(SpectralLibraryDockWidget)] - def spectralLibrary(self) -> SpectralLibrary: + def spectralLibraries(self) -> typing.List[SpectralLibrary]: """ - Returns the SpectraLibrary of the SpectralLibrary dock + Returns the SpectraLibrary that are opened as the SpectralLibrary dock :return: SpectraLibrary """ - from .externals.qps.speclib.gui import SpectralLibraryPanel - if isinstance(self.ui.dockSpectralLibrary, SpectralLibraryPanel): - return self.ui.dockSpectralLibrary.SLW.speclib() - else: - return None + return [w.speclib() for w in self.spectralLibraryWidgets()] def openAddSubdatasetsDialog(self, *args, title: str = 'Open Subdatasets', @@ -1089,23 +1083,21 @@ class EOTimeSeriesViewer(QgisInterface, QObject): Reads the QSettings object and applies its values to related widget components """ - from eotimeseriesviewer.settings import value, Keys, defaultValues, setValue - # the default values defaults = defaultValues() - for key in list(Keys): - if value(key) == None and key in defaults.keys(): + for key in list(SettingKeys): + if SettingValue(key) == None and key in defaults.keys(): setValue(key, defaults[key]) - v = value(Keys.DateTimePrecision) + v = SettingValue(SettingKeys.DateTimePrecision) if isinstance(v, DateTimePrecision): self.mTimeSeries.setDateTimePrecision(v) - v = value(Keys.SensorMatching) + v = SettingValue(SettingKeys.SensorMatching) if isinstance(v, SensorMatching): self.mTimeSeries.setSensorMatching(v) - v = value(Keys.SensorSpecs) + v = SettingValue(SettingKeys.SensorSpecs) if isinstance(v, dict): sensors = dict() for s in self.sensors(): @@ -1119,19 +1111,19 @@ class EOTimeSeriesViewer(QgisInterface, QObject): if 'name' in specs.keys(): sensor.setName(specs['name']) - v = value(Keys.MapUpdateInterval) + v = SettingValue(SettingKeys.MapUpdateInterval) if isinstance(v, int) and v > 0: self.ui.mMapWidget.mMapRefreshTimer.start(v) - v = value(Keys.MapBackgroundColor) + v = SettingValue(SettingKeys.MapBackgroundColor) if isinstance(v, QColor): self.ui.dockMapViews.setMapBackgroundColor(v) - v = value(Keys.MapTextFormat) + v = SettingValue(SettingKeys.MapTextFormat) if isinstance(v, QgsTextFormat): self.ui.dockMapViews.setMapTextFormat(v) - v = value(Keys.MapSize) + v = SettingValue(SettingKeys.MapSize) if isinstance(v, QSize): self.ui.mMapWidget.setMapSize(v) @@ -1261,18 +1253,18 @@ class EOTimeSeriesViewer(QgisInterface, QObject): sensorLayers = [l for l in mapCanvas.layers() if isinstance(l, SensorProxyLayer)] currentSpectra = [] - sl = self.spectralLibrary() + for lyr in sensorLayers: assert isinstance(lyr, SensorProxyLayer) p = SpectralProfile.fromRasterLayer(lyr, spatialPoint) basename = os.path.basename(lyr.source()) if isinstance(p, SpectralProfile): - p2 = p.copyFieldSubset(sl.fields()) - p2.setName(f'{basename}') - p2.setAttribute('date', '{}'.format(tsd.date())) - p2.setAttribute('doy', int(tsd.doy())) - p2.setAttribute('sensor', tsd.sensor().name()) - currentSpectra.append(p2) + p = p.copyFieldSubset(SPECTRA_PROFILE_FIELDS) + p.setName(f'{basename}') + p.setAttribute('date', '{}'.format(tsd.date())) + p.setAttribute('doy', int(tsd.doy())) + p.setAttribute('sensor', tsd.sensor().name()) + currentSpectra.append(p) if self.mCurrentMapSpectraLoading == 'TOP': break @@ -1285,7 +1277,12 @@ class EOTimeSeriesViewer(QgisInterface, QObject): for s in spectra: assert isinstance(s, SpectralProfile) - for w in self.spectralLibraryWidgets(): + widgets: typing.List[SpectralLibraryWidget] = self.spectralLibraryWidgets() + if len(widgets) == 0: + self.onCreateSpectralLibrary() + widgets = self.spectralLibraryWidgets() + + for w in widgets: w.setCurrentProfiles(spectra) @pyqtSlot(SpatialPoint) @@ -1349,7 +1346,7 @@ class EOTimeSeriesViewer(QgisInterface, QObject): :param n_max: :return: """ - s = settings() + s = settings.settings() if not (isinstance(path, str) and os.path.isfile(path)): defFile = s.value('file_ts_definition') @@ -1465,7 +1462,7 @@ class EOTimeSeriesViewer(QgisInterface, QObject): return self.ui.mMapWidget def saveTimeSeriesDefinition(self): - s = settings() + s = settings.settings() defFile = s.value('FILE_TS_DEFINITION') if defFile is not None: defFile = os.path.dirname(defFile) @@ -1588,24 +1585,46 @@ class EOTimeSeriesViewer(QgisInterface, QObject): if isinstance(layer, (QgsRasterLayer, QgsVectorLayer)): self.currentLayerChanged.emit(layer) + def vectorLayerTools(self) -> VectorLayerTools: + return self.mVectorLayerTools + def show(self): self.ui.show() def showAttributeTable(self, lyr: QgsVectorLayer, filterExpression: str = ""): assert isinstance(lyr, QgsVectorLayer) - from eotimeseriesviewer.labeling import LabelDockWidget # 1. check if this layer is already opened as dock widget - for d in self.ui.findChildren(LabelDockWidget): - assert isinstance(d, LabelDockWidget) - if d.vectorLayer() == lyr: + docks: typing.List[QgsDockWidget] = [d for d in self.ui.findChildren(QgsDockWidget) + if isinstance(d, (LabelDockWidget, SpectralLibraryDockWidget))] + + for d in docks: + if isinstance(d, LabelDockWidget) and d.vectorLayer().id() == lyr.id(): + d.show() + return + elif isinstance(d, SpectralLibraryDockWidget) and d.speclib().id() == lyr.id(): + d.show() return # 2. create dock widget - from .labeling import LabelWidget - dock = LabelDockWidget(lyr) - dock.setVectorLayerTools(self.mVectorLayerTools) + + if isinstance(lyr, SpectralLibrary): + dock = SpectralLibraryDockWidget(speclib=lyr) + dock.setObjectName(f'SpectralLibraryDockWidget{id(dock)}') + dock.setVectorLayerTools(self.vectorLayerTools()) + dock.SLW.actionSelectProfilesFromMap.setVisible(True) + dock.SLW.sigLoadFromMapRequest.connect(lambda *args: self.setMapTool(MapTools.SpectralProfile)) + #dock.SLW.actionSelectProfilesFromMap.triggered.connect(self.activateIdentifySpectralProfileMapTool) + else: + dock = LabelDockWidget(lyr) + dock.setObjectName(f'LabelDockWidget{id(dock)}') + dock.setVectorLayerTools(self.vectorLayerTools()) + self.ui.addDockWidget(Qt.BottomDockWidgetArea, dock) + self.ui.menuPanels.addAction(dock.toggleViewAction()) + if len(docks) > 0: + self.ui.tabifyDockWidget(docks[0], dock) + dock.raise_() def clearLayoutWidgets(self, L): if L is not None: @@ -1617,6 +1636,35 @@ class EOTimeSeriesViewer(QgisInterface, QObject): # w.widget().deleteLater() QApplication.processEvents() + def addMapLayers(self, layers: typing.List[QgsMapLayer]) -> typing.List[QgsMapLayer]: + """ + Adds QgsMapLayers + :param layers: + :return: + """ + if isinstance(layers, QgsMapLayer): + layers = [layers] + + layers = [l for l in layers if isinstance(l, QgsMapLayer) and not isinstance(l, SensorProxyLayer)] + if len(layers) > 0: + QgsProject.instance().addMapLayers(layers) + for mapView in self.mapViews(): + assert isinstance(mapView, MapView) + for l in layers: + mapView.addLayer(l) + + break # add to first mapview only + return layers + + def onCreateSpectralLibrary(self): + speclib: SpectralLibrary = SpectralLibrary() + speclib.startEditing() + for field in SPECTRA_PROFILE_FIELDS: + speclib.addAttribute(field) + assert speclib.commitChanges() + self.showAttributeTable(speclib) + self.addMapLayers(speclib) + def addVectorData(self, files=None) -> list: """ Adds vector data @@ -1625,7 +1673,7 @@ class EOTimeSeriesViewer(QgisInterface, QObject): """ vectorLayers = [] if files is None: - s = settings() + s = settings.settings() defDir = s.value('DIR_FILESEARCH') filters = QgsProviderRegistry.instance().fileVectorFilters() files, filter = QFileDialog.getOpenFileNames(directory=defDir, filter=filters) @@ -1642,13 +1690,7 @@ class EOTimeSeriesViewer(QgisInterface, QObject): vectorLayers.extend(subLayers(QgsVectorLayer(f))) if len(vectorLayers) > 0: - QgsProject.instance().addMapLayers(vectorLayers) - for mapView in self.mapViews(): - assert isinstance(mapView, MapView) - for l in vectorLayers: - mapView.addLayer(l) - - break # add to first mapview only + self.addMapLayers(vectorLayers) return vectorLayers @@ -1658,7 +1700,7 @@ class EOTimeSeriesViewer(QgisInterface, QObject): :param files: """ if files is None: - s = settings() + s = settings.settings() defDir = s.value('dir_datasources') filters = QgsProviderRegistry.instance().fileRasterFilters() diff --git a/eotimeseriesviewer/mapvisualization.py b/eotimeseriesviewer/mapvisualization.py index 536529b8..33e60f18 100644 --- a/eotimeseriesviewer/mapvisualization.py +++ b/eotimeseriesviewer/mapvisualization.py @@ -43,7 +43,7 @@ from qgis.PyQt.QtWidgets import \ from qgis.PyQt.QtXml import \ QDomDocument, QDomNode, QDomElement from qgis.core import \ - QgsCoordinateReferenceSystem, QgsVector, QgsTextFormat, \ + QgsCoordinateReferenceSystem, QgsVectorLayer, QgsTextFormat, \ QgsRectangle, QgsRasterRenderer, QgsMapLayerStore, QgsMapLayerStyle, \ QgsLayerTreeModel, QgsLayerTreeGroup, QgsPointXY, \ QgsLayerTree, QgsLayerTreeLayer, QgsReadWriteContext, \ @@ -186,6 +186,9 @@ class MapView(QFrame): fixMenuButtons(self) + def __iter__(self) -> typing.Iterator[TimeSeriesDate]: + return iter(self.mapCanvases()) + @staticmethod def readXml(node: QDomNode): if node.nodeName() == 'MapView': @@ -332,14 +335,14 @@ class MapView(QFrame): c.setCurrentLayer(layer) return True - def addSpectralProfileLayer(self): - """Adds the EOTSV Spectral Profile Layer""" + def addSpectralProfileLayers(self): + """Adds the EOTSV Spectral Profile Layers""" from eotimeseriesviewer.main import EOTimeSeriesViewer tsv = EOTimeSeriesViewer.instance() if isinstance(tsv, EOTimeSeriesViewer): - lyr = tsv.spectralLibrary() - if lyr not in self.layers(): - self.addLayer(lyr) + for lyr in tsv.spectralLibraries(): + if lyr not in self.layers(): + self.addLayer(lyr) def addTemporalProfileLayer(self): """Adds the EOTSV Temporal Profile Layer""" @@ -816,7 +819,7 @@ class MapViewLayerTreeViewMenuProvider(QgsLayerTreeViewMenuProvider): # ---- menu.addSeparator() a = menu.addAction('Add Spectral Library Layer') - a.triggered.connect(self.mapView().addSpectralProfileLayer) + a.triggered.connect(self.mapView().addSpectralProfileLayers) a = menu.addAction('Add Temporal Profile Layer') a.triggered.connect(self.mapView().addTemporalProfileLayer) @@ -1003,8 +1006,8 @@ class MapWidget(QFrame): sigMapViewAdded = pyqtSignal(MapView) sigMapViewRemoved = pyqtSignal(MapView) sigCurrentLayerChanged = pyqtSignal(QgsMapLayer) - sigCurrentCanvasChanged = pyqtSignal(MapCanvas) - sigCurrentMapViewChanged = pyqtSignal(MapView) + # sigCurrentCanvasChanged = pyqtSignal(MapCanvas) + # sigCurrentMapViewChanged = pyqtSignal(MapView) sigCurrentDateChanged = pyqtSignal(TimeSeriesDate) sigCurrentLocationChanged = pyqtSignal([QgsCoordinateReferenceSystem, QgsPointXY], [QgsCoordinateReferenceSystem, QgsPointXY, QgsMapCanvas]) @@ -1281,7 +1284,7 @@ class MapWidget(QFrame): canvases = mapView.mapCanvases() if len(canvases) > 0: position = min(position, len(canvases) - 1) - self.currentMapCanvas(canvases[position]) + self.setCurrentMapCanvas(canvases[position]) def writeXml(self, node: QDomElement, doc: QDomDocument) -> bool: """ diff --git a/eotimeseriesviewer/ui/timeseriesviewer.ui b/eotimeseriesviewer/ui/timeseriesviewer.ui index a0dd174a..c17e4e8f 100644 --- a/eotimeseriesviewer/ui/timeseriesviewer.ui +++ b/eotimeseriesviewer/ui/timeseriesviewer.ui @@ -75,7 +75,7 @@ <x>0</x> <y>0</y> <width>774</width> - <height>17</height> + <height>26</height> </rect> </property> <property name="contextMenuPolicy"> @@ -100,6 +100,7 @@ <addaction name="menuAdd_Product"/> <addaction name="separator"/> <addaction name="actionAddVectorData"/> + <addaction name="actionCreateSpectralLibrary"/> <addaction name="separator"/> <addaction name="actionLoadTS"/> <addaction name="actionSaveTS"/> @@ -388,7 +389,7 @@ <bool>true</bool> </property> <property name="icon"> - <iconset resource="../../../../cpp/QGIS/images/images.qrc"> + <iconset resource="../../../QGIS/images/images.qrc"> <normaloff>:/images/themes/default/mActionZoomIn.svg</normaloff>:/images/themes/default/mActionZoomIn.svg</iconset> </property> <property name="text"> @@ -403,7 +404,7 @@ <bool>true</bool> </property> <property name="icon"> - <iconset resource="../../../../cpp/QGIS/images/images.qrc"> + <iconset resource="../../../QGIS/images/images.qrc"> <normaloff>:/images/themes/default/mActionZoomOut.svg</normaloff>:/images/themes/default/mActionZoomOut.svg</iconset> </property> <property name="text"> @@ -418,7 +419,7 @@ <bool>true</bool> </property> <property name="icon"> - <iconset resource="../../../../cpp/QGIS/images/images.qrc"> + <iconset resource="../../../QGIS/images/images.qrc"> <normaloff>:/images/themes/default/mActionZoomFullExtent.svg</normaloff>:/images/themes/default/mActionZoomFullExtent.svg</iconset> </property> <property name="text"> @@ -433,7 +434,7 @@ <bool>true</bool> </property> <property name="icon"> - <iconset resource="../../../../cpp/QGIS/images/images.qrc"> + <iconset resource="../../../QGIS/images/images.qrc"> <normaloff>:/images/themes/default/mActionZoomActual.svg</normaloff>:/images/themes/default/mActionZoomActual.svg</iconset> </property> <property name="text"> @@ -448,7 +449,7 @@ <bool>true</bool> </property> <property name="icon"> - <iconset resource="../../../../cpp/QGIS/images/images.qrc"> + <iconset resource="../../../QGIS/images/images.qrc"> <normaloff>:/images/themes/default/mActionPan.svg</normaloff>:/images/themes/default/mActionPan.svg</iconset> </property> <property name="text"> @@ -522,7 +523,7 @@ </action> <action name="actionRefresh"> <property name="icon"> - <iconset resource="../../../../cpp/QGIS/images/images.qrc"> + <iconset resource="../../../QGIS/images/images.qrc"> <normaloff>:/images/themes/default/mActionReload.svg</normaloff>:/images/themes/default/mActionReload.svg</iconset> </property> <property name="text"> @@ -600,7 +601,7 @@ </action> <action name="actionAddVectorData"> <property name="icon"> - <iconset resource="../../../../cpp/QGIS/images/images.qrc"> + <iconset resource="../../../QGIS/images/images.qrc"> <normaloff>:/images/themes/default/mActionAddOgrLayer.svg</normaloff>:/images/themes/default/mActionAddOgrLayer.svg</iconset> </property> <property name="text"> @@ -653,7 +654,7 @@ <bool>false</bool> </property> <property name="icon"> - <iconset resource="../../../../cpp/QGIS/images/images.qrc"> + <iconset resource="../../../QGIS/images/images.qrc"> <normaloff>:/images/themes/default/mActionPropertiesWidget.svg</normaloff>:/images/themes/default/mActionPropertiesWidget.svg</iconset> </property> <property name="text"> @@ -728,7 +729,7 @@ <bool>true</bool> </property> <property name="icon"> - <iconset resource="../../../../cpp/QGIS/images/images.qrc"> + <iconset resource="../../../QGIS/images/images.qrc"> <normaloff>:/images/themes/default/mActionSelectRectangle.svg</normaloff>:/images/themes/default/mActionSelectRectangle.svg</iconset> </property> <property name="text"> @@ -743,7 +744,7 @@ <bool>true</bool> </property> <property name="icon"> - <iconset resource="../../../../cpp/QGIS/images/images.qrc"> + <iconset resource="../../../QGIS/images/images.qrc"> <normaloff>:/images/themes/default/mActionSelectPolygon.svg</normaloff>:/images/themes/default/mActionSelectPolygon.svg</iconset> </property> <property name="text"> @@ -758,7 +759,7 @@ <bool>true</bool> </property> <property name="icon"> - <iconset resource="../../../../cpp/QGIS/images/images.qrc"> + <iconset resource="../../../QGIS/images/images.qrc"> <normaloff>:/images/themes/default/mActionSelectFreehand.svg</normaloff>:/images/themes/default/mActionSelectFreehand.svg</iconset> </property> <property name="text"> @@ -770,7 +771,7 @@ <bool>true</bool> </property> <property name="icon"> - <iconset resource="../../../../cpp/QGIS/images/images.qrc"> + <iconset resource="../../../QGIS/images/images.qrc"> <normaloff>:/images/themes/default/mActionSelectRadius.svg</normaloff>:/images/themes/default/mActionSelectRadius.svg</iconset> </property> <property name="text"> @@ -779,7 +780,7 @@ </action> <action name="actionExportMapsToImages"> <property name="icon"> - <iconset resource="../../../../cpp/QGIS/images/images.qrc"> + <iconset resource="../../../QGIS/images/images.qrc"> <normaloff>:/images/themes/default/mActionSaveMapAsImage.svg</normaloff>:/images/themes/default/mActionSaveMapAsImage.svg</iconset> </property> <property name="text"> @@ -799,7 +800,7 @@ <bool>true</bool> </property> <property name="icon"> - <iconset resource="../../../../cpp/QGIS/images/images.qrc"> + <iconset resource="../../../QGIS/images/images.qrc"> <normaloff>:/images/themes/default/lockedGray.svg</normaloff>:/images/themes/default/lockedGray.svg</iconset> </property> <property name="text"> @@ -827,7 +828,7 @@ </action> <action name="mActionSaveEdits"> <property name="icon"> - <iconset resource="../../../../cpp/QGIS/images/images.qrc"> + <iconset resource="../../../QGIS/images/images.qrc"> <normaloff>:/images/themes/default/mActionSaveEdits.svg</normaloff>:/images/themes/default/mActionSaveEdits.svg</iconset> </property> <property name="text"> @@ -842,7 +843,7 @@ <bool>true</bool> </property> <property name="icon"> - <iconset resource="../../../../cpp/QGIS/images/images.qrc"> + <iconset resource="../../../QGIS/images/images.qrc"> <normaloff>:/images/themes/default/mActionCapturePolygon.svg</normaloff>:/images/themes/default/mActionCapturePolygon.svg</iconset> </property> <property name="text"> @@ -857,7 +858,7 @@ <bool>true</bool> </property> <property name="icon"> - <iconset resource="../../../../cpp/QGIS/images/images.qrc"> + <iconset resource="../../../QGIS/images/images.qrc"> <normaloff>:/images/themes/default/mActionToggleEditing.svg</normaloff>:/images/themes/default/mActionToggleEditing.svg</iconset> </property> <property name="text"> @@ -869,7 +870,7 @@ <bool>true</bool> </property> <property name="icon"> - <iconset resource="../../../../cpp/QGIS/images/images.qrc"> + <iconset resource="../../../QGIS/images/images.qrc"> <normaloff>:/images/themes/default/mActionSelectRectangle.svg</normaloff>:/images/themes/default/mActionSelectRectangle.svg</iconset> </property> <property name="text"> @@ -884,7 +885,7 @@ </action> <action name="mActionDeselectFeatures"> <property name="icon"> - <iconset resource="../../../../cpp/QGIS/images/images.qrc"> + <iconset resource="../../../QGIS/images/images.qrc"> <normaloff>:/images/themes/default/mActionDeselectAll.svg</normaloff>:/images/themes/default/mActionDeselectAll.svg</iconset> </property> <property name="text"> @@ -901,7 +902,7 @@ </action> <action name="mActionOpenTable"> <property name="icon"> - <iconset resource="../../../../cpp/QGIS/images/images.qrc"> + <iconset resource="../../../QGIS/images/images.qrc"> <normaloff>:/images/themes/default/mActionOpenTable.svg</normaloff>:/images/themes/default/mActionOpenTable.svg</iconset> </property> <property name="text"> @@ -913,7 +914,7 @@ </action> <action name="mActionZoomToLayer"> <property name="icon"> - <iconset resource="../../../../cpp/QGIS/images/images.qrc"> + <iconset resource="../../../QGIS/images/images.qrc"> <normaloff>:/images/themes/default/mActionZoomToLayer.svg</normaloff>:/images/themes/default/mActionZoomToLayer.svg</iconset> </property> <property name="text"> @@ -932,7 +933,7 @@ </action> <action name="actionSaveProject"> <property name="icon"> - <iconset resource="../../../../cpp/QGIS/images/images.qrc"> + <iconset resource="../../../QGIS/images/images.qrc"> <normaloff>:/images/themes/default/mActionFileSave.svg</normaloff>:/images/themes/default/mActionFileSave.svg</iconset> </property> <property name="text"> @@ -941,7 +942,7 @@ </action> <action name="actionLoadProject"> <property name="icon"> - <iconset resource="../../../../cpp/QGIS/images/images.qrc"> + <iconset resource="../../../QGIS/images/images.qrc"> <normaloff>:/images/themes/default/mActionFileOpen.svg</normaloff>:/images/themes/default/mActionFileOpen.svg</iconset> </property> <property name="text"> @@ -956,6 +957,18 @@ <string>Reloads settings from the QGIS Project</string> </property> </action> + <action name="actionCreateSpectralLibrary"> + <property name="icon"> + <iconset resource="../externals/qps/qpsresources.qrc"> + <normaloff>:/qps/ui/icons/speclib.svg</normaloff>:/qps/ui/icons/speclib.svg</iconset> + </property> + <property name="text"> + <string>Create Spectral Library</string> + </property> + <property name="toolTip"> + <string>Opens a Spectral Library to collect spectral profiles</string> + </property> + </action> </widget> <customwidgets> <customwidget> @@ -968,7 +981,7 @@ <resources> <include location="eotsv_resources.qrc"/> <include location="../externals/qps/qpsresources.qrc"/> - <include location="../../../../cpp/QGIS/images/images.qrc"/> + <include location="../../../QGIS/images/images.qrc"/> </resources> <connections/> </ui> -- GitLab