diff --git a/eotimeseriesviewer/__init__.py b/eotimeseriesviewer/__init__.py index 517ade224ae12905acc799f8f5b6f4bc087dd59e..6e30947a67f1c5956470d991f7a119bb2aabf4da 100644 --- a/eotimeseriesviewer/__init__.py +++ b/eotimeseriesviewer/__init__.py @@ -19,7 +19,10 @@ ***************************************************************************/ """ # noinspection PyPep8Naming - +import os +import pathlib +from qgis.core import QgsApplication, Qgis +from qgis.PyQt.QtGui import QIcon __version__ = '1.13' # sub-subversion number is added automatically LICENSE = 'GNU GPL-3' @@ -37,16 +40,6 @@ CREATE_ISSUE = 'https://bitbucket.org/jakimowb/eo-time-series-viewer/issues/new' DEPENDENCIES = ['numpy', 'gdal'] URL_TESTDATA = r'' -import os -import sys -import fnmatch -import site -import re -import pathlib - -from qgis.core import QgsApplication, Qgis -from qgis.PyQt.QtGui import QIcon - DEBUG: bool = bool(os.environ.get('EOTSV_DEBUG', False)) DIR = pathlib.Path(__file__).parent diff --git a/eotimeseriesviewer/dateparser.py b/eotimeseriesviewer/dateparser.py index 62a0c86913e05b8be861c324ec9b12c6edae23fb..0c26de72d5eadc53f41274208b210b84e2031cdb 100644 --- a/eotimeseriesviewer/dateparser.py +++ b/eotimeseriesviewer/dateparser.py @@ -1,22 +1,27 @@ - -import os, re, logging, datetime -from osgeo import gdal +import datetime +import re +import os import numpy as np -from qgis import * -from qgis.PyQt.QtCore import QDate +from osgeo import gdal +from qgis.core import QgsMapLayer, QgsRasterLayer +from qgis.PyQt.QtCore import QDate # regular expression. compile them only once # thanks to user "funkwurm" in # http://stackoverflow.com/questions/28020805/regex-validate-correct-iso8601-date-string-with-time -regISODate1 = re.compile(r'(?:[1-9]\d{3}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1\d|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)|(?:[1-9]\d(?:0[48]|[2468][048]|[13579][26])|(?:[2468][048]|[13579][26])00)-02-29)T(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:Z|[+-][01]\d:[0-5]\d)') -regISODate3 = re.compile(r'([\+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24\:?00)([\.,]\d+(?!:))?)?(\17[0-5]\d([\.,]\d+)?)?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?') -regISODate2 = re.compile(r'(19|20|21\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24\:?00)([\.,]\d+(?!:))?)?(\17[0-5]\d([\.,]\d+)?)?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?') -#regISODate2 = re.compile(r'([12]\d{3}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24\:?00)([\.,]\d+(?!:))?)?(\17[0-5]\d([\.,]\d+)?)?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?') -#https://www.safaribooksonline.com/library/view/regular-expressions-cookbook/9781449327453/ch04s07.html - -regYYYYMMDD = re.compile(r'(?P<year>(19|20)\d\d)(?P<hyphen>-?)(?P<month>1[0-2]|0[1-9])(?P=hyphen)(?P<day>3[01]|0[1-9]|[12][0-9])') +regISODate1 = re.compile( + r'(?:[1-9]\d{3}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1\d|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)|(?:[1-9]\d(?:0[48]|[2468][048]|[13579][26])|(?:[2468][048]|[13579][26])00)-02-29)T(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:Z|[+-][01]\d:[0-5]\d)') +regISODate3 = re.compile( + r'([\+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24\:?00)([\.,]\d+(?!:))?)?(\17[0-5]\d([\.,]\d+)?)?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?') +regISODate2 = re.compile( + r'(19|20|21\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24\:?00)([\.,]\d+(?!:))?)?(\17[0-5]\d([\.,]\d+)?)?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?') +# regISODate2 = re.compile(r'([12]\d{3}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24\:?00)([\.,]\d+(?!:))?)?(\17[0-5]\d([\.,]\d+)?)?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?') +# https://www.safaribooksonline.com/library/view/regular-expressions-cookbook/9781449327453/ch04s07.html + +regYYYYMMDD = re.compile( + r'(?P<year>(19|20)\d\d)(?P<hyphen>-?)(?P<month>1[0-2]|0[1-9])(?P=hyphen)(?P<day>3[01]|0[1-9]|[12][0-9])') regYYYY = re.compile(r'(?P<year>(19|20)\d\d)') regMissingHypen = re.compile(r'^\d{8}') regYYYYMM = re.compile(r'([0-9]{4})-(1[0-2]|0[1-9])') @@ -24,6 +29,7 @@ regYYYYMM = re.compile(r'([0-9]{4})-(1[0-2]|0[1-9])') regYYYYDOY = re.compile(r'(?P<year>(19|20)\d\d)-?(?P<day>36[0-6]|3[0-5][0-9]|[12][0-9]{2}|0[1-9][0-9]|00[1-9])') regDecimalYear = re.compile(r'(?P<year>(19|20)\d\d)\.(?P<datefraction>\d\d\d)') + def matchOrNone(regex, text): match = regex.search(text) if match: @@ -31,7 +37,8 @@ def matchOrNone(regex, text): else: return None -def dateDOY(date:datetime.date) -> int: + +def dateDOY(date: datetime.date) -> int: """ Returns the DOY :param date: @@ -43,6 +50,7 @@ def dateDOY(date:datetime.date) -> int: date = date.astype(datetime.date) return date.timetuple().tm_yday + def daysPerYear(year) -> int: """Returns the days per year""" if isinstance(year, np.datetime64): @@ -52,6 +60,7 @@ def daysPerYear(year) -> int: return dateDOY(datetime.date(year=year, month=12, day=31)) + def num2date(n, dt64=True, qDate=False): """ Converts a decimal-year number into a date @@ -69,12 +78,11 @@ def num2date(n, dt64=True, qDate=False): yearDuration = daysPerYear(year) yearElapsed = fraction * yearDuration - import math doy = round(yearElapsed) if doy < 1: doy = 1 try: - date = datetime.date(year, 1, 1) + datetime.timedelta(days=doy-1) + date = datetime.date(year, 1, 1) + datetime.timedelta(days=doy - 1) except: s = "" if qDate: @@ -85,7 +93,7 @@ def num2date(n, dt64=True, qDate=False): return date -def extractDateTimeGroup(text:str) -> np.datetime64: +def extractDateTimeGroup(text: str) -> np.datetime64: """ Extracts a date-time-group from a text string :param text: a string @@ -95,7 +103,7 @@ def extractDateTimeGroup(text:str) -> np.datetime64: if match: matchedText = match.group() if regMissingHypen.search(matchedText): - matchedText = '{}-{}-{}'.format(matchedText[0:4],matchedText[4:6],matchedText[6:]) + matchedText = '{}-{}-{}'.format(matchedText[0:4], matchedText[4:6], matchedText[6:]) return np.datetime64(matchedText) match = regYYYYMMDD.search(text) @@ -122,29 +130,32 @@ def extractDateTimeGroup(text:str) -> np.datetime64: return np.datetime64(match.group('year')) return None + def datetime64FromYYYYMMDD(yyyymmdd): if re.search(r'^\d{8}$', yyyymmdd): - #insert hyphens - yyyymmdd = '{}-{}-{}'.format(yyyymmdd[0:4],yyyymmdd[4:6],yyyymmdd[6:8]) + # insert hyphens + yyyymmdd = '{}-{}-{}'.format(yyyymmdd[0:4], yyyymmdd[4:6], yyyymmdd[6:8]) return np.datetime64(yyyymmdd) + def datetime64FromYYYYDOY(yyyydoy): return datetime64FromDOY(yyyydoy[0:4], yyyydoy[4:7]) + def DOYfromDatetime64(dt): doy = dt.astype('datetime64[D]') - dt.astype('datetime64[Y]') + 1 doy = doy.astype(np.int16) return doy - return (dt.astype('datetime64[D]') - dt.astype('datetime64[Y]')).astype(int)+1 + return (dt.astype('datetime64[D]') - dt.astype('datetime64[Y]')).astype(int) + 1 -def datetime64FromDOY(year, doy): - if type(year) is str: - year = int(year) - if type(doy) is str: - doy = int(doy) - return np.datetime64('{:04d}-01-01'.format(year)) + np.timedelta64(doy-1, 'D') +def datetime64FromDOY(year, doy): + if type(year) is str: + year = int(year) + if type(doy) is str: + doy = int(doy) + return np.datetime64('{:04d}-01-01'.format(year)) + np.timedelta64(doy - 1, 'D') class ImageDateReader(object): @@ -166,6 +177,7 @@ class ImageDateReader(object): raise NotImplementedError() return None + class ImageReaderOWS(ImageDateReader): """Date reader for OGC web services""" @@ -189,6 +201,7 @@ class ImageDateReaderDefault(ImageDateReader): """ Default reader for dates in gdal.Datasets """ + def __init__(self, dataSet): super(ImageDateReaderDefault, self).__init__(dataSet) self.regDateKeys = re.compile('(acquisition[ _]*(time|date|datetime))', re.IGNORECASE) @@ -218,10 +231,12 @@ class ImageDateReaderDefault(ImageDateReader): return dtg return None + class ImageDateReaderPLEIADES(ImageDateReader): """ Date reader for PLEIADES images """ + def __init__(self, dataSet): super(ImageDateReaderPLEIADES, self).__init__(dataSet) @@ -254,20 +269,22 @@ class ImageDateReaderSentinel2(ImageDateReader): return np.datetime64(timeStamp) return None + class ImageDateParserLandsat(ImageDateReader): """ Reader for date in LANDSAT images #see https://landsat.usgs.gov/what-are-naming-conventions-landsat-scene-identifiers """ - regLandsatSceneID = re.compile(r'L[COTEM][4578]\d{3}\d{3}\d{4}\d{3}[A-Z]{2}[A-Z1]\d{2}') - regLandsatProductID = re.compile(r'L[COTEM]0[78]_(L1TP|L1GT|L1GS)_\d{3}\d{3}_\d{4}\d{2}\d{2}_\d{4}\d{2}\d{2}_0\d{1}_(RT|T1|T2)') + regLandsatSceneID = re.compile(r'L[COTEM][4578]\d{3}\d{3}\d{4}\d{3}[A-Z]{2}[A-Z1]\d{2}') + regLandsatProductID = re.compile( + r'L[COTEM]0[78]_(L1TP|L1GT|L1GS)_\d{3}\d{3}_\d{4}\d{2}\d{2}_\d{4}\d{2}\d{2}_0\d{1}_(RT|T1|T2)') def __init__(self, dataSet): super(ImageDateParserLandsat, self).__init__(dataSet) def readDTG(self): - #search for LandsatSceneID (old) and Landsat Product IDs (new) + # search for LandsatSceneID (old) and Landsat Product IDs (new) sceneID = matchOrNone(ImageDateParserLandsat.regLandsatSceneID, self.baseName) if sceneID: return datetime64FromYYYYDOY(sceneID[9:16]) @@ -278,15 +295,14 @@ class ImageDateParserLandsat(ImageDateReader): return None - dateParserList = [c for c in ImageDateReader.__subclasses__()] -dateParserList.insert(0, dateParserList.pop(dateParserList.index(ImageDateReaderDefault))) # set to first position +dateParserList.insert(0, dateParserList.pop(dateParserList.index(ImageDateReaderDefault))) # set to first position -def parseDateFromDataSet(dataSet:gdal.Dataset) -> np.datetime64: + +def parseDateFromDataSet(dataSet: gdal.Dataset) -> np.datetime64: assert isinstance(dataSet, gdal.Dataset) for parser in dateParserList: dtg = parser(dataSet).readDTG() if dtg: return dtg return None - diff --git a/eotimeseriesviewer/labeling.py b/eotimeseriesviewer/labeling.py index 3210b2fb6dd67a1b20d45029936999251712a675..aeda382075f7383242a047b3b18de2778506354a 100644 --- a/eotimeseriesviewer/labeling.py +++ b/eotimeseriesviewer/labeling.py @@ -1,10 +1,8 @@ import enum -from qgis.core import * from qgis.core import QgsMapLayer, QgsRasterLayer, QgsVectorLayer, QgsField, QgsFields, \ QgsEditorWidgetSetup, QgsFeature, QgsVectorLayerTools, \ QgsRendererCategory, QgsCategorizedSymbolRenderer, QgsProject, QgsMapLayerStore, QgsSymbol -from qgis.gui import * from qgis.gui import QgsDockWidget, QgsSpinBox, QgsDoubleSpinBox, \ QgsEditorConfigWidget, QgsEditorWidgetFactory, QgsEditorWidgetWrapper, \ QgsGui, QgsEditorWidgetRegistry, QgsDateTimeEdit, QgsDateEdit, QgsTimeEdit diff --git a/eotimeseriesviewer/main.py b/eotimeseriesviewer/main.py index b299dac97704d6e3234d896378b1f7576d23a3d3..3251271062d201d316b09375800f871cdfedb6d8 100644 --- a/eotimeseriesviewer/main.py +++ b/eotimeseriesviewer/main.py @@ -43,7 +43,7 @@ from qgis.core import * from qgis.core import QgsMapLayer, QgsRasterLayer, QgsVectorLayer, QgsMessageOutput, QgsCoordinateReferenceSystem, \ Qgis, QgsWkbTypes, QgsTask, QgsProviderRegistry, QgsMapLayerStore, QgsFeature, QgsField, \ QgsTextFormat, QgsProject, QgsSingleSymbolRenderer, QgsGeometry, QgsApplication, QgsFillSymbol, \ - QgsProjectArchive, QgsZipUtils + QgsProjectArchive, QgsZipUtils, QgsPointXY from qgis.gui import * from qgis.gui import QgsMapCanvas, QgsStatusBar, QgsFileWidget, \ @@ -410,8 +410,8 @@ class EOTimeSeriesViewer(QgisInterface, QObject): """ return EOTimeSeriesViewer._instance - sigCurrentLocationChanged = pyqtSignal([SpatialPoint], - [SpatialPoint, QgsMapCanvas]) + sigCurrentLocationChanged = pyqtSignal([QgsCoordinateReferenceSystem, QgsPointXY], + [QgsCoordinateReferenceSystem, QgsPointXY, QgsMapCanvas]) sigCurrentSpectralProfilesChanged = pyqtSignal(list) sigCurrentTemporalProfilesChanged = pyqtSignal(list) @@ -456,15 +456,19 @@ class EOTimeSeriesViewer(QgisInterface, QObject): self.mTaskManagerButton.setFont(statusBarFont) self.mStatusBar.addPermanentWidget(self.mTaskManagerButton, 10, QgsStatusBar.AnchorLeft) self.mTaskManagerButton.clicked.connect(lambda *args: self.ui.dockTaskManager.raise_()) - mvd = self.ui.dockMapViews - dts = self.ui.dockTimeSeries - mw = self.ui.mMapWidget - self.mMapLayerStore = self.mapWidget().mMapLayerStore + import eotimeseriesviewer.utils eotimeseriesviewer.utils.MAP_LAYER_STORES.insert(0, self.mapLayerStore()) from eotimeseriesviewer.timeseries import TimeSeriesDock from eotimeseriesviewer.mapvisualization import MapViewDock, MapWidget + + mvd: MapViewDock = self.ui.dockMapViews + dts = self.ui.dockTimeSeries + mw: MapWidget = self.ui.mMapWidget + + + assert isinstance(mvd, MapViewDock) assert isinstance(mw, MapWidget) assert isinstance(dts, TimeSeriesDock) @@ -515,7 +519,9 @@ class EOTimeSeriesViewer(QgisInterface, QObject): mw.sigSpatialExtentChanged.connect(self.timeSeries().setCurrentSpatialExtent) mw.sigVisibleDatesChanged.connect(self.timeSeries().setVisibleDates) mw.sigMapViewAdded.connect(self.onMapViewAdded) - mw.sigCurrentLocationChanged.connect(self.setCurrentLocation) + mw.sigCurrentLocationChanged.connect( + lambda crs, pt, canvas=mw: self.setCurrentLocation(SpatialPoint(crs, pt), + mapCanvas=mw.currentMapCanvas())) mw.sigCurrentLayerChanged.connect(self.updateCurrentLayerActions) self.ui.optionSyncMapCenter.toggled.connect(self.mapWidget().setSyncWithQGISMapCanvas) @@ -631,14 +637,15 @@ class EOTimeSeriesViewer(QgisInterface, QObject): from eotimeseriesviewer import DOCUMENTATION, SpectralLibrary, SpectralLibraryPanel, SpectralLibraryWidget self.ui.actionShowOnlineHelp.triggered.connect(lambda: webbrowser.open(DOCUMENTATION)) - SLW = self.ui.dockSpectralLibrary.spectralLibraryWidget() + SLW: SpectralLibraryWidget = self.ui.dockSpectralLibrary.spectralLibraryWidget() assert isinstance(SLW, SpectralLibraryWidget) - - SLW.setMapInteraction(True) - SLW.setCurrentProfilesMode(SpectralLibraryWidget.CurrentProfilesMode.automatically) - SLW.sigMapExtentRequested.connect(self.setSpatialExtent) - SLW.sigMapCenterRequested.connect(self.setSpatialCenter) - + 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) # add time-specific fields sl = self.spectralLibrary() @@ -1202,7 +1209,8 @@ class EOTimeSeriesViewer(QgisInterface, QObject): self.mCurrentMapLocation = spatialPoint if isinstance(mapCanvas, QgsMapCanvas): - self.sigCurrentLocationChanged[SpatialPoint, QgsMapCanvas].emit(self.mCurrentMapLocation, mapCanvas) + self.sigCurrentLocationChanged[QgsCoordinateReferenceSystem, QgsPointXY, QgsMapCanvas].emit( + self.mCurrentMapLocation.crs(), self.mCurrentMapLocation, mapCanvas) if bCLV: self.loadCursorLocationValueInfo(spatialPoint, mapCanvas) @@ -1216,7 +1224,9 @@ class EOTimeSeriesViewer(QgisInterface, QObject): if bTP: self.loadCurrentTemporalProfile(spatialPoint) - self.sigCurrentLocationChanged[SpatialPoint].emit(self.mCurrentMapLocation) + self.sigCurrentLocationChanged[QgsCoordinateReferenceSystem, QgsPointXY].emit( + self.mCurrentMapLocation.crs(), + self.mCurrentMapLocation) @pyqtSlot(SpatialPoint, QgsMapCanvas) def loadCursorLocationValueInfo(self, spatialPoint: SpatialPoint, mapCanvas: QgsMapCanvas): diff --git a/eotimeseriesviewer/mapcanvas.py b/eotimeseriesviewer/mapcanvas.py index 5085cd1070201f63401caa06ca3cda44d46ca629..0f6ae816bea01a4f3a6d91285dea9c881658ce82 100644 --- a/eotimeseriesviewer/mapcanvas.py +++ b/eotimeseriesviewer/mapcanvas.py @@ -19,27 +19,18 @@ ***************************************************************************/ """ # noinspection PyPep8Naming -KEY_LAST_CLICKED = 'LAST_CLICKED' - import time - -import eotimeseriesviewer.settings +import sys +import typing +import os +import re +import enum import qgis.utils -from .externals.qps.classification.classificationscheme import ClassificationScheme, ClassInfo -from .externals.qps.crosshair.crosshair import CrosshairDialog, CrosshairStyle, CrosshairMapCanvasItem -from .externals.qps.layerproperties import showLayerPropertiesDialog -from .externals.qps.maptools import * -from .externals.qps.utils import * -from .labeling import quickLabelLayers, setQuickTSDLabelsForRegisteredLayers -from .timeseries import TimeSeriesDate, TimeSeriesSource, SensorProxyLayer - from qgis.PyQt.QtGui import QIcon, QContextMenuEvent, QMouseEvent, QPainter, QFont, QColor from qgis.PyQt.QtWidgets import QApplication, QDialog, QMenu, QFileDialog, QSizePolicy, QStyle, QStyleOptionProgressBar from qgis.PyQt.QtCore import QSize, QDate, QDateTime, QDir, QFile, QMimeData, pyqtSignal, Qt, \ QPoint, QObject, QRectF, QPointF, QRect, QTimer -from qgis.core import * -from qgis.gui import * from qgis.core import QgsMapLayer, QgsRasterLayer, QgsVectorLayer, QgsContrastEnhancement, \ QgsDateTimeRange, QgsProject, QgsTextRenderer, QgsApplication, QgsCoordinateReferenceSystem, \ QgsMapToPixel, QgsRenderContext, QgsMapSettings, QgsRasterRenderer, \ @@ -47,7 +38,7 @@ from qgis.core import QgsMapLayer, QgsRasterLayer, QgsVectorLayer, QgsContrastEn QgsSingleBandPseudoColorRenderer, QgsWkbTypes, QgsRasterLayerTemporalProperties, QgsRasterDataProvider, \ QgsTextFormat, QgsMapLayerStore, QgsMultiBandColorRenderer, QgsSingleBandGrayRenderer, QgsField, \ QgsRectangle, QgsPolygon, QgsMultiBandColorRenderer, QgsRectangle, QgsSingleBandGrayRenderer, \ - QgsLayerTreeGroup, QgsUnitTypes + QgsLayerTreeGroup, QgsUnitTypes, QgsMimeDataUtils from qgis.gui import QgsMapCanvas, QgisInterface, QgsFloatingWidget, QgsUserInputWidget, \ QgsAdvancedDigitizingDockWidget, QgsMapCanvasItem, \ @@ -55,6 +46,20 @@ from qgis.gui import QgsMapCanvas, QgisInterface, QgsFloatingWidget, QgsUserInpu QgsGeometryRubberBand +from .externals.qps.classification.classificationscheme import ClassificationScheme, ClassInfo +from .externals.qps.crosshair.crosshair import CrosshairDialog, CrosshairStyle, CrosshairMapCanvasItem +from .externals.qps.layerproperties import showLayerPropertiesDialog +from .externals.qps.maptools import QgsMapToolSelectionHandler, \ + CursorLocationMapTool, QgsMapToolAddFeature, \ + SpectralProfileMapTool, TemporalProfileMapTool, MapToolCenter, PixelScaleExtentMapTool, FullExtentMapTool, QgsMapToolSelect +from .externals.qps.utils import SpatialExtent, SpatialPoint +from .labeling import quickLabelLayers, setQuickTSDLabelsForRegisteredLayers +from .timeseries import TimeSeriesDate, TimeSeriesSource, SensorProxyLayer +import eotimeseriesviewer.settings + +KEY_LAST_CLICKED = 'LAST_CLICKED' + + def toQgsMimeDataUtilsUri(mapLayer: QgsMapLayer): uri = QgsMimeDataUtils.Uri() uri.name = mapLayer.name() @@ -1360,7 +1365,7 @@ class MapCanvas(QgsMapCanvas): path = filenameFromString('{}.{}'.format(self.mTSD.date(), self.mMapView.title())) else: path = 'mapcanvas' - path = jp(lastDir, '{}.{}'.format(path, fileType.lower())) + path = os.path.join(lastDir, '{}.{}'.format(path, fileType.lower())) path, _ = QFileDialog.getSaveFileName(self, 'Save map as {}'.format(fileType), path) if len(path) > 0: self.saveAsImage(path, None, fileType) diff --git a/eotimeseriesviewer/mapviewscrollarea.py b/eotimeseriesviewer/mapviewscrollarea.py index ce155f79a089655ca5a335b9621ec0e40e857261..7f0248096d49d0ad4a114ba14c0a52c9c6fadde9 100644 --- a/eotimeseriesviewer/mapviewscrollarea.py +++ b/eotimeseriesviewer/mapviewscrollarea.py @@ -19,35 +19,22 @@ # noinspection PyPep8Naming from qgis.PyQt.QtCore import * -from qgis.PyQt.QtGui import * from qgis.PyQt.QtWidgets import * class MapViewScrollArea(QScrollArea): - #sigResized = pyqtSignal() - def __init__(self, *args, **kwds): super(MapViewScrollArea, self).__init__(*args, **kwds) self.horizontalScrollBar().setTracking(False) self.verticalScrollBar().setTracking(False) - #def resizeEvent(self, event): - #super(MapViewScrollArea, self).resizeEvent(event) - #self.sigResized.emit() - - def distanceToCenter(self, widget:QWidget) -> int: + def distanceToCenter(self, widget: QWidget) -> int: # self.visibleRegion().boundingRect().isValid() halfSize = widget.size() * 0.5 centerInParent = widget.mapToParent(QPoint(halfSize.width(), halfSize.height())) r = self.viewport().rect() - centerViewPort = QPoint(int(r.x() + r.width() *0.5 ), int(r.y()+r.height()*0.5)) + centerViewPort = QPoint(int(r.x() + r.width() * 0.5), int(r.y() + r.height() * 0.5)) diff = centerInParent - centerViewPort return diff.manhattanLength() - - #def sizeHint(self): - # parent = self.parent() - # hint = super(MapViewScrollArea, self).sizeHint() - - # return hint diff --git a/eotimeseriesviewer/mapvisualization.py b/eotimeseriesviewer/mapvisualization.py index 58ef68d7563673e30baccf5b5acc9e63513c0c9e..159d97489483432561065b484f7e0d4d50ecfc0d 100644 --- a/eotimeseriesviewer/mapvisualization.py +++ b/eotimeseriesviewer/mapvisualization.py @@ -20,43 +20,34 @@ ***************************************************************************/ """ -import os +import enum import sys -import re -import fnmatch -import collections -import copy import traceback -import bisect -import json -from eotimeseriesviewer import DIR_UI -from qgis.core import * -from qgis.core import QgsContrastEnhancement, QgsRasterShader, QgsColorRampShader, QgsProject, \ - QgsCoordinateReferenceSystem, QgsVector, QgsTextFormat, \ +import typing + +import numpy as np + +import qgis.utils +from qgis.PyQt.QtCore import Qt, QSize, pyqtSignal, QModelIndex, QTimer, QAbstractListModel +from qgis.PyQt.QtGui import QColor, QIcon, QGuiApplication, QMouseEvent +from qgis.PyQt.QtWidgets import QWidget, QLayoutItem, QFrame, QLabel, QGridLayout, QSlider, QMenu, QToolBox, QDialog +from qgis.PyQt.QtXml import QDomDocument, QDomNode, QDomElement +from qgis.core import QgsCoordinateReferenceSystem, QgsVector, QgsTextFormat, \ QgsRectangle, QgsRasterRenderer, QgsMapLayerStore, QgsMapLayerStyle, \ QgsLayerTreeModel, QgsLayerTreeGroup, \ QgsLayerTree, QgsLayerTreeLayer, \ - QgsRasterLayer, QgsVectorLayer, QgsMapLayer, QgsMapLayerProxyModel, QgsColorRamp, QgsSingleBandPseudoColorRenderer - -from qgis.gui import * -from qgis.gui import QgsDockWidget, QgsMapCanvas, QgsMapTool, QgsCollapsibleGroupBox, QgsLayerTreeView, \ + QgsRasterLayer, QgsVectorLayer, QgsMapLayer, QgsMapLayerProxyModel, QgsPointXY, QgsReadWriteContext +from qgis.gui import QgsDockWidget, QgsMapCanvas, QgsLayerTreeView, \ QgisInterface, QgsLayerTreeViewMenuProvider, QgsLayerTreeMapCanvasBridge, \ QgsProjectionSelectionWidget, QgsMessageBar -from qgis.PyQt.QtXml import * -from qgis.PyQt.QtCore import * -from qgis.PyQt.QtGui import * -import numpy as np -from .utils import * -from . import Option, OptionListModel -from .timeseries import SensorInstrument, TimeSeriesDate, TimeSeries, SensorProxyLayer -from .utils import loadUi -from .mapviewscrollarea import MapViewScrollArea -from .mapcanvas import MapCanvas, MapTools, MapCanvasInfoItem, MapCanvasMapTools, KEY_LAST_CLICKED -from eotimeseriesviewer import debugLog from .externals.qps.crosshair.crosshair import getCrosshairStyle, CrosshairStyle, CrosshairMapCanvasItem -from .externals.qps.layerproperties import showLayerPropertiesDialog -from .externals.qps.maptools import * - +from .externals.qps.layerproperties import VectorLayerTools +from .externals.qps.maptools import MapTools +from .mapcanvas import MapCanvas, MapCanvasInfoItem, KEY_LAST_CLICKED +from .timeseries import SensorInstrument, TimeSeriesDate, TimeSeries, SensorProxyLayer +from .utils import loadUi, SpatialPoint, SpatialExtent, datetime64 +from eotimeseriesviewer import DIR_UI +from eotimeseriesviewer.main import fixMenuButtons KEY_LOCKED_LAYER = 'eotsv/locked' KEY_SENSOR_GROUP = 'eotsv/sensorgroup' KEY_SENSOR_LAYER = 'eotsv/sensorlayer' @@ -999,7 +990,7 @@ class MapWidget(QFrame): sigCurrentCanvasChanged = pyqtSignal(MapCanvas) sigCurrentMapViewChanged = pyqtSignal(MapView) sigCurrentDateChanged = pyqtSignal(TimeSeriesDate) - sigCurrentLocationChanged = pyqtSignal(SpatialPoint, MapCanvas) + sigCurrentLocationChanged = pyqtSignal(QgsCoordinateReferenceSystem, QgsPointXY) sigVisibleDatesChanged = pyqtSignal(list) sigViewModeChanged = pyqtSignal(ViewMode) @@ -1350,7 +1341,6 @@ class MapWidget(QFrame): self.mTimeSeries.sigTimeSeriesDatesAdded.disconnect(self._updateSliderRange) self.mTimeSeries.sigTimeSeriesDatesRemoved.disconnect(self._updateSliderRange) - self.mTimeSeries = ts if isinstance(self.mTimeSeries, TimeSeries): self.mTimeSeries.sigVisibilityChanged.connect(self._updateCanvasDates) @@ -1728,7 +1718,7 @@ class MapWidget(QFrame): # mapCanvas.sigDestinationCrsChanged.connect(self.setCrs) mapCanvas.sigCrosshairPositionChanged.connect(self.onCrosshairPositionChanged) mapCanvas.sigCanvasClicked.connect(self.onCanvasClicked) - mapCanvas.mapTools().mtCursorLocation.sigLocationRequest[SpatialPoint, QgsMapCanvas].connect( + mapCanvas.mapTools().mtCursorLocation.sigLocationRequest[QgsCoordinateReferenceSystem, QgsPointXY].connect( self.sigCurrentLocationChanged) def _disconnectCanvasSignals(self, mapCanvas: MapCanvas): @@ -2209,8 +2199,6 @@ class MapViewDock(QgsDockWidget): return QSize(self.spinBoxMapSizeX.value(), self.spinBoxMapSizeY.value()) - def dummySlot(self): - s = "" def onMapViewsRemoved(self, mapViews): diff --git a/eotimeseriesviewer/mimedata.py b/eotimeseriesviewer/mimedata.py index 5581106d7c067ebbb95913085603ae523c640587..cacf7132f03dc2ba20048137ee97e67648892b86 100644 --- a/eotimeseriesviewer/mimedata.py +++ b/eotimeseriesviewer/mimedata.py @@ -1,8 +1,7 @@ - - -from qgis.core import * from qgis.PyQt.QtCore import * from qgis.PyQt.QtXml import * +from qgis.core import QgsReadWriteContext, QgsProject, QgsMapLayer, QgsRasterLayer, QgsVectorLayer +from qgis.gui import QgsLayerTreeLayer, QgsLayerTreeGroup, QgsLayerTree import re from qgis.gui import * @@ -60,8 +59,6 @@ def fromLayerList(mapLayers): return mimeData - - def toLayerList(mimeData): """ Extracts a layer-tree-group from a QMimeData @@ -77,14 +74,13 @@ def toLayerList(mimeData): xml = doc.toString() node = doc.firstChildElement(MDF_LAYERTREEMODELDATA_XML) context = QgsReadWriteContext() - #context.setPathResolver(QgsProject.instance().pathResolver()) + # context.setPathResolver(QgsProject.instance().pathResolver()) layerTree = QgsLayerTree.readXml(node, context) lt = QgsLayerTreeGroup.readXml(node, context) - #layerTree.resolveReferences(QgsProject.instance(), True) + # layerTree.resolveReferences(QgsProject.instance(), True) registeredLayers = QgsProject.instance().mapLayers() - - attributesLUT= {} + attributesLUT = {} childs = node.childNodes() for i in range(childs.count()): @@ -118,7 +114,7 @@ def toLayerList(mimeData): if isinstance(mapLayer, QgsMapLayer): newMapLayers.append(mapLayer) elif MDF_URILIST in mimeData.formats(): - pass + pass else: s = "" @@ -139,6 +135,7 @@ def textToByteArray(text): data.append(text) return data + def textFromByteArray(data): """ Decodes a QByteArray into a str @@ -148,4 +145,3 @@ def textFromByteArray(data): assert isinstance(data, QByteArray) s = data.data().decode() return s - diff --git a/eotimeseriesviewer/profilevisualization.py b/eotimeseriesviewer/profilevisualization.py index ecf1eda2e2c93ccd0fed66423f61a4e1a9baa610..691706d8a4683475b64103549791598e76371b3f 100644 --- a/eotimeseriesviewer/profilevisualization.py +++ b/eotimeseriesviewer/profilevisualization.py @@ -20,28 +20,35 @@ """ # noinspection PyPep8Naming -import os, sys, pickle, datetime +import sys +import typing +import re from collections import OrderedDict -from qgis.gui import * -from qgis.core import * from qgis.PyQt.QtCore import * from qgis.PyQt.QtXml import * from qgis.PyQt.QtGui import * +from qgis.PyQt.QtWidgets import * +from qgis.core import QgsMapLayer, QgsRasterLayer, QgsVectorLayer, QgsPoint, QgsPointXY, \ + QgsAttributeTableConfig, QgsMapLayerProxyModel, QgsFeature, QgsCoordinateReferenceSystem +from qgis.gui import QgsFieldExpressionWidget, QgsFeatureListComboBox, QgsDockWidget +import numpy as np + from eotimeseriesviewer import DIR_UI -from .timeseries import * +from .timeseries import TimeSeries, TimeSeriesDate, SensorInstrument from .utils import SpatialExtent, SpatialPoint, px2geo, loadUi, nextColor from .externals.qps.plotstyling.plotstyling import PlotStyle, PlotStyleButton, PlotStyleDialog from .externals.pyqtgraph import ScatterPlotItem, SpotItem, GraphicsScene from .externals.qps.externals.pyqtgraph.GraphicsScene.mouseEvents import MouseClickEvent, MouseDragEvent from .externals import pyqtgraph as pg +from .externals.pyqtgraph import fn from .externals.qps.layerproperties import AttributeTableWidget from .externals.qps.vectorlayertools import VectorLayerTools from .sensorvisualization import SensorListModel -from .temporalprofiles import * - -import numpy as np +from .temporalprofiles import TemporalProfile, num2date, date2num, TemporalProfileLayer, \ + LABEL_EXPRESSION_2D, LABEL_TIME, sensorExampleQgsFeature, FN_ID, bandKey2bandIndex, bandIndex2bandKey, \ + dateDOY, rxBandKey, rxBandKeyExact DEBUG = False OPENGL_AVAILABLE = False @@ -49,13 +56,13 @@ ENABLE_OPENGL = False try: import OpenGL + OPENGL_AVAILABLE = True except Exception as ex: print('unable to import OpenGL based packages:\n{}'.format(ex)) - def getTextColorWithContrast(c): assert isinstance(c, QColor) if c.lightness() < 0.5: @@ -63,6 +70,7 @@ def getTextColorWithContrast(c): else: return QColor('black') + def selectedModelIndices(tableView): assert isinstance(tableView, QTableView) result = {} @@ -77,7 +85,6 @@ def selectedModelIndices(tableView): return result.values() - class _SensorPoints(pg.PlotDataItem): def __init__(self, *args, **kwds): super(_SensorPoints, self).__init__(*args, **kwds) @@ -86,12 +93,11 @@ class _SensorPoints(pg.PlotDataItem): self.menu = None def boundingRect(self): - return super(_SensorPoints,self).boundingRect() + return super(_SensorPoints, self).boundingRect() def paint(self, p, *args): super(_SensorPoints, self).paint(p, *args) - # On right-click, raise the context menu def mouseClickEvent(self, ev): if ev.button() == QtCore.Qt.RightButton: @@ -128,7 +134,7 @@ class _SensorPoints(pg.PlotDataItem): alpha = QWidgetAction(self.menu) alphaSlider = QSlider() - alphaSlider.setOrientation(QtCore.Qt.Horizontal) + alphaSlider.setOrientation(Qt.Horizontal) alphaSlider.setMaximum(255) alphaSlider.setValue(255) alphaSlider.valueChanged.connect(self.setAlpha) @@ -140,7 +146,6 @@ class _SensorPoints(pg.PlotDataItem): class TemporalProfilePlotStyle(PlotStyle): - sigStyleUpdated = pyqtSignal() sigDataUpdated = pyqtSignal() @@ -167,7 +172,8 @@ class TemporalProfilePlotStyle(PlotStyle): """ Returns True if this style has all information to get plotted """ - return self.isVisible() and isinstance(self.temporalProfile(), TemporalProfile) and isinstance(self.sensor(), SensorInstrument) + return self.isVisible() and isinstance(self.temporalProfile(), TemporalProfile) and isinstance(self.sensor(), + SensorInstrument) def createPlotItem(self): raise NotImplementedError() @@ -198,7 +204,7 @@ class TemporalProfilePlotStyle(PlotStyle): def expression(self) -> str: return self.mExpression - + def expressionBandIndices(self) -> typing.List[int]: return [bandKey2bandIndex(k) for k in self.expressionBandKeys()] @@ -213,7 +219,7 @@ class TemporalProfilePlotStyle(PlotStyle): def __getstate__(self): result = super(TemporalProfilePlotStyle, self).__getstate__() - #remove + # remove del result['mTP'] del result['mSensor'] @@ -236,22 +242,23 @@ class TemporalProfilePlotStyle(PlotStyle): self.setSensor(plotStyle.sensor()) self.setTemporalProfile(plotStyle.temporalProfile()) + class TemporalProfilePlotDataItem(pg.PlotDataItem): def __init__(self, plotStyle: TemporalProfilePlotStyle, parent=None): assert isinstance(plotStyle, TemporalProfilePlotStyle) super(TemporalProfilePlotDataItem, self).__init__([], [], parent=parent) - self.menu = None - #self.setFlags(QGraphicsItem.ItemIsSelectable) - self.mPlotStyle : TemporalProfilePlotStyle = plotStyle + self.menu: QMenu = None + # self.setFlags(QGraphicsItem.ItemIsSelectable) + self.mPlotStyle: TemporalProfilePlotStyle = plotStyle self.setAcceptedMouseButtons(Qt.LeftButton | Qt.RightButton) self.mPlotStyle.sigUpdated.connect(self.updateDataAndStyle) self.updateDataAndStyle() # On right-click, raise the context menu def mouseClickEvent(self, ev): - if ev.button() == QtCore.Qt.RightButton: + if ev.button() == Qt.RightButton: if self.raiseContextMenu(ev): ev.accept() @@ -263,7 +270,7 @@ class TemporalProfilePlotDataItem(pg.PlotDataItem): menu = self.scene().addParentContextMenus(self, menu, ev) pos = ev.screenPos() - menu.popup(QtCore.QPoint(pos.x(), pos.y())) + menu.popup(QPoint(pos.x(), pos.y())) return True # This method will be called when this item's _children_ want to raise @@ -285,7 +292,7 @@ class TemporalProfilePlotDataItem(pg.PlotDataItem): alpha = QWidgetAction(self.menu) alphaSlider = QSlider() - alphaSlider.setOrientation(QtCore.Qt.Horizontal) + alphaSlider.setOrientation(Qt.Horizontal) alphaSlider.setMaximum(255) alphaSlider.setValue(255) alphaSlider.valueChanged.connect(self.setAlpha) @@ -358,6 +365,7 @@ class TemporalProfilePlotDataItem(pg.PlotDataItem): pen.setWidth(width) self.setPen(pen) + class PlotSettingsModel(QAbstractTableModel): def __init__(self, temporalProfileLayer: TemporalProfileLayer, parent=None, *args): @@ -385,11 +393,11 @@ class PlotSettingsModel(QAbstractTableModel): def temporalProfileLayer(self) -> TemporalProfileLayer: return self.mTemporalProfileLayer - def onTemporalProfilesAdded(self, temporalProfiles:typing.List[TemporalProfile]): + def onTemporalProfilesAdded(self, temporalProfiles: typing.List[TemporalProfile]): if len(temporalProfiles) > 0: self.setCurrentProfile(temporalProfiles[-1]) - def onTemporalProfilesDeleted(self, temporalProfiles:typing.List[TemporalProfile]): + def onTemporalProfilesDeleted(self, temporalProfiles: typing.List[TemporalProfile]): # remote deleted temporal profiles from plotstyles col = self.columnNames.index(self.cnTemporalProfile) rowMin = rowMax = None @@ -408,7 +416,7 @@ class PlotSettingsModel(QAbstractTableModel): [Qt.EditRole, Qt.DisplayRole] ) - def onTemporalProfilesUpdated(self, temporalProfiles:typing.List[TemporalProfile]): + def onTemporalProfilesUpdated(self, temporalProfiles: typing.List[TemporalProfile]): col = self.columnNames.index(self.cnTemporalProfile) @@ -481,12 +489,12 @@ class PlotSettingsModel(QAbstractTableModel): i = len(self.mPlotSettings) if len(plotStyles) > 0: - self.beginInsertRows(QModelIndex(), i, i + len(plotStyles)-1) + self.beginInsertRows(QModelIndex(), i, i + len(plotStyles) - 1) for j, plotStyle in enumerate(plotStyles): assert isinstance(plotStyle, TemporalProfilePlotStyle) - #plotStyle.sigExpressionUpdated.connect(lambda *args, s = plotStyle: self.onStyleUpdated(s)) - #plotStyle.sigExpressionUpdated.connect(self.sigReloadMissingBandValuesRequest.emit) - self.mPlotSettings.insert(i+j, plotStyle) + # plotStyle.sigExpressionUpdated.connect(lambda *args, s = plotStyle: self.onStyleUpdated(s)) + # plotStyle.sigExpressionUpdated.connect(self.sigReloadMissingBandValuesRequest.emit) + self.mPlotSettings.insert(i + j, plotStyle) self.endInsertRows() self.checkForRequiredDataUpdates(plotStyles) @@ -509,7 +517,7 @@ class PlotSettingsModel(QAbstractTableModel): plotStyle.setTemporalProfile(temporalProfile) if len(self) > 0: - lastStyle = self[0] #top style in list is the most-recent + lastStyle = self[0] # top style in list is the most-recent assert isinstance(lastStyle, TemporalProfilePlotStyle) markerColor = nextColor(lastStyle.markerBrush.color()) plotStyle.markerBrush.setColor(markerColor) @@ -529,17 +537,16 @@ class PlotSettingsModel(QAbstractTableModel): assert isinstance(plotStyle, PlotStyle) if plotStyle in self.mPlotSettings: idx = self.plotStyle2idx(plotStyle) - self.beginRemoveRows(QModelIndex(), idx.row(),idx.row()) + self.beginRemoveRows(QModelIndex(), idx.row(), idx.row()) self.mPlotSettings.remove(plotStyle) self.endRemoveRows() - def rowCount(self, parent = QModelIndex()): + def rowCount(self, parent=QModelIndex()): return len(self.mPlotSettings) + def removeRows(self, row, count, parent=QModelIndex()): - def removeRows(self, row, count , parent = QModelIndex()): - - self.beginRemoveRows(parent, row, row + count-1) + self.beginRemoveRows(parent, row, row + count - 1) toRemove = self.mPlotSettings[row:row + count] @@ -563,7 +570,7 @@ class PlotSettingsModel(QAbstractTableModel): return self.mPlotSettings[index.row()] return None - def columnCount(self, parent = QModelIndex()): + def columnCount(self, parent=QModelIndex()): return len(self.columnNames) def index(self, row: int, column: int, parent: QModelIndex = None) -> QModelIndex: @@ -616,7 +623,7 @@ class PlotSettingsModel(QAbstractTableModel): columnName = self.columnNames[index.column()] result = False - plotStyle : TemporalProfilePlotStyle = index.data(Qt.UserRole) + plotStyle: TemporalProfilePlotStyle = index.data(Qt.UserRole) if isinstance(plotStyle, TemporalProfilePlotStyle): if role == Qt.CheckStateRole: @@ -656,7 +663,7 @@ class PlotSettingsModel(QAbstractTableModel): def restorePlotSettings(self, sensor, index='DEFAULT'): return None - def checkForRequiredDataUpdates(self, profileStyles:typing.List[TemporalProfilePlotStyle]): + def checkForRequiredDataUpdates(self, profileStyles: typing.List[TemporalProfilePlotStyle]): if not isinstance(profileStyles, list): profileStyles = [profileStyles] @@ -685,10 +692,11 @@ class PlotSettingsModel(QAbstractTableModel): flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable if columnName in [self.cnTemporalProfile]: flags = flags | Qt.ItemIsUserCheckable - if columnName in [self.cnTemporalProfile, self.cnSensor, self.cnExpression, self.cnStyle]: #allow check state + if columnName in [self.cnTemporalProfile, self.cnSensor, self.cnExpression, + self.cnStyle]: # allow check state flags = flags | Qt.ItemIsEditable return flags - #return item.qt_flags(index.column()) + # return item.qt_flags(index.column()) return Qt.NoItemFlags def headerData(self, col, orientation, role): @@ -700,6 +708,7 @@ class PlotSettingsModel(QAbstractTableModel): return str(col) return None + class PlotSettingsTableView(QTableView): def __init__(self, *args, **kwds): @@ -710,10 +719,10 @@ class PlotSettingsTableView(QTableView): pal.setColor(QPalette.Inactive, QPalette.Highlight, cSelected) self.setPalette(pal) - def sensorHasWavelengths(self, sensor:SensorInstrument) -> bool: + def sensorHasWavelengths(self, sensor: SensorInstrument) -> bool: return isinstance(sensor, SensorInstrument) and \ - sensor.wl is not None and \ - len(sensor.wl) > 0 + sensor.wl is not None and \ + len(sensor.wl) > 0 def contextMenuEvent(self, event: QContextMenuEvent): """ @@ -723,8 +732,6 @@ class PlotSettingsTableView(QTableView): indices = self.selectionModel().selectedIndexes() - - if len(indices) > 0: refIndex = indices[0] assert isinstance(refIndex, QModelIndex) @@ -763,7 +770,7 @@ class PlotSettingsTableView(QTableView): a.setToolTip('Show values of red band (band closest to {} nm'.format(LUT_WAVELENGTH['NIR'])) a.setToolTip('Show values of Near Infrared (NIR) band') a.triggered.connect(lambda *args, exp='<NIR>': - self.onSetExpression(exp)) + self.onSetExpression(exp)) a = m.addAction('SWIR1 Band') a.setToolTip('Show values of SWIR 1 band (band closest to {} nm'.format(LUT_WAVELENGTH['SWIR1'])) @@ -789,12 +796,12 @@ class PlotSettingsTableView(QTableView): a = m.addAction('NBR') a.setToolTip('Calculate the Normalized Burn Ratio (NBR)') a.triggered.connect(lambda *args, exp='(<NIR>-<SWIR2>)/(<NIR>+<SWIR2>)': - self.onSetExpression(exp)) + self.onSetExpression(exp)) a = m.addAction('NBR 2') a.setToolTip('Calculate the Normalized Burn Ratio between two SWIR bands (NBR2)') a.triggered.connect(lambda *args, exp='(<SWIR1>-<SWIR2>)/(<SWIR1>+<SWIR2>)': - self.onSetExpression(exp)) + self.onSetExpression(exp)) menu.popup(QCursor.pos()) @@ -834,7 +841,6 @@ class PlotSettingsTableView(QTableView): if '<' not in expr2: self.model().setData(idx, expr2, Qt.EditRole) - def plotSettingsModel(self) -> PlotSettingsModel: return self.model().sourceModel() @@ -855,10 +861,12 @@ class PlotSettingsTableView(QTableView): idx2 = self.model().index(idx.row(), col) self.model().setData(idx2, newStyle, role=Qt.EditRole) + class PlotSettingsTableViewWidgetDelegate(QStyledItemDelegate): """ """ + def __init__(self, tableView, parent=None): assert isinstance(tableView, PlotSettingsTableView) super(PlotSettingsTableViewWidgetDelegate, self).__init__(parent=parent) @@ -877,7 +885,7 @@ class PlotSettingsTableViewWidgetDelegate(QStyledItemDelegate): self.mSensorListModel = SensorListModel(self.mTemporalProfileLayer.timeSeries()) return self.mSensorListModel - def paint(self, painter: QPainter, option: 'QStyleOptionViewItem', index: QtCore.QModelIndex): + def paint(self, painter: QPainter, option: 'QStyleOptionViewItem', index: QModelIndex): if index.column() == 2: style: TemporalProfilePlotStyle = index.data(Qt.UserRole) @@ -889,7 +897,7 @@ class PlotSettingsTableViewWidgetDelegate(QStyledItemDelegate): label = QLabel() label.setPixmap(px) painter.drawPixmap(option.rect, px) - #QApplication.style().drawControl(QStyle.CE_CustomBase, label, painter) + # QApplication.style().drawControl(QStyle.CE_CustomBase, label, painter) else: super(PlotSettingsTableViewWidgetDelegate, self).paint(painter, option, index) else: @@ -916,6 +924,7 @@ class PlotSettingsTableViewWidgetDelegate(QStyledItemDelegate): def columnName(self, index: QModelIndex) -> str: assert index.isValid() return index.model().headerData(index.column(), Qt.Horizontal, Qt.DisplayRole) + """ def sizeHint(self, options, index): s = super(ExpressionDelegate, self).sizeHint(options, index) @@ -926,6 +935,7 @@ class PlotSettingsTableViewWidgetDelegate(QStyledItemDelegate): s = QSize(x, s.height()) return self._preferedSize """ + def exampleLyr(self, sensor): # if isinstance(sensor, SensorInstrument): if sensor not in self.mSensorLayers.keys(): @@ -958,7 +968,7 @@ class PlotSettingsTableViewWidgetDelegate(QStyledItemDelegate): w = QgsFieldExpressionWidget(parent=parent) w.setExpressionDialogTitle('Values') w.setToolTip('Set an expression to specify the image band or calculate a spectral index.') - #w.fieldChanged[str, bool].connect(lambda n, b: self.checkData(index, w, w.expression())) + # w.fieldChanged[str, bool].connect(lambda n, b: self.checkData(index, w, w.expression())) w.setExpression(plotStyle.expression()) if isinstance(plotStyle.sensor(), SensorInstrument): @@ -968,7 +978,7 @@ class PlotSettingsTableViewWidgetDelegate(QStyledItemDelegate): w = PlotStyleButton(parent=parent) w.setPlotStyle(plotStyle) w.setToolTip('Set style.') - #w.sigPlotStyleChanged.connect(lambda ps: self.checkData(index, w, ps)) + # w.sigPlotStyleChanged.connect(lambda ps: self.checkData(index, w, ps)) elif cname == model.cnSensor: w = QComboBox(parent=parent) @@ -997,14 +1007,14 @@ class PlotSettingsTableViewWidgetDelegate(QStyledItemDelegate): self.commitData.emit(w) else: s = "" - #print(('Delegate commit failed',w.asExpression())) + # print(('Delegate commit failed',w.asExpression())) if isinstance(w, PlotStyleButton): self.commitData.emit(w) def setEditorData(self, editor, index): model = self.plotSettingsModel() - #index = self.sortFilterProxyModel().mapToSource(index) + # index = self.sortFilterProxyModel().mapToSource(index) w = None if index.isValid() and isinstance(model, PlotSettingsModel): style = index.data(Qt.UserRole) @@ -1037,9 +1047,9 @@ class PlotSettingsTableViewWidgetDelegate(QStyledItemDelegate): else: raise NotImplementedError() - def setModelData(self, w, model, index:QModelIndex): + def setModelData(self, w, model, index: QModelIndex): cname = self.columnName(index) - #model = self.plotSettingsModel() + # model = self.plotSettingsModel() srcModel = index.model().sourceModel() assert isinstance(srcModel, PlotSettingsModel) @@ -1091,7 +1101,7 @@ class DateTimePlotWidget(pg.PlotWidget): A plotwidget to visualize temporal profiles """ - def __init__(self, parent: QWidget=None): + def __init__(self, parent: QWidget = None): """ Constructor of the widget """ @@ -1149,17 +1159,16 @@ class DateTimePlotWidget(pg.PlotWidget): self.mPlotSettingsModel.dataChanged.connect(self.onPlotSettingsChanged) self.mUpdateTimer.start() - def onPlotSettingsChanged(self, idx0: QModelIndex, idxe: QModelIndex, roles:list): + def onPlotSettingsChanged(self, idx0: QModelIndex, idxe: QModelIndex, roles: list): if not isinstance(self.mPlotSettingsModel, PlotSettingsModel): return None row = idx0.row() while row <= idxe.row(): - style = self.mPlotSettingsModel.index(row, 0).data(Qt.UserRole) + style = self.mPlotSettingsModel.index(row, 0).data(Qt.UserRole) assert isinstance(style, TemporalProfilePlotStyle) self.mUpdatedProfileStyles.add(style) row += 1 - def setUpdateInterval(self, msec: int): """ Sets the update interval @@ -1227,7 +1236,7 @@ class DateTimePlotWidget(pg.PlotWidget): assert isinstance(pdi, TemporalProfilePlotDataItem) assert pdi in self.temporalProfilePlotDataItems() assert pdi.isVisible() - #assert len(pdi.xData) > 0 + # assert len(pdi.xData) > 0 assert len(pdi.xData) == len(pdi.yData) if len(toBeUpdated) > 0: @@ -1236,7 +1245,6 @@ class DateTimePlotWidget(pg.PlotWidget): assert isinstance(pdi, TemporalProfilePlotDataItem) pdi.updateDataAndStyle() - def resetViewBox(self): self.plotItem.getViewBox().autoRange() @@ -1530,7 +1538,6 @@ class DateTimeViewBox(pg.ViewBox): class ProfileViewDock(QgsDockWidget): - """ Signalizes to move to specific date of interest """ @@ -1574,14 +1581,13 @@ class ProfileViewDock(QgsDockWidget): self.mAttributeTableToolBar.insertSeparator(before) self.mPlotToolBar.setMovable(False) - #self.mPlotToolBar.addActions(self.mActions2D) - #self.mPlotToolBar.addSeparator() - #self.mPlotToolBar.addActions(self.mActionsTP) + # self.mPlotToolBar.addActions(self.mActions2D) + # self.mPlotToolBar.addSeparator() + # self.mPlotToolBar.addActions(self.mActionsTP) + # self.pagePixel.addToolBar(self.mTemporalProfilesToolBar) - #self.pagePixel.addToolBar(self.mTemporalProfilesToolBar) - - #self.mTemporalProfilesToolBar.setMovable(False) + # self.mTemporalProfilesToolBar.setMovable(False) config = QgsAttributeTableConfig() config.update(self.mTemporalProfileLayer.fields()) @@ -1607,7 +1613,7 @@ class ProfileViewDock(QgsDockWidget): self.delegateTableView2D = PlotSettingsTableViewWidgetDelegate(self.tableView2DProfiles) self.plot2D: DateTimePlotWidget = self.plotWidget2D - assert isinstance(self.plot2D, DateTimePlotWidget ) + assert isinstance(self.plot2D, DateTimePlotWidget) self.plot2D.setPlotSettingsModel(self.plotSettingsModel2D) self.plot2D.getViewBox().sigMoveToDate.connect(self.sigMoveToDate) self.plot2D.getViewBox().scene().sigMouseClicked.connect(self.onPointsClicked2D) @@ -1643,7 +1649,7 @@ class ProfileViewDock(QgsDockWidget): def vectorLayerTools(self) -> VectorLayerTools: return self.pagePixel.vectorLayerTools() - def setTimeSeries(self, timeSeries:TimeSeries): + def setTimeSeries(self, timeSeries: TimeSeries): self.temporalProfileLayer().setTimeSeries(timeSeries) def timeSeries(self) -> TimeSeries: @@ -1732,7 +1738,7 @@ class ProfileViewDock(QgsDockWidget): self.actionAddStyle2D.triggered.connect(self.createNewPlotStyle2D) self.actionRefresh2D.triggered.connect(self.updatePlot2D) self.actionRemoveStyle2D.triggered.connect( - lambda:self.removePlotStyles2D(self.selected2DPlotStyles())) + lambda: self.removePlotStyles2D(self.selected2DPlotStyles())) self.actionLoadTPFromOgr.triggered.connect(self.onLoadFromVector) self.actionLoadMissingValues.triggered.connect( lambda *args: self.mTemporalProfileLayer.loadMissingBandInfos()) @@ -1755,7 +1761,6 @@ class ProfileViewDock(QgsDockWidget): self.mTemporalProfileLayer.loadCoordinatesFromOgr(l.source()) break - def onToggleEditing(self, b): if self.mTemporalProfileLayer.isEditable(): @@ -1764,7 +1769,6 @@ class ProfileViewDock(QgsDockWidget): self.mTemporalProfileLayer.startEditing() self.onEditingToggled() - def onMoveToDate(self, date): dt = np.asarray([np.abs(tsd.date() - date) for tsd in self.timeSeries()]) i = np.argmin(dt) @@ -1798,9 +1802,8 @@ class ProfileViewDock(QgsDockWidget): if len(self.mTemporalProfileLayer) == 1 and len(self.plotSettingsModel2D) == 0: self.createNewPlotStyle2D() - @QtCore.pyqtSlot() + @pyqtSlot() def updatePlot2D(self): if isinstance(self.plotSettingsModel2D, PlotSettingsModel): self.plot2D.mUpdatedProfileStyles.update(self.plotSettingsModel2D[:]) self.plot2D.updateTemporalProfilePlotItems() - diff --git a/eotimeseriesviewer/sensorvisualization.py b/eotimeseriesviewer/sensorvisualization.py index e0d6f9a48eb168b4f778423881bec60f17e6b553..774c7c6461eed82dbbf284953a3770c5d3ac3de3 100644 --- a/eotimeseriesviewer/sensorvisualization.py +++ b/eotimeseriesviewer/sensorvisualization.py @@ -20,14 +20,15 @@ """ # noinspection PyPep8Naming -from eotimeseriesviewer import DIR_UI -from eotimeseriesviewer.timeseries import TimeSeries, SensorInstrument, TimeSeriesDate -from eotimeseriesviewer.utils import loadUi from qgis.PyQt.QtCore import * from qgis.PyQt.QtGui import * from qgis.PyQt.QtWidgets import * from qgis.gui import QgsDockWidget +from eotimeseriesviewer import DIR_UI +from eotimeseriesviewer.timeseries import TimeSeries, SensorInstrument, TimeSeriesDate +from eotimeseriesviewer.utils import loadUi + class SensorDockUI(QgsDockWidget): def __init__(self, parent=None): @@ -35,6 +36,8 @@ class SensorDockUI(QgsDockWidget): loadUi(DIR_UI / 'sensordock.ui', self) self.TS = None + self.mSensorModel: SensorTableModel = None + self.mSortedModel: QSortFilterProxyModel = QSortFilterProxyModel() def setTimeSeries(self, timeSeries): from eotimeseriesviewer.timeseries import TimeSeries @@ -42,11 +45,9 @@ class SensorDockUI(QgsDockWidget): assert isinstance(timeSeries, TimeSeries) self.TS = timeSeries self.mSensorModel = SensorTableModel(self.TS) - self.mSortedModel = QSortFilterProxyModel() self.mSortedModel.setSourceModel(self.mSensorModel) self.sensorView.setModel(self.mSortedModel) self.sensorView.horizontalHeader().setResizeMode(QHeaderView.ResizeToContents) - s = "" class SensorTableModel(QAbstractTableModel): diff --git a/eotimeseriesviewer/stackedbandinput.py b/eotimeseriesviewer/stackedbandinput.py index 4838c4263aa3f0ad53dd4c97728e60b8b1c28e4b..acd02883c41ac40d314781d69b8d64a014cb5560 100644 --- a/eotimeseriesviewer/stackedbandinput.py +++ b/eotimeseriesviewer/stackedbandinput.py @@ -19,15 +19,25 @@ * * *************************************************************************** """ - - -from .utils import * -from .virtualrasters import * -from .dateparser import * +import os +import copy +from osgeo import gdal +import numpy as np +from collections import OrderedDict +from xml.etree import ElementTree +from qgis.PyQt.QtCore import Qt, QModelIndex, QAbstractTableModel, QItemSelectionModel, QTimer +from qgis.PyQt.QtGui import QColor +from qgis.PyQt.QtWidgets import QHeaderView, QDialog, QDialogButtonBox, QFileDialog +from qgis.core import QgsRasterLayer, QgisInterface, QgsProviderRegistry, QgsProject +from qgis.gui import QgsFileWidget +import qgis.utils +from .utils import read_vsimem, loadUi +from .virtualrasters import VRTRaster, VRTRasterBand, VRTRasterInputSourceBand from eotimeseriesviewer import DIR_UI +from eotimeseriesviewer.dateparser import extractDateTimeGroup -def datesFromDataset(dataset:gdal.Dataset) -> list: +def datesFromDataset(dataset: gdal.Dataset) -> list: nb = dataset.RasterCount def checkDates(dateList): @@ -50,7 +60,7 @@ def datesFromDataset(dataset:gdal.Dataset) -> list: searchedKeysBand.append(re.compile('date$', re.I)) searchedKeysBand.append(re.compile('wavelength$', re.I)) - #1. Check Metadata + # 1. Check Metadata for domain in dataset.GetMetadataDomainList(): domainData = dataset.GetMetadata_Dict(domain) assert isinstance(domainData, dict) @@ -64,10 +74,9 @@ def datesFromDataset(dataset:gdal.Dataset) -> list: if checkDates(dateValues): return dateValues - # 2. Search in band metadata # 2.1. via GetDescription - bandDates = [extractDateTimeGroup(dataset.GetRasterBand(b+1).GetDescription()) for b in range(nb)] + bandDates = [extractDateTimeGroup(dataset.GetRasterBand(b + 1).GetDescription()) for b in range(nb)] bandDates = [b for b in bandDates if isinstance(b, np.datetime64)] if checkDates(bandDates): return bandDates @@ -75,7 +84,7 @@ def datesFromDataset(dataset:gdal.Dataset) -> list: # 2.2 via Band Metadata bandDates = [] for b in range(nb): - band = dataset.GetRasterBand(b+1) + band = dataset.GetRasterBand(b + 1) assert isinstance(band, gdal.Band) bandDate = None for domain in band.GetMetadataDomainList(): @@ -104,17 +113,17 @@ def datesFromDataset(dataset:gdal.Dataset) -> list: if checkDates(bandDates): return bandDates - return [] + class InputStackInfo(object): def __init__(self, dataset): if isinstance(dataset, str): - #test ENVI header first + # test ENVI header first basename = os.path.splitext(dataset)[0] ds = None - if os.path.isfile(basename+'.hdr'): + if os.path.isfile(basename + '.hdr'): ds = gdal.OpenEx(dataset, allowed_drivers=['ENVI']) if not isinstance(ds, gdal.Dataset): ds = gdal.Open(dataset) @@ -152,12 +161,11 @@ class InputStackInfo(object): self.nodatavalues = [] for b in range(self.nb): - band = dataset.GetRasterBand(b+1) + band = dataset.GetRasterBand(b + 1) assert isinstance(band, gdal.Band) self.bandnames.append(band.GetDescription()) self.nodatavalues.append(band.GetNoDataValue()) - self.mDates = datesFromDataset(dataset) def __len__(self): @@ -167,7 +175,6 @@ class InputStackInfo(object): """Returns a list of dates""" return self.mDates - def structure(self): return (self.ns, self.nl, self.nb, self.gt, self.wkt) @@ -183,21 +190,17 @@ class OutputVRTDescription(object): Descrbies an output VRT """ - def __init__(self, path:str, date:np.datetime64): + def __init__(self, path: str, date: np.datetime64): super(OutputVRTDescription, self).__init__() self.mPath = path self.mDate = date - - def setPath(self, path:str): + def setPath(self, path: str): self.mPath = path - class InputStackTableModel(QAbstractTableModel): - - def __init__(self, parent=None): super(InputStackTableModel, self).__init__(parent) @@ -211,7 +214,8 @@ class InputStackTableModel(QAbstractTableModel): self.cn_nb = 'nb' self.cn_name = 'Band Name' self.cn_wl = 'Wavelength' - self.mColumnNames = [self.cn_source, self.cn_dates, self.cn_name, self.cn_wl, self.cn_ns, self.cn_nl, self.cn_nb, self.cn_crs] + self.mColumnNames = [self.cn_source, self.cn_dates, self.cn_name, self.cn_wl, self.cn_ns, self.cn_nl, + self.cn_nb, self.cn_crs] self.mColumnTooltips = {} @@ -241,7 +245,7 @@ class InputStackTableModel(QAbstractTableModel): :return: [all dates], [dates in common] """ if len(self) == 0: - return [],[] + return [], [] datesTotal = set() datesInCommon = None for i, f in enumerate(self.mStackImages): @@ -261,11 +265,11 @@ class InputStackTableModel(QAbstractTableModel): if index.isValid(): columnName = self.columnName(index) flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable - if columnName in [self.cn_name, self.cn_wl]: #allow check state + if columnName in [self.cn_name, self.cn_wl]: # allow check state flags = flags | Qt.ItemIsEditable return flags - #return item.qt_flags(index.column()) + # return item.qt_flags(index.column()) return None def headerData(self, col, orientation, role): @@ -303,15 +307,15 @@ class InputStackTableModel(QAbstractTableModel): infos = [InputStackInfo(p) for p in paths] if len(infos) > 0: - self.beginInsertRows(QModelIndex(), i, i+len(infos)-1) + self.beginInsertRows(QModelIndex(), i, i + len(infos) - 1) for j, info in enumerate(infos): assert isinstance(info, InputStackInfo) if len(info.outputBandName) == 0: - info.outputBandName = 'Band {}'.format(i+j+1) - self.mStackImages.insert(i+j, info) + info.outputBandName = 'Band {}'.format(i + j + 1) + self.mStackImages.insert(i + j, info) self.endInsertRows() - def removeSources(self, stackInfos:list): + def removeSources(self, stackInfos: list): for stackInfo in stackInfos: assert stackInfo in self.mStackImages @@ -332,7 +336,7 @@ class InputStackTableModel(QAbstractTableModel): ref = self.mStackImages[0] assert isinstance(ref, InputStackInfo) - #all input stacks need to have the same characteristic + # all input stacks need to have the same characteristic for stackInfo in self.mStackImages[1:]: assert isinstance(stackInfo, InputStackInfo) if not ref.dates() == stackInfo.dates(): @@ -341,13 +345,12 @@ class InputStackTableModel(QAbstractTableModel): return False return True - - def index2info(self, index:QModelIndex) -> InputStackInfo: + def index2info(self, index: QModelIndex) -> InputStackInfo: return self.mStackImages[index.row()] - def info2index(self, info:InputStackInfo) -> QModelIndex: + def info2index(self, info: InputStackInfo) -> QModelIndex: r = self.mStackImages.index(info) - return self.createIndex(r,0, info) + return self.createIndex(r, 0, info) def data(self, index: QModelIndex, role: int): if not index.isValid(): @@ -372,7 +375,6 @@ class InputStackTableModel(QAbstractTableModel): dates = dates[0:10] + ['...'] return '\n'.join([str(d) for d in dates]) - if cname == self.cn_ns: return info.ns if cname == self.cn_nl: @@ -426,12 +428,12 @@ class InputStackTableModel(QAbstractTableModel): self.dataChanged.emit(index, index) return changed + class OutputImageModel(QAbstractTableModel): def __init__(self, parent=None): super(OutputImageModel, self).__init__(parent) - self.cn_uri = 'Path' self.cn_date = 'Date' self.mOutputImages = [] @@ -446,8 +448,6 @@ class OutputImageModel(QAbstractTableModel): self.mOutputDir = '/vsimem/' self.mOutputPrefix = 'date' - - def headerData(self, col, orientation, role): if Qt is None: return None @@ -461,7 +461,7 @@ class OutputImageModel(QAbstractTableModel): return col return None - def createVRTUri(self, date:np.datetime64): + def createVRTUri(self, date: np.datetime64): path = os.path.join(self.mOutputDir, self.mOutputPrefix) path = '{}{}.vrt'.format(path, date) @@ -473,7 +473,7 @@ class OutputImageModel(QAbstractTableModel): self.mOutputImages = [] self.endRemoveRows() - def setMultiStackSources(self, listOfInputStacks:list, dates:list): + def setMultiStackSources(self, listOfInputStacks: list, dates: list): self.clearOutputs() @@ -490,13 +490,13 @@ class OutputImageModel(QAbstractTableModel): self.masterVRT_DateLookup.clear() self.masterVRT_InputStacks = listOfInputStacks self.masterVRT_SourceBandTemplates.clear() - #dates = set() - #for s in listOfInputStacks: + # dates = set() + # for s in listOfInputStacks: # for d in s.dates(): # dates.add(d) - #dates = sorted(list(dates)) + # dates = sorted(list(dates)) - #create a LUT to get the stack indices for a related date (not each stack might contain a band for each date) + # create a LUT to get the stack indices for a related date (not each stack might contain a band for each date) for stackIndex, s in enumerate(listOfInputStacks): for bandIndex, bandDate in enumerate(s.dates()): @@ -504,7 +504,7 @@ class OutputImageModel(QAbstractTableModel): self.masterVRT_DateLookup[bandDate] = [] self.masterVRT_DateLookup[bandDate].append((stackIndex, bandIndex)) - #create VRT Template XML + # create VRT Template XML VRT = VRTRaster() wavelength = [] for stackIndex, stack in enumerate(listOfInputStacks): @@ -525,7 +525,7 @@ class OutputImageModel(QAbstractTableModel): dsVRT.SetMetadataItem('wavelength units', 'Nanometers') for stackIndex, stack in enumerate(listOfInputStacks): - band = dsVRT.GetRasterBand(stackIndex+1) + band = dsVRT.GetRasterBand(stackIndex + 1) assert isinstance(band, gdal.Band) assert isinstance(stack, InputStackInfo) if isinstance(stack.colorTable, gdal.ColorTable) and stack.colorTable.GetCount() > 0: @@ -539,10 +539,9 @@ class OutputImageModel(QAbstractTableModel): drv.Delete(pathVSITmp) outputVRTs = [] - eTree = ElementTree.fromstring(masterVRT_XML) for iBand, elemBand in enumerate(eTree.findall('VRTRasterBand')): - sourceElements = elemBand.findall('ComplexSource') + elemBand.findall('SimpleSource') + sourceElements = elemBand.findall('ComplexSource') + elemBand.findall('SimpleSource') assert len(sourceElements) == 1 self.masterVRT_SourceBandTemplates[iBand] = copy.deepcopy(sourceElements[0]) elemBand.remove(sourceElements[0]) @@ -555,30 +554,28 @@ class OutputImageModel(QAbstractTableModel): self.masterVRT_XML = eTree - - self.beginInsertRows(QModelIndex(), 0, len(outputVRTs)-1) + self.beginInsertRows(QModelIndex(), 0, len(outputVRTs) - 1) self.mOutputImages = outputVRTs[:] self.endInsertRows() - def setOutputDir(self, path:str): + def setOutputDir(self, path: str): self.mOutputDir = path self.updateOutputURIs() - def setOutputPrefix(self, basename:str): + def setOutputPrefix(self, basename: str): self.mOutputPrefix = basename self.updateOutputURIs() def updateOutputURIs(self): c = self.mColumnNames.index(self.cn_uri) ul = self.createIndex(0, c) - lr = self.createIndex(self.rowCount()-1, c) + lr = self.createIndex(self.rowCount() - 1, c) for outputVRT in self: assert isinstance(outputVRT, OutputVRTDescription) outputVRT.setPath(self.createVRTUri(outputVRT.mDate)) self.dataChanged.emit(ul, lr) - def __len__(self): return len(self.mOutputImages) @@ -596,14 +593,14 @@ class OutputImageModel(QAbstractTableModel): i = i.column() return self.mColumnNames[i] - def columnIndex(self, columnName:str) -> QModelIndex: + def columnIndex(self, columnName: str) -> QModelIndex: c = self.mColumnNames.index(columnName) return self.createIndex(0, c) - def index2vrt(self, index:QModelIndex) -> OutputVRTDescription: + def index2vrt(self, index: QModelIndex) -> OutputVRTDescription: return self.mOutputImages[index.row()] - def vrt2index(self, vrt:OutputVRTDescription) -> QModelIndex: + def vrt2index(self, vrt: OutputVRTDescription) -> QModelIndex: i = self.mOutputImages[vrt] return self.createIndex(i, 0, vrt) @@ -620,7 +617,7 @@ class OutputImageModel(QAbstractTableModel): if cname == self.cn_date: return str(vrt.mDate) - def vrtXML(self, outputDefinition:OutputVRTDescription, asElementTree=False) -> str: + def vrtXML(self, outputDefinition: OutputVRTDescription, asElementTree=False) -> str: """ Create the VRT XML related to an outputDefinition :param outputDefinition: @@ -632,7 +629,7 @@ class OutputImageModel(QAbstractTableModel): # xml = copy.deepcopy(eTree) if self.masterVRT_XML is None: return None - #xmlTree = ElementTree.fromstring(self.masterVRT_XML) + # xmlTree = ElementTree.fromstring(self.masterVRT_XML) xmlTree = copy.deepcopy(self.masterVRT_XML) # set metadata @@ -649,7 +646,7 @@ class OutputImageModel(QAbstractTableModel): stackIndex, stackBandIndex = t stackSourceXMLTemplate = copy.deepcopy(self.masterVRT_SourceBandTemplates[stackIndex]) - stackSourceXMLTemplate.find('SourceBand').text = str(stackBandIndex+1) + stackSourceXMLTemplate.find('SourceBand').text = str(stackBandIndex + 1) xmlVRTBands[stackIndex].append(stackSourceXMLTemplate) if asElementTree: @@ -658,9 +655,6 @@ class OutputImageModel(QAbstractTableModel): return ElementTree.tostring(xmlTree).decode('utf-8') - - - class StackedBandInputDialog(QDialog): def __init__(self, parent=None): @@ -698,7 +692,7 @@ class StackedBandInputDialog(QDialog): sm = self.tableViewSourceStacks.selectionModel() assert isinstance(sm, QItemSelectionModel) sm.selectionChanged.connect(self.onSourceStackSelectionChanged) - self.onSourceStackSelectionChanged([],[]) + self.onSourceStackSelectionChanged([], []) sm = self.tableViewOutputImages.selectionModel() assert isinstance(sm, QItemSelectionModel) @@ -808,7 +802,6 @@ class StackedBandInputDialog(QDialog): """ self.actionRemoveSourceStack.setEnabled(len(selected) > 0) - def onOutputImageSelectionChanged(self, selected, deselected): if len(selected) > 0: @@ -822,14 +815,12 @@ class StackedBandInputDialog(QDialog): self.tbXMLPreview.setPlainText(None) s = "" - def saveImages(self): """ Write the VRT images :return: [list-of-written-file-paths] """ - nTotal = len(self.tableModelOutputImages) writtenFiles = [] if nTotal == 0: @@ -859,4 +850,4 @@ class StackedBandInputDialog(QDialog): mapLayers = [QgsRasterLayer(p) for p in writtenFiles] QgsProject.instance().addMapLayers(mapLayers, addToLegend=True) self.mWrittenFiles.extend(writtenFiles) - return writtenFiles \ No newline at end of file + return writtenFiles diff --git a/eotimeseriesviewer/systeminfo.py b/eotimeseriesviewer/systeminfo.py index d8c5db002bc7f51fb073afa06ee55c47b75e3c15..404d43195241749a7a80807c2ca9c7210253502e 100644 --- a/eotimeseriesviewer/systeminfo.py +++ b/eotimeseriesviewer/systeminfo.py @@ -20,8 +20,10 @@ """ # noinspection PyPep8Naming -import sys, os, re -from qgis.core import * +import sys +import os +import re +from qgis.core import QgsMapLayer, QgsRasterLayer, QgsVectorLayer, QgsProject from collections import OrderedDict from qgis.gui import QgsDockWidget from qgis.PyQt.QtCore import * @@ -35,282 +37,278 @@ from eotimeseriesviewer.utils import loadUi, SpatialExtent PSUTIL_AVAILABLE = False try: import psutil + PSUTIL_AVAILABLE = True except: pass -def value2str(args, separator = ''): - return str(args) -class MapLayerRegistryModel(QAbstractTableModel): - - class LayerWrapper(object): - def __init__(self, lyr): - assert isinstance(lyr, QgsMapLayer) - self.lyr = lyr +def value2str(args, separator=''): + return str(args) - def __init__(self, parent=None): - super(MapLayerRegistryModel, self).__init__(parent) - self.cID = '#' - self.cPID = 'PID' - self.cName = 'Name' - self.cSrc = 'Uri' - self.cType= 'Type' +class MapLayerRegistryModel(QAbstractTableModel): + class LayerWrapper(object): + def __init__(self, lyr): + assert isinstance(lyr, QgsMapLayer) + self.lyr = lyr - self.mLayers = list() - self.REG = QgsProject.instance() - self.REG.layersAdded.connect(self.addLayers) - self.REG.layersWillBeRemoved.connect(self.removeLayers) - self.addLayers(self.REG.mapLayers().values()) + def __init__(self, parent=None): + super(MapLayerRegistryModel, self).__init__(parent) + self.cID = '#' + self.cPID = 'PID' + self.cName = 'Name' + self.cSrc = 'Uri' + self.cType = 'Type' - s = "" + self.mLayers = list() + self.REG = QgsProject.instance() + self.REG.layersAdded.connect(self.addLayers) + self.REG.layersWillBeRemoved.connect(self.removeLayers) + self.addLayers(self.REG.mapLayers().values()) - def addLayers(self, lyrs): + s = "" - lyrs = [l for l in lyrs if isinstance(l, QgsMapLayer)] + def addLayers(self, lyrs): - l = len(lyrs) + lyrs = [l for l in lyrs if isinstance(l, QgsMapLayer)] - if l > 0: - i = len(self.mLayers) - self.beginInsertRows(QModelIndex(),i, i+l) - self.mLayers.extend(lyrs) - self.endInsertRows() + l = len(lyrs) - #@pyqtSlot(list) - def removeLayers(self, lyrNames): + if l > 0: + i = len(self.mLayers) + self.beginInsertRows(QModelIndex(), i, i + l) + self.mLayers.extend(lyrs) + self.endInsertRows() + # @pyqtSlot(list) + def removeLayers(self, lyrNames): - to_remove = [self.REG.mapLayer(name) for name in lyrNames] + to_remove = [self.REG.mapLayer(name) for name in lyrNames] - for l in to_remove: - if l in self.mLayers: - i = self.mLayers.index(l) - self.beginRemoveRows(QModelIndex(),i,i) - self.mLayers.remove(l) - self.endRemoveRows() - #self.reset() + for l in to_remove: + if l in self.mLayers: + i = self.mLayers.index(l) + self.beginRemoveRows(QModelIndex(), i, i) + self.mLayers.remove(l) + self.endRemoveRows() + # self.reset() - def columnNames(self): - return [self.cID, self.cPID, self.cName, self.cType, self.cSrc] + def columnNames(self): + return [self.cID, self.cPID, self.cName, self.cType, self.cSrc] - def headerData(self, col, orientation, role): - if orientation == Qt.Horizontal and role == Qt.DisplayRole: - return self.columnNames()[col] - elif orientation == Qt.Vertical and role == Qt.DisplayRole: - return col - return None + def headerData(self, col, orientation, role): + if orientation == Qt.Horizontal and role == Qt.DisplayRole: + return self.columnNames()[col] + elif orientation == Qt.Vertical and role == Qt.DisplayRole: + return col + return None - def sort(self, col, order): - """Sort table by given column number. + def sort(self, col, order): + """Sort table by given column number. """ - self.layoutAboutToBeChanged.emit() - columnName = self.columnNames()[col] - rev = order == Qt.DescendingOrder - sortedLyers = None - - if columnName == self.cName: - sortedLyers = sorted(self.mLayers, key=lambda l: l.name(), reverse=rev) - elif columnName == self.cSrc: - sortedLyers = sorted(self.mLayers, key=lambda l: l.source(), reverse=rev) - elif columnName == self.cID: - lyrs = self.REG.mapLayers().values() - sortedLyers = sorted(self.mLayers, key=lambda l: lyrs.index(l), reverse=rev) - elif columnName == self.cPID: - sortedLyers = sorted(self.mLayers, key=lambda l: id(l), reverse=rev) - elif columnName == self.cType: - types = [QgsVectorLayer, QgsRasterLayer] - sortedLyers = sorted(self.mLayers, key=lambda l: types.index(type(l)), reverse=rev) - - del self.mLayers[:] - self.mLayers.extend(sortedLyers) - self.layoutChanged.emit() - - def rowCount(self, parentIdx=None, *args, **kwargs): - return len(self.mLayers) + self.layoutAboutToBeChanged.emit() + columnName = self.columnNames()[col] + rev = order == Qt.DescendingOrder + sortedLyers = None + + if columnName == self.cName: + sortedLyers = sorted(self.mLayers, key=lambda l: l.name(), reverse=rev) + elif columnName == self.cSrc: + sortedLyers = sorted(self.mLayers, key=lambda l: l.source(), reverse=rev) + elif columnName == self.cID: + lyrs = self.REG.mapLayers().values() + sortedLyers = sorted(self.mLayers, key=lambda l: lyrs.index(l), reverse=rev) + elif columnName == self.cPID: + sortedLyers = sorted(self.mLayers, key=lambda l: id(l), reverse=rev) + elif columnName == self.cType: + types = [QgsVectorLayer, QgsRasterLayer] + sortedLyers = sorted(self.mLayers, key=lambda l: types.index(type(l)), reverse=rev) + + del self.mLayers[:] + self.mLayers.extend(sortedLyers) + self.layoutChanged.emit() + + def rowCount(self, parentIdx=None, *args, **kwargs): + return len(self.mLayers) + + def columnCount(self, QModelIndex_parent=None, *args, **kwargs): + return len(self.columnNames()) + + def lyr2idx(self, lyr): + assert isinstance(lyr, QgsMapLayer) + # return self.createIndex(self.mSpecLib.index(profile), 0) + # pw = self.mProfileWrappers[profile] + if not lyr in self.mLayers: + return None + return self.createIndex(self.mLayers.index(lyr), 0) - def columnCount(self, QModelIndex_parent=None, *args, **kwargs): - return len(self.columnNames()) + def idx2lyr(self, index): + assert isinstance(index, QModelIndex) + if not index.isValid(): + return None + return self.mLayers[index.row()] - def lyr2idx(self, lyr): - assert isinstance(lyr, QgsMapLayer) - # return self.createIndex(self.mSpecLib.index(profile), 0) - # pw = self.mProfileWrappers[profile] - if not lyr in self.mLayers: - return None - return self.createIndex(self.mLayers.index(lyr), 0) + def idx2lyrs(self, indices): + lyrs = [self.idx2lyr(i) for i in indices] + return [l for l in lyrs if isinstance(l, QgsMapLayer)] - def idx2lyr(self, index): - assert isinstance(index, QModelIndex) - if not index.isValid(): - return None - return self.mLayers[index.row()] + def data(self, index, role=Qt.DisplayRole): + if role is None or not index.isValid(): + return None + columnName = self.columnNames()[index.column()] + lyr = self.idx2lyr(index) + value = None + assert isinstance(lyr, QgsMapLayer) + if role == Qt.DisplayRole: + if columnName == self.cPID: + value = id(lyr) + elif columnName == self.cID: + value = list(self.REG.mapLayers().values()).index(lyr) + elif columnName == self.cName: + value = lyr.name() + elif columnName == self.cSrc: + value = lyr.source() + elif columnName in self.cType: + value = re.sub('[\'<>]', '', str(type(lyr))).split('.')[-1] - def idx2lyrs(self, indices): - lyrs = [self.idx2lyr(i) for i in indices] - return [l for l in lyrs if isinstance(l, QgsMapLayer)] + if role == Qt.UserRole: + value = lyr - def data(self, index, role=Qt.DisplayRole): - if role is None or not index.isValid(): - return None + return value + def flags(self, index): + if index.isValid(): columnName = self.columnNames()[index.column()] - lyr = self.idx2lyr(index) - value = None - assert isinstance(lyr, QgsMapLayer) - if role == Qt.DisplayRole: - if columnName == self.cPID: - value = id(lyr) - elif columnName == self.cID: - value = list(self.REG.mapLayers().values()).index(lyr) - elif columnName == self.cName: - value = lyr.name() - elif columnName == self.cSrc: - value = lyr.source() - elif columnName in self.cType: - value = re.sub('[\'<>]','',str(type(lyr))).split('.')[-1] - - if role == Qt.UserRole: - value = lyr - - return value - - def flags(self, index): - if index.isValid(): - columnName = self.columnNames()[index.column()] - flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable - return flags - return None - + flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable + return flags + return None class DataLoadingModel(QAbstractTableModel): - def __init__(self, parent=None): - super(DataLoadingModel, self).__init__(parent) - - self.cName = 'Type' - self.cSamples = 'n' - self.cAvgAll = u'mean \u0394t(all) [ms]' - self.cMaxAll = u'max \u0394t(all) [ms]' - self.cAvg10 = u'mean \u0394t(10) [ms]' - self.cMax10 = u'max \u0394t(10) [ms]' - self.cLast = u'last \u0394t [ms]' - - self.mCacheSize = 500 - self.mLoadingTimes = OrderedDict() - - def addTimeDelta(self, name, timedelta): - assert isinstance(timedelta, np.timedelta64) - #if timedelta.astype(float) > 0: - #print(timedelta) - if name not in self.mLoadingTimes.keys(): - self.mLoadingTimes[name] = [] - to_remove = max(0,len(self.mLoadingTimes[name]) + 1 - self.mCacheSize) - if to_remove > 0: - del self.mLoadingTimes[name][0:to_remove] - self.mLoadingTimes[name].append(timedelta) - + def __init__(self, parent=None): + super(DataLoadingModel, self).__init__(parent) + + self.cName = 'Type' + self.cSamples = 'n' + self.cAvgAll = u'mean \u0394t(all) [ms]' + self.cMaxAll = u'max \u0394t(all) [ms]' + self.cAvg10 = u'mean \u0394t(10) [ms]' + self.cMax10 = u'max \u0394t(10) [ms]' + self.cLast = u'last \u0394t [ms]' + + self.mCacheSize = 500 + self.mLoadingTimes = OrderedDict() + + def addTimeDelta(self, name, timedelta): + assert isinstance(timedelta, np.timedelta64) + # if timedelta.astype(float) > 0: + # print(timedelta) + if name not in self.mLoadingTimes.keys(): + self.mLoadingTimes[name] = [] + to_remove = max(0, len(self.mLoadingTimes[name]) + 1 - self.mCacheSize) + if to_remove > 0: + del self.mLoadingTimes[name][0:to_remove] + self.mLoadingTimes[name].append(timedelta) + + self.layoutChanged.emit() + + def variableNames(self): + return [self.cName, self.cSamples, self.cLast, self.cMaxAll, self.cAvgAll, self.cMax10, self.cAvg10] + + def headerData(self, col, orientation, role): + if orientation == Qt.Horizontal and role == Qt.DisplayRole: + return self.variableNames()[col] + elif orientation == Qt.Vertical and role == Qt.DisplayRole: + return col + return None + + def sort(self, col, order): + """Sort table by given column number. + """ + self.layoutAboutToBeChanged.emit() + columnName = self.variableNames()[col] + rev = order == Qt.DescendingOrder + sortedNames = None + if columnName == self.cName: + sortedNames = sorted(self.mLoadingTimes.keys(), reverse=rev) + elif columnName == self.cSamples: + sortedNames = sorted(self.mLoadingTimes.keys(), key=lambda n: len(self.mLoadingTimes[n]), reverse=rev) + elif columnName == self.cAvgAll: + sortedNames = sorted(self.mLoadingTimes.keys(), key=lambda name: + np.asarray(self.mLoadingTimes[name]).mean(), reverse=rev) + elif columnName == self.cAvg10: + sortedNames = sorted(self.mLoadingTimes.keys(), key=lambda name: + np.asarray(self.mLoadingTimes[name][-10:]).mean(), reverse=rev) + + if sortedNames is not None: + tmp = OrderedDict([(name, self.mLoadingTimes[name]) for name in sortedNames]) + self.mLoadingTimes.clear() + self.mLoadingTimes.update(tmp) self.layoutChanged.emit() - def variableNames(self): - return [self.cName, self.cSamples, self.cLast, self.cMaxAll, self.cAvgAll, self.cMax10, self.cAvg10] + def rowCount(self, parentIdx=None, *args, **kwargs): + return len(self.mLoadingTimes) + + def columnCount(self, QModelIndex_parent=None, *args, **kwargs): + return len(self.variableNames()) - def headerData(self, col, orientation, role): - if orientation == Qt.Horizontal and role == Qt.DisplayRole: - return self.variableNames()[col] - elif orientation == Qt.Vertical and role == Qt.DisplayRole: - return col + def type2idx(self, type): + assert isinstance(type, str) + if type not in self.mLoadingTimes.keys(): return None + return self.createIndex(self.mLoadingTimes.keys().index(type), 0) - def sort(self, col, order): - """Sort table by given column number. - """ - self.layoutAboutToBeChanged.emit() - columnName = self.variableNames()[col] - rev = order == Qt.DescendingOrder - sortedNames = None - if columnName == self.cName: - sortedNames = sorted(self.mLoadingTimes.keys(), reverse=rev) - elif columnName == self.cSamples: - sortedNames = sorted(self.mLoadingTimes.keys(), key=lambda n: len(self.mLoadingTimes[n]), reverse=rev) - elif columnName == self.cAvgAll: - sortedNames = sorted(self.mLoadingTimes.keys(), key=lambda name: - np.asarray(self.mLoadingTimes[name]).mean(), reverse=rev) - elif columnName == self.cAvg10: - sortedNames = sorted(self.mLoadingTimes.keys(), key=lambda name: - np.asarray(self.mLoadingTimes[name][-10:]).mean(), reverse=rev) - - if sortedNames is not None: - tmp = OrderedDict([(name, self.mLoadingTimes[name]) for name in sortedNames]) - self.mLoadingTimes.clear() - self.mLoadingTimes.update(tmp) - self.layoutChanged.emit() - - def rowCount(self, parentIdx=None, *args, **kwargs): - return len(self.mLoadingTimes) - - def columnCount(self, QModelIndex_parent=None, *args, **kwargs): - return len(self.variableNames()) - - def type2idx(self, type): - assert isinstance(type, str) - if type not in self.mLoadingTimes.keys(): - return None - return self.createIndex(self.mLoadingTimes.keys().index(type), 0) - - def idx2type(self, index): - assert isinstance(index, QModelIndex) - if not index.isValid(): - return None - return list(self.mLoadingTimes.keys())[index.row()] - - def data(self, index, role=Qt.DisplayRole): - if role is None or not index.isValid(): - return None + def idx2type(self, index): + assert isinstance(index, QModelIndex) + if not index.isValid(): + return None + return list(self.mLoadingTimes.keys())[index.row()] - columnName = self.variableNames()[index.column()] - name = self.idx2type(index) - lTimes = self.mLoadingTimes[name] - value = None - if role in [Qt.DisplayRole, Qt.EditRole]: - if columnName == self.cName: - value = name - elif columnName == self.cSamples: - value = len(lTimes) - - if len(lTimes) > 0: - if columnName == self.cAvg10: - value = float(np.asarray(lTimes[-10:]).mean().astype(float)) - elif columnName == self.cAvgAll: - value = float(np.asarray(lTimes[:]).mean().astype(float)) - elif columnName == self.cMax10: - value = float(np.asarray(lTimes[-10:]).max().astype(float)) - elif columnName == self.cMaxAll: - value = float(np.asarray(lTimes[:]).max().astype(float)) - elif columnName == self.cLast: - value = float(lTimes[-1].astype(float)) - - if role == Qt.UserRole: - value = lTimes - - return value - - def flags(self, index): - if index.isValid(): - columnName = self.variableNames()[index.column()] - flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable - return flags + def data(self, index, role=Qt.DisplayRole): + if role is None or not index.isValid(): return None + columnName = self.variableNames()[index.column()] + name = self.idx2type(index) + lTimes = self.mLoadingTimes[name] + value = None + if role in [Qt.DisplayRole, Qt.EditRole]: + if columnName == self.cName: + value = name + elif columnName == self.cSamples: + value = len(lTimes) + + if len(lTimes) > 0: + if columnName == self.cAvg10: + value = float(np.asarray(lTimes[-10:]).mean().astype(float)) + elif columnName == self.cAvgAll: + value = float(np.asarray(lTimes[:]).mean().astype(float)) + elif columnName == self.cMax10: + value = float(np.asarray(lTimes[-10:]).max().astype(float)) + elif columnName == self.cMaxAll: + value = float(np.asarray(lTimes[:]).max().astype(float)) + elif columnName == self.cLast: + value = float(lTimes[-1].astype(float)) + + if role == Qt.UserRole: + value = lTimes + + return value + + def flags(self, index): + if index.isValid(): + columnName = self.variableNames()[index.column()] + flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable + return flags + return None class SystemInfoDock(QgsDockWidget): - def __init__(self, parent=None): super(SystemInfoDock, self).__init__(parent) loadUi(DIR_UI / 'systeminfo.ui', self) @@ -335,27 +333,25 @@ class SystemInfoDock(QgsDockWidget): self.labelPSUTIL.setVisible(PSUTIL_AVAILABLE == False) if PSUTIL_AVAILABLE: self.tableViewSystemParameters.setVisible(True) - #self.systemInfoModel = SystemInfoModel() - #self.tableViewSystemParameters.setModel(self.systemInfoModel) + # self.systemInfoModel = SystemInfoModel() + # self.tableViewSystemParameters.setModel(self.systemInfoModel) else: self.systemInfoModel = None def addTimeDelta(self, type, timedelta): self.dataLoadingModel.addTimeDelta(type, timedelta) - def contextMenuEvent(self, tableView, event): assert isinstance(tableView, QTableView) menu = QMenu(self) a = menu.addAction("Copy selected") - a.triggered.connect(lambda :self.onCopy2Clipboard(tableView, 'SELECTED', separator=';')) + a.triggered.connect(lambda: self.onCopy2Clipboard(tableView, 'SELECTED', separator=';')) a = menu.addAction("Copy table") a.triggered.connect(lambda: self.onCopy2Clipboard(tableView, 'TABLE', separator=';')) a = menu.addAction('Save to file') - a.triggered.connect(lambda : self.onSaveToFile(tableView, 'TABLE')) - + a.triggered.connect(lambda: self.onSaveToFile(tableView, 'TABLE')) menu.popup(QCursor.pos()) @@ -394,7 +390,6 @@ class SystemInfoDock(QgsDockWidget): def onSaveToFile(self, tableView, key): lines = self.readTableValues(key, tableView) - if len(lines) > 0: filters = 'Textfile (*.txt);;CSV Table (*.csv)' path = QFileDialog.getSaveFileName(parent=None, caption="Save Table to file", diff --git a/eotimeseriesviewer/temporalprofiles.py b/eotimeseriesviewer/temporalprofiles.py index 64b721d8dc9476068433028e5d8a1c53e76c5d1e..925f0ba95901130d5e283b07c2a1460eb16e12b0 100644 --- a/eotimeseriesviewer/temporalprofiles.py +++ b/eotimeseriesviewer/temporalprofiles.py @@ -22,12 +22,12 @@ import os import sys -import pickle import datetime import re - +import typing +import pathlib +import traceback from collections import OrderedDict -from qgis.core import * from qgis.core import QgsMapLayer, QgsRasterLayer, QgsVectorLayer, QgsMessageOutput, QgsCoordinateReferenceSystem, \ Qgis, QgsWkbTypes, QgsTask, QgsProviderRegistry, QgsMapLayerStore, QgsFeature, QgsDateTimeRange, \ QgsTextFormat, QgsProject, QgsSingleSymbolRenderer, QgsGeometry, QgsApplication, QgsFillSymbol, \ @@ -37,23 +37,20 @@ from qgis.core import QgsMapLayer, QgsRasterLayer, QgsVectorLayer, QgsMessageOut QgsConditionalStyle, QgsConditionalLayerStyles, \ QgsField, QgsFields, QgsExpressionContext, QgsExpression -from qgis.gui import * from qgis.gui import QgsMapCanvas, QgsStatusBar, QgsFileWidget, \ QgsMessageBar, QgsMessageViewer, QgsDockWidget, QgsTaskManagerWidget, QgisInterface, \ QgsAttributeTableFilterModel, QgsIFeatureSelectionManager, QgsAttributeTableModel, QgsAttributeTableView -from qgis.analysis import * + from qgis.PyQt.QtCore import * from qgis.PyQt.QtGui import * from qgis.PyQt.QtWidgets import * import numpy as np from osgeo import ogr, osr, gdal from .externals import pyqtgraph as pg -from .externals.pyqtgraph import functions as fn, AxisItem, ScatterPlotItem, SpotItem, GraphicsScene -from .externals.qps.plotstyling.plotstyling import PlotStyle from .timeseries import TimeSeries, TimeSeriesDate, SensorInstrument, TimeSeriesSource -from .utils import * -from .externals.qps.speclib.core import createQgsField +from .utils import SpatialExtent, SpatialPoint, px2geo, geo2px +from .externals.qps.speclib.core import createQgsField, setQgsFieldValue LABEL_EXPRESSION_2D = 'DN or Index' @@ -954,7 +951,8 @@ class TemporalProfileLayer(QgsVectorLayer): assert isinstance(temporalProfiles, list) - temporalProfiles = [tp for tp in temporalProfiles if isinstance(tp, TemporalProfile) and tp.id() in self.mProfiles.keys()] + temporalProfiles = [tp for tp in temporalProfiles + if isinstance(tp, TemporalProfile) and tp.id() in self.mProfiles.keys()] if len(temporalProfiles) > 0: b = self.isEditable() diff --git a/eotimeseriesviewer/tests.py b/eotimeseriesviewer/tests.py index 1b925fcc1df4c82ed069a0dbd45168867927891e..6a561f330c7ac5616ee3c844244c2b59d0413ab6 100644 --- a/eotimeseriesviewer/tests.py +++ b/eotimeseriesviewer/tests.py @@ -21,11 +21,7 @@ # noinspection PyPep8Naming import os -import re -import io -import importlib -import uuid -from qgis.core import * +import pathlib import numpy as np from qgis.gui import * from qgis.PyQt.QtCore import * @@ -34,13 +30,11 @@ from qgis.PyQt.QtWidgets import * import eotimeseriesviewer.externals.qps.testing import eotimeseriesviewer.externals.qps from eotimeseriesviewer.utils import file_search -from osgeo import ogr, osr, gdal, gdal_array -import qgis.testing +from osgeo import osr, gdal import example from eotimeseriesviewer import DIR_EXAMPLES, DIR_QGIS_RESOURCES, DIR_UI, DIR_REPO from eotimeseriesviewer.timeseries import TimeSeries -from eotimeseriesviewer.externals.qps.resources import findQGISResourceFiles -from eotimeseriesviewer.externals.qps.testing import * +from eotimeseriesviewer.externals.qps.testing import TestObjects, TestCase, start_app from eotimeseriesviewer.externals.qps.resources import initQtResources diff --git a/eotimeseriesviewer/timeseries.py b/eotimeseriesviewer/timeseries.py index 0af066f72e39a7c996e12a0b02e21b52cc00c899..22fc0bb0dd52019652dd51534dac021cc4a7c4ff 100644 --- a/eotimeseriesviewer/timeseries.py +++ b/eotimeseriesviewer/timeseries.py @@ -19,7 +19,7 @@ ***************************************************************************/ """ # noinspection PyPep8Naming - +import os import bisect import collections import datetime @@ -35,7 +35,6 @@ from osgeo import osr, ogr, gdal_array from eotimeseriesviewer import DIR_UI from eotimeseriesviewer.utils import relativePath -from qgis import * from qgis.PyQt.QtCore import * from qgis.PyQt.QtGui import * from qgis.PyQt.QtWidgets import * @@ -1358,7 +1357,7 @@ class TimeSeries(QAbstractItemModel): self.mTSDs = list() self.mSensors = [] self.mShape = None - + self.mTreeView: QTreeView = None self.mDateTimePrecision = DateTimePrecision.Original self.mSensorMatchingFlags = SensorMatching.PX_DIMS diff --git a/eotimeseriesviewer/utils.py b/eotimeseriesviewer/utils.py index 14bdf2e4b59b436759763d1cd8b77779d4e15c5a..44c7b48a02ecdd861eeda14a7db287fbb109617a 100644 --- a/eotimeseriesviewer/utils.py +++ b/eotimeseriesviewer/utils.py @@ -20,20 +20,9 @@ """ # noinspection PyPep8Naming -import os, sys, math, re, io, fnmatch, uuid - -from collections import defaultdict -from qgis.core import * -from qgis.gui import * from qgis.gui import QgisInterface import qgis.utils -from qgis.PyQt.QtCore import * -from qgis.PyQt.QtWidgets import * -from qgis.PyQt.QtGui import * -from qgis.PyQt.QtXml import QDomDocument -from PyQt5 import uic -from osgeo import gdal, ogr from eotimeseriesviewer.externals.qps.utils import * diff --git a/eotimeseriesviewer/virtualrasters.py b/eotimeseriesviewer/virtualrasters.py index 7051ba4dd6f0432d639ce27ed5ea75ea6edf83a3..8c020c12b019197bace21a9f9ad2e7c3295014cb 100644 --- a/eotimeseriesviewer/virtualrasters.py +++ b/eotimeseriesviewer/virtualrasters.py @@ -24,56 +24,57 @@ from xml.etree import ElementTree from collections import OrderedDict import tempfile from osgeo import gdal, osr, ogr, gdalconst as gc -from qgis.core import * +from qgis.core import QgsCoordinateReferenceSystem, QgsCoordinateTransform, QgsPoint, QgsCircularString, \ + QgsPolygon, QgsRectangle from qgis.gui import * from qgis.PyQt.QtCore import * from qgis.PyQt.QtGui import * from qgis.PyQt.QtWidgets import * from eotimeseriesviewer import Option, OptionListModel -#lookup GDAL Data Type and its size in bytes -LUT_GDT_SIZE = {gdal.GDT_Byte:1, - gdal.GDT_UInt16:2, - gdal.GDT_Int16:2, - gdal.GDT_UInt32:4, - gdal.GDT_Int32:4, - gdal.GDT_Float32:4, - gdal.GDT_Float64:8, - gdal.GDT_CInt16:2, - gdal.GDT_CInt32:4, - gdal.GDT_CFloat32:4, - gdal.GDT_CFloat64:8} - -LUT_GDT_NAME = {gdal.GDT_Byte:'Byte', - gdal.GDT_UInt16:'UInt16', - gdal.GDT_Int16:'Int16', - gdal.GDT_UInt32:'UInt32', - gdal.GDT_Int32:'Int32', - gdal.GDT_Float32:'Float32', - gdal.GDT_Float64:'Float64', - gdal.GDT_CInt16:'Int16', - gdal.GDT_CInt32:'Int32', - gdal.GDT_CFloat32:'Float32', - gdal.GDT_CFloat64:'Float64'} - - -GRA_tooltips = {'NearestNeighbour':'nearest neighbour resampling (default, fastest algorithm, worst interpolation quality).', - 'Bilinear':'bilinear resampling.', - 'Lanczos':'lanczos windowed sinc resampling.', - 'Average':'average resampling, computes the average of all non-NODATA contributing pixels.', - 'Cubic':'cubic resampling.', - 'CubicSpline':'cubic spline resampling.', - 'Mode':'mode resampling, selects the value which appears most often of all the sampled points', - 'Max':'maximum resampling, selects the maximum value from all non-NODATA contributing pixels', - 'Min':'minimum resampling, selects the minimum value from all non-NODATA contributing pixels.', - 'Med':'median resampling, selects the median value of all non-NODATA contributing pixels.', - 'Q1':'first quartile resampling, selects the first quartile value of all non-NODATA contributing pixels. ', - 'Q3':'third quartile resampling, selects the third quartile value of all non-NODATA contributing pixels' - } + +# lookup GDAL Data Type and its size in bytes +LUT_GDT_SIZE = {gdal.GDT_Byte: 1, + gdal.GDT_UInt16: 2, + gdal.GDT_Int16: 2, + gdal.GDT_UInt32: 4, + gdal.GDT_Int32: 4, + gdal.GDT_Float32: 4, + gdal.GDT_Float64: 8, + gdal.GDT_CInt16: 2, + gdal.GDT_CInt32: 4, + gdal.GDT_CFloat32: 4, + gdal.GDT_CFloat64: 8} + +LUT_GDT_NAME = {gdal.GDT_Byte: 'Byte', + gdal.GDT_UInt16: 'UInt16', + gdal.GDT_Int16: 'Int16', + gdal.GDT_UInt32: 'UInt32', + gdal.GDT_Int32: 'Int32', + gdal.GDT_Float32: 'Float32', + gdal.GDT_Float64: 'Float64', + gdal.GDT_CInt16: 'Int16', + gdal.GDT_CInt32: 'Int32', + gdal.GDT_CFloat32: 'Float32', + gdal.GDT_CFloat64: 'Float64'} + +GRA_tooltips = { + 'NearestNeighbour': 'nearest neighbour resampling (default, fastest algorithm, worst interpolation quality).', + 'Bilinear': 'bilinear resampling.', + 'Lanczos': 'lanczos windowed sinc resampling.', + 'Average': 'average resampling, computes the average of all non-NODATA contributing pixels.', + 'Cubic': 'cubic resampling.', + 'CubicSpline': 'cubic spline resampling.', + 'Mode': 'mode resampling, selects the value which appears most often of all the sampled points', + 'Max': 'maximum resampling, selects the maximum value from all non-NODATA contributing pixels', + 'Min': 'minimum resampling, selects the minimum value from all non-NODATA contributing pixels.', + 'Med': 'median resampling, selects the median value of all non-NODATA contributing pixels.', + 'Q1': 'first quartile resampling, selects the first quartile value of all non-NODATA contributing pixels. ', + 'Q3': 'third quartile resampling, selects the third quartile value of all non-NODATA contributing pixels' + } RESAMPLE_ALGS = OptionListModel() for GRAkey in [k for k in list(gdal.__dict__.keys()) if k.startswith('GRA_')]: - GRA = gdal.__dict__[GRAkey] GRA_Name = GRAkey[4:] @@ -88,13 +89,14 @@ def read_vsimem(fn): :param fn: vsimem path (str) :return: result of gdal.VSIFReadL(1, vsileng, vsifile) """ - vsifile = gdal.VSIFOpenL(fn,'r') + vsifile = gdal.VSIFOpenL(fn, 'r') gdal.VSIFSeekL(vsifile, 0, 2) vsileng = gdal.VSIFTellL(vsifile) gdal.VSIFSeekL(vsifile, 0, 0) return gdal.VSIFReadL(1, vsileng, vsifile) -def write_vsimem(fn:str,data:str): + +def write_vsimem(fn: str, data: str): """ Writes data to vsimem path :param fn: vsimem path (str) @@ -102,22 +104,22 @@ def write_vsimem(fn:str,data:str): :return: result of gdal.VSIFCloseL(vsifile) """ '''Write GDAL vsimem files''' - vsifile = gdal.VSIFOpenL(fn,'w') + vsifile = gdal.VSIFOpenL(fn, 'w') size = len(data) gdal.VSIFWriteL(data, 1, size, vsifile) return gdal.VSIFCloseL(vsifile) def px2geo(px, gt): - #see http://www.gdal.org/gdal_datamodel.html - gx = gt[0] + px.x()*gt[1]+px.y()*gt[2] - gy = gt[3] + px.x()*gt[4]+px.y()*gt[5] - return QgsPoint(gx,gy) + # see http://www.gdal.org/gdal_datamodel.html + gx = gt[0] + px.x() * gt[1] + px.y() * gt[2] + gy = gt[3] + px.x() * gt[4] + px.y() * gt[5] + return QgsPoint(gx, gy) def describeRawFile(pathRaw, pathVrt, xsize, ysize, bands=1, - eType = gdal.GDT_Byte, + eType=gdal.GDT_Byte, interleave='bsq', byteOrder='LSB', headerOffset=0): @@ -142,7 +144,7 @@ def describeRawFile(pathRaw, pathVrt, xsize, ysize, assert eType in LUT_GDT_SIZE.keys(), 'dataType "{}" is not a valid gdal datatype'.format(eType) interleave = interleave.lower() - assert interleave in ['bsq','bil','bip'] + assert interleave in ['bsq', 'bil', 'bip'] assert byteOrder in ['LSB', 'MSB'] drvVRT = gdal.GetDriverByName('VRT') @@ -150,7 +152,7 @@ def describeRawFile(pathRaw, pathVrt, xsize, ysize, dsVRT = drvVRT.Create(pathVrt, xsize, ysize, bands=0, eType=eType) assert isinstance(dsVRT, gdal.Dataset) - #vrt = ['<VRTDataset rasterXSize="{xsize}" rasterYSize="{ysize}">'.format(xsize=xsize,ysize=ysize)] + # vrt = ['<VRTDataset rasterXSize="{xsize}" rasterYSize="{ysize}">'.format(xsize=xsize,ysize=ysize)] vrtDir = os.path.dirname(pathVrt) if pathRaw.startswith(vrtDir): @@ -191,15 +193,15 @@ def describeRawFile(pathRaw, pathVrt, xsize, ysize, lineOffset=lineOffset, byteOrder=byteOrder) - #md = {} - #md['source_0'] = xml - #vrtBand = dsVRT.GetRasterBand(b + 1) + # md = {} + # md['source_0'] = xml + # vrtBand = dsVRT.GetRasterBand(b + 1) assert dsVRT.AddBand(eType, options=options) == 0 - vrtBand = dsVRT.GetRasterBand(b+1) + vrtBand = dsVRT.GetRasterBand(b + 1) assert isinstance(vrtBand, gdal.Band) - #vrtBand.SetMetadata(md, 'vrt_sources') - #vrt.append(' <VRTRasterBand dataType="{dataType}" band="{band}" subClass="VRTRawRasterBand">'.format(dataType=LUT_GDT_NAME[eType], band=b+1)) + # vrtBand.SetMetadata(md, 'vrt_sources') + # vrt.append(' <VRTRasterBand dataType="{dataType}" band="{band}" subClass="VRTRawRasterBand">'.format(dataType=LUT_GDT_NAME[eType], band=b+1)) dsVRT.FlushCache() return dsVRT @@ -224,16 +226,13 @@ class VRTRasterInputSourceBand(object): srcBands.append(VRTRasterInputSourceBand(path, b)) return srcBands - - - def __init__(self, path:str, bandIndex:int, bandName:str=''): + def __init__(self, path: str, bandIndex: int, bandName: str = ''): self.mPath = path self.mBandIndex = bandIndex self.mBandName = bandName self.mNoData = None self.mVirtualBand = None - def isEqual(self, other): if isinstance(other, VRTRasterInputSourceBand): return self.mPath == other.mPath and self.mBandIndex == other.mBandIndex @@ -260,6 +259,7 @@ class VRTRasterBand(QObject): sigNameChanged = pyqtSignal(str) sigSourceInserted = pyqtSignal(int, VRTRasterInputSourceBand) sigSourceRemoved = pyqtSignal(int, VRTRasterInputSourceBand) + def __init__(self, name='', parent=None): super(VRTRasterBand, self).__init__(parent) self.mSources = [] @@ -280,8 +280,6 @@ class VRTRasterBand(QObject): def name(self): return self.mName - - def addSource(self, virtualBandInputSource): assert isinstance(virtualBandInputSource, VRTRasterInputSourceBand) self.insertSource(len(self.mSources), virtualBandInputSource) @@ -294,14 +292,14 @@ class VRTRasterBand(QObject): self.sigSourceInserted.emit(index, virtualBandInputSource) else: pass - #print('DEBUG: index <= len(self.sources)') + # print('DEBUG: index <= len(self.sources)') + def bandIndex(self): if isinstance(self.mVRT, VRTRaster): return self.mVRT.mBands.index(self) else: return None - def removeSource(self, vrtRasterInputSourceBand): """ Removes a VRTRasterInputSourceBand @@ -315,7 +313,6 @@ class VRTRasterBand(QObject): self.mSources.remove(vrtRasterInputSourceBand) self.sigSourceRemoved.emit(i, vrtRasterInputSourceBand) - def sourceFiles(self): """ :return: list of file-paths to all source files @@ -332,7 +329,6 @@ class VRTRasterBand(QObject): class VRTRaster(QObject): - sigSourceBandInserted = pyqtSignal(VRTRasterBand, VRTRasterInputSourceBand) sigSourceBandRemoved = pyqtSignal(VRTRasterBand, VRTRasterInputSourceBand) sigSourceRasterAdded = pyqtSignal(list) @@ -341,7 +337,7 @@ class VRTRaster(QObject): sigBandRemoved = pyqtSignal(int, VRTRasterBand) sigCrsChanged = pyqtSignal(QgsCoordinateReferenceSystem) sigResolutionChanged = pyqtSignal() - sigResamplingAlgChanged = pyqtSignal([str],[int]) + sigResamplingAlgChanged = pyqtSignal([str], [int]) sigExtentChanged = pyqtSignal() def __init__(self, parent=None): @@ -358,7 +354,6 @@ class VRTRaster(QObject): self.sigBandRemoved.connect(self.updateSourceRasterBounds) self.sigBandInserted.connect(self.updateSourceRasterBounds) - def setResamplingAlg(self, value): """ Sets the resampling algorithm @@ -384,7 +379,6 @@ class VRTRaster(QObject): self.sigResamplingAlgChanged[str].emit(self.resamplingAlg(asString=True)) self.sigResamplingAlgChanged[int].emit(self.resamplingAlg()) - def resamplingAlg(self, asString=False): """ "Returns the resampling algorithms. @@ -401,7 +395,7 @@ class VRTRaster(QObject): def setExtent(self, rectangle, crs=None): last = self.mExtent if rectangle is None: - #use implicit/automatic values + # use implicit/automatic values self.mExtent = None else: if isinstance(crs, QgsCoordinateReferenceSystem) and isinstance(self.mCrs, QgsCoordinateReferenceSystem): @@ -437,7 +431,7 @@ class VRTRaster(QObject): assert xy.height() > 0 self.mResolution = QSizeF(xy) elif isinstance(xy, str): - assert xy in ['average','highest','lowest'] + assert xy in ['average', 'highest', 'lowest'] self.mResolution = xy if last != self.mResolution: @@ -450,7 +444,6 @@ class VRTRaster(QObject): """ return self.mResolution - def setCrs(self, crs): """ Sets the output Coordinate Reference System (CRS) @@ -473,7 +466,6 @@ class VRTRaster(QObject): self.mCrs = crs self.sigCrsChanged.emit(self.mCrs) - def crs(self): return self.mCrs @@ -494,14 +486,12 @@ class VRTRaster(QObject): :param sourceBandIndex: source file band index """ - while virtualBandIndex > len(self.mBands)-1: - + while virtualBandIndex > len(self.mBands) - 1: self.insertVirtualBand(len(self.mBands), VRTRasterBand()) vBand = self.mBands[virtualBandIndex] vBand.addSourceBand(pathSource, sourceBandIndex) - def insertVirtualBand(self, index, virtualBand): """ Inserts a VirtualBand @@ -512,7 +502,7 @@ class VRTRaster(QObject): assert isinstance(virtualBand, VRTRasterBand) assert index <= len(self.mBands) if len(virtualBand.name()) == 0: - virtualBand.setName('Band {}'.format(index+1)) + virtualBand.setName('Band {}'.format(index + 1)) virtualBand.mVRT = self virtualBand.sigSourceInserted.connect( @@ -525,8 +515,6 @@ class VRTRaster(QObject): return self[index] - - def removeVirtualBands(self, bandsOrIndices): assert isinstance(bandsOrIndices, list) to_remove = [] @@ -540,7 +528,6 @@ class VRTRaster(QObject): self.mBands.remove(virtualBand) self.sigBandRemoved.emit(index, virtualBand) - def removeInputSource(self, path): assert path in self.sourceRaster() for vBand in self.mBands: @@ -562,8 +549,8 @@ class VRTRaster(QObject): assert isinstance(ds, gdal.Dataset) nb = ds.RasterCount for b in range(nb): - if b+1 < len(self): - #add new virtual band + if b + 1 < len(self): + # add new virtual band self.addVirtualBand(VRTRasterBand()) vBand = self[b] assert isinstance(vBand, VRTRasterBand) @@ -583,12 +570,11 @@ class VRTRaster(QObject): nb = ds.RasterCount ds = None for b in range(nb): - #each new band is a new virtual band + # each new band is a new virtual band vBand = self.addVirtualBand(VRTRasterBand()) assert isinstance(vBand, VRTRasterBand) vBand.addSource(VRTRasterInputSourceBand(file, b)) - return self def sourceRaster(self): @@ -601,7 +587,6 @@ class VRTRaster(QObject): def sourceRasterBounds(self): return self.mSourceRasterBounds - def updateSourceRasterBounds(self): srcFiles = self.sourceRaster() @@ -619,18 +604,17 @@ class VRTRaster(QObject): elif len(srcFiles) == 0: self.setCrs(None) - if len(toRemove) > 0: self.sigSourceRasterRemoved.emit(toRemove) if len(toAdd) > 0: self.sigSourceRasterAdded.emit(toAdd) - def loadVRT(self, pathVRT, bandIndex = None): + def loadVRT(self, pathVRT, bandIndex=None): """ Load the VRT definition in pathVRT and appends it to this VRT :param pathVRT: """ - if pathVRT in [None,'']: + if pathVRT in [None, '']: return if bandIndex is None: @@ -641,10 +625,9 @@ class VRTRaster(QObject): assert ds.GetDriver().GetDescription() == 'VRT' for b in range(ds.RasterCount): - srcBand = ds.GetRasterBand(b+1) + srcBand = ds.GetRasterBand(b + 1) vrtBand = VRTRasterBand(name=srcBand.GetDescription().decode('utf-8')) for key, xml in srcBand.GetMetadata(str('vrt_sources')).items(): - tree = ElementTree.fromstring(xml) srcPath = tree.find('SourceFilename').text srcBandIndex = int(tree.find('SourceBand').text) @@ -653,10 +636,7 @@ class VRTRaster(QObject): self.insertVirtualBand(bandIndex, vrtBand) bandIndex += 1 - - - - def saveVRT(self, pathVRT, warpedImageFolder = '.warpedimage'): + def saveVRT(self, pathVRT, warpedImageFolder='.warpedimage'): """ Save the VRT to path. If source images need to be warped to the final CRS warped VRT image will be created in a folder <directory>/<basename>+<warpedImageFolder>/ @@ -695,7 +675,7 @@ class VRTRaster(QObject): if crs == self.mCrs: srcLookup[pathSrc] = pathSrc else: - #do a CRS transformation using VRTs + # do a CRS transformation using VRTs warpedFileName = 'warped.{}.vrt'.format(os.path.basename(pathSrc)) if inMemory: @@ -710,7 +690,7 @@ class VRTRaster(QObject): assert isinstance(tmp, gdal.Dataset) vrtXML = read_vsimem(warpedFileName) xml = ElementTree.fromstring(vrtXML) - #print(vrtXML.decode('utf-8')) + # print(vrtXML.decode('utf-8')) if False: dsTmp = gdal.Open(warpedFileName) @@ -723,7 +703,7 @@ class VRTRaster(QObject): srcFiles = [srcLookup[src] for src in self.sourceRaster()] - #these need to be set + # these need to be set ns = nl = gt = crs = eType = None res = self.resolution() extent = self.extent() @@ -742,7 +722,7 @@ class VRTRaster(QObject): kwds['xRes'] = res.width() kwds['yRes'] = res.height() else: - assert res in ['highest','lowest','average'] + assert res in ['highest', 'lowest', 'average'] kwds['resolution'] = res if isinstance(extent, QgsRectangle): @@ -751,8 +731,6 @@ class VRTRaster(QObject): if srs is not None: kwds['outputSRS'] = srs - - pathInMEMVRT = '/vsimem/{}.vrt'.format(uuid.uuid4()) vro = gdal.BuildVRTOptions(separate=True, **kwds) dsVRTDst = gdal.BuildVRT(pathInMEMVRT, srcFiles, options=vro) @@ -765,10 +743,10 @@ class VRTRaster(QObject): eType = dsVRTDst.GetRasterBand(1).DataType SOURCE_TEMPLATES = dict() for i, srcFile in enumerate(srcFiles): - vrt_sources = dsVRTDst.GetRasterBand(i+1).GetMetadata(str('vrt_sources')) + vrt_sources = dsVRTDst.GetRasterBand(i + 1).GetMetadata(str('vrt_sources')) assert len(vrt_sources) == 1 srcXML = vrt_sources['source_0'] - assert os.path.basename(srcFile)+'</SourceFilename>' in srcXML + assert os.path.basename(srcFile) + '</SourceFilename>' in srcXML assert '<SourceBand>1</SourceBand>' in srcXML SOURCE_TEMPLATES[srcFile] = srcXML @@ -776,7 +754,7 @@ class VRTRaster(QObject): else: # special case: no source files defined - ns = nl = 1 #this is the minimum size + ns = nl = 1 # this is the minimum size if isinstance(extent, QgsRectangle): x0 = extent.xMinimum() y1 = extent.yMaximum() @@ -794,38 +772,37 @@ class VRTRaster(QObject): gt = (x0, resx, 0, y1, 0, -resy) eType = gdal.GDT_Float32 - #2. build final VRT from scratch + # 2. build final VRT from scratch drvVRT = gdal.GetDriverByName('VRT') assert isinstance(drvVRT, gdal.Driver) - dsVRTDst = drvVRT.Create(pathVRT, ns, nl,0, eType=eType) - #2.1. set general properties + dsVRTDst = drvVRT.Create(pathVRT, ns, nl, 0, eType=eType) + # 2.1. set general properties assert isinstance(dsVRTDst, gdal.Dataset) if srs is not None: dsVRTDst.SetProjection(srs) dsVRTDst.SetGeoTransform(gt) - #2.2. add virtual bands + # 2.2. add virtual bands for i, vBand in enumerate(self.mBands): assert isinstance(vBand, VRTRasterBand) assert dsVRTDst.AddBand(eType, options=['subClass=VRTSourcedRasterBand']) == 0 - vrtBandDst = dsVRTDst.GetRasterBand(i+1) + vrtBandDst = dsVRTDst.GetRasterBand(i + 1) assert isinstance(vrtBandDst, gdal.Band) vrtBandDst.SetDescription(vBand.name()) md = {} - #add all input sources for this virtual band + # add all input sources for this virtual band for iSrc, sourceInfo in enumerate(vBand.mSources): assert isinstance(sourceInfo, VRTRasterInputSourceBand) bandIndex = sourceInfo.mBandIndex xml = SOURCE_TEMPLATES[srcLookup[sourceInfo.mPath]] - xml = re.sub('<SourceBand>1</SourceBand>', '<SourceBand>{}</SourceBand>'.format(bandIndex+1), xml) + xml = re.sub('<SourceBand>1</SourceBand>', '<SourceBand>{}</SourceBand>'.format(bandIndex + 1), xml) md['source_{}'.format(iSrc)] = xml - vrtBandDst.SetMetadata(md,'vrt_sources') - + vrtBandDst.SetMetadata(md, 'vrt_sources') dsVRTDst = None - #check if we get what we like to get + # check if we get what we like to get dsCheck = gdal.Open(pathVRT) assert isinstance(dsCheck, gdal.Dataset) @@ -855,9 +832,6 @@ class VRTRaster(QObject): return iter(self.mClasses) - - - def createVirtualBandMosaic(bandFiles, pathVRT): drv = gdal.GetDriverByName('VRT') @@ -871,15 +845,15 @@ def createVirtualBandMosaic(bandFiles, pathVRT): separate=False ) if len(bandFiles) > 1: - s ="" + s = "" vrtDS = gdal.BuildVRT(pathVRT, bandFiles, options=vrtOptions) vrtDS.FlushCache() assert vrtDS.RasterCount == nb return vrtDS -def createVirtualBandStack(bandFiles, pathVRT): +def createVirtualBandStack(bandFiles, pathVRT): nb = len(bandFiles) drv = gdal.GetDriverByName('VRT') @@ -898,9 +872,9 @@ def createVirtualBandStack(bandFiles, pathVRT): assert vrtDS.RasterCount == nb - #copy band metadata from + # copy band metadata from for i in range(nb): - band = vrtDS.GetRasterBand(i+1) + band = vrtDS.GetRasterBand(i + 1) band.SetDescription(bandFiles[i]) band.ComputeBandStats() @@ -910,7 +884,6 @@ def createVirtualBandStack(bandFiles, pathVRT): return vrtDS - class RasterBounds(object): def __init__(self, path): self.path = None @@ -921,7 +894,6 @@ class RasterBounds(object): if path is not None: self.fromImage(path) - def fromImage(self, path): self.path = path ds = gdal.Open(path) @@ -955,4 +927,3 @@ class RasterBounds(object): def __repr__(self): return self.polygon.asWkt() -