diff --git a/make/createtestdata.py b/make/createtestdata.py new file mode 100644 index 0000000000000000000000000000000000000000..e86e535065da29ebeac95be2a62a2e82f5dcbe82 --- /dev/null +++ b/make/createtestdata.py @@ -0,0 +1,284 @@ +import sys, re, os, collections +from os.path import join as jp + + +from osgeo import gdal, ogr +from qgis import * +from qgis.core import * +from qgis.gui import * +from PyQt4.QtGui import * +from PyQt4.QtCore import * + +from timeseriesviewer import * +from timeseriesviewer.utils import * +from timeseriesviewer import file_search +from timeseriesviewer.timeseries import * +from timeseriesviewer.virtualrasters import VirtualBandInputSource, VirtualBand, VirtualRasterBuilder + +def groupCBERS(dirIn, dirOut, pattern='CBERS*.tif'): + files = file_search(dirIn, pattern, recursive=True) + + if not os.path.exists(dirOut): + os.mkdir(dirOut) + + CONTAINERS = dict() + for file in files: + dn = os.path.dirname(file) + bn = os.path.basename(file) + #basenames like CBERS_4_MUX_20150603_167_107_L4_BAND5_GRID_SURFACE.tif + splitted = bn.split('_') + id = '_'.join(splitted[:4]) + bandName = splitted[7] + + if id not in CONTAINERS.keys(): + CONTAINERS[id] = dict() + + bandSources = CONTAINERS[id] + if bandName not in bandSources.keys(): + bandSources[bandName] = list() + bandSources[bandName].append(file) + + #mosaic all scenes of same date + # and stack all bands related to the same channel + for id, bandSources in CONTAINERS.items(): + + pathVRT = id + '.vrt' + pathVRT = os.path.join(dirOut, pathVRT) + V = VirtualRasterBuilder() + + #vrt = createVirtualBandStack(bandSources, pathVRT) + #add bands in sorted order + for bandName in sorted(bandSources.keys()): + vBandSources = bandSources[bandName] + VB = VirtualBand(name=bandName) + for path in vBandSources: + VB.addSourceBand(path, 0) #it's always one band only + + V.addVirtualBand(VB) + #print(V) + dsVRT = V.saveVRT(pathVRT) + assert isinstance(dsVRT, gdal.Dataset) + #add center wavelength information to ENVI domain + import math + #MUXCAM bands + #see http://www.cbers.inpe.br/ingles/satellites/cameras_cbers3_4.php + cwl = [0.5*(0.45+0.52), + 0.5*(0.52 + 0.59), + 0.5*(0.63 + 0.69), + 0.5*(0.77 + 0.89)] + #https://www.harrisgeospatial.com/docs/ENVIHeaderFiles.html + dsVRT.SetMetadataItem('wavelength units','Micrometers', 'ENVI') + dsVRT.SetMetadataItem('wavelength', '{{{}}}'.format(','.join([str(w) for w in cwl])), 'ENVI') + dsVRT.SetMetadataItem('sensor type', 'CBERS', 'ENVI') + for i, cw in enumerate(cwl): + band = dsVRT.GetRasterBand(i+1) + assert isinstance(band, gdal.Band) + band.SetMetadataItem('wavelength',str(cw), 'ENVI') + dsVRT = None + + +def groupLandsat(dirIn, dirOut, pattern='L*_sr_band*.img'): + files = file_search(dirIn, pattern, recursive=True) + + if not os.path.exists(dirOut): + os.mkdir(dirOut) + + CONTAINERS = dict() + for file in files: + dn = os.path.dirname(file) + bn = os.path.basename(file) + #basenames like LC82270652013140LGN01_sr_band2.img + splitted = re.split('[_\.]', bn) + id = splitted[0] + bandName = splitted[2] + + if id not in CONTAINERS.keys(): + CONTAINERS[id] = dict() + + bandSources = CONTAINERS[id] + if bandName not in bandSources.keys(): + bandSources[bandName] = list() + bandSources[bandName].append(file) + + #mosaic all scenes of same date + # and stack all bands related to the same channel + for id, bandSources in CONTAINERS.items(): + + pathVRT = id + '.vrt' + pathVRT = os.path.join(dirOut, pathVRT) + V = VirtualRasterBuilder() + + #vrt = createVirtualBandStack(bandSources, pathVRT) + #add bands in sorted order + for bandName in sorted(bandSources.keys()): + vBandSources = bandSources[bandName] + VB = VirtualBand(name=bandName) + for path in vBandSources: + VB.addSourceBand(path, 0) #it's always one band only + + V.addVirtualBand(VB) + #print(V) + dsVRT = V.saveVRT(pathVRT) + assert isinstance(dsVRT, gdal.Dataset) + #add center wavelength information to ENVI domain + import math + #MUXCAM bands + #see http://www.cbers.inpe.br/ingles/satellites/cameras_cbers3_4.php + if re.search('LC8', id): + cwl = [0.5*(0.433 + 0.453), + 0.5*(0.450 + 0.515), + 0.5*(0.525 + 0.600), + 0.5*(0.630 + 0.680), + 0.5 * (0.845 + 0.885), + 0.5 * (1.560 + 1.660), + 0.5 * (2.100 + 2.300)] + else: + raise NotImplementedError() + + #https://www.harrisgeospatial.com/docs/ENVIHeaderFiles.html + dsVRT.SetMetadataItem('wavelength units','Micrometers', 'ENVI') + dsVRT.SetMetadataItem('wavelength', '{{{}}}'.format(','.join([str(w) for w in cwl])), 'ENVI') + dsVRT.SetMetadataItem('sensor type', 'Landsat-8 OLI', 'ENVI') + from timeseriesviewer.dateparser import datetime64FromYYYYDOY + dt = datetime64FromYYYYDOY(id[9:16]) + assert dt > np.datetime64('1900-01-01') + assert dt < np.datetime64('2999-12-31') + dsVRT.SetMetadataItem('acquisition time', str(dt), 'ENVI') + for i, cw in enumerate(cwl): + band = dsVRT.GetRasterBand(i+1) + assert isinstance(band, gdal.Band) + band.SetMetadataItem('wavelength',str(cw), 'ENVI') + dsVRT = None + + +def groupRapidEyeTiles(dirIn, dirOut): + """ + + :param dirIn: + :param dirOut: + :return: + """ + + files = file_search(dirIn, '*_RE*_3A_*2.tif', recursive=True) + + if not os.path.exists(dirOut): + os.mkdir(dirOut) + + sources = dict() + for file in files: + if not file.endswith('.tif'): + continue + dn = os.path.dirname(file) + bn = os.path.basename(file) + print(bn) + id, date, sensor, product, _ = tuple(bn.split('_')) + + if not date in sources.keys(): + sources[date] = [] + sources[date].append(file) + + from timeseriesviewer.virtualrasters import createVirtualBandMosaic + for date, files in sources.items(): + pathVRT = os.path.join(dirOut, 're_{}.vrt'.format(date)) + + + dsVRT = createVirtualBandMosaic(files, pathVRT) + cwl = [0.5 * (440 + 510), + 0.5 * (520 + 590), + 0.5 * (630 + 685), + 0.5 * (690 + 730), + 0.5 * (760 + 850)] + + dsVRT.SetMetadataItem('wavelength units', 'Nanometers', 'ENVI') + dsVRT.SetMetadataItem('wavelength', '{{{}}}'.format(','.join([str(w) for w in cwl])), 'ENVI') + dsVRT.SetMetadataItem('sensor type', 'RapidEye', 'ENVI') + dsVRT = createVirtualBandMosaic(files, pathVRT) + + +def copyMetadataDomains(dsSrc, dsDst, domains=['ENVI']): + """ + Updates metadata. + :param dsSrc: + :param dsDst: + :param domains: + :return: + """ + assert isinstance(dsSrc, gdal.Dataset) + assert isinstance(dsDst, gdal.Dataset) + + for domain in domains: + mdDst = dsDst.GetMetadata(domain) + mdDst.update(dsSrc.GetMetadata(domain)) + dsDst.SetMetadata(mdDst, domain) + for i in range(min([dsSrc.RasterCount, dsDst.RasterCount])): + bandSrc = dsSrc.GetRasterBand(i+1) + bandDst = dsDst.GetRasterBand(i+1) + mdDst = bandDst.GetMetadata(domain) + mdDst.update(bandSrc.GetMetadata(domain)) + bandDst.SetMetadata(mdDst, domain) + +def addPyramids(dir, pattern, levels=None): + + for file in file_search(dir, pattern): + ds = gdal.Open(file) + assert isinstance(ds, gdal.Dataset) + nb = ds.RasterCount + #BuildOverviews(Dataset self, char const * resampling="NEAREST", int overviewlist=0, + # GDALProgressFunc callback=0, + # void * callback_data=None) -> int + res = ds.BuildOverviews('NEAREST', [2,4,8]) + + + + +def vrt2Binary(dirVRTs, dirBins, drvNameDst='GTiff', recursive=False, overwrite=True): + pathVRTs = file_search(dirVRTs, '*.vrt', recursive=recursive) + if not os.path.isdir(dirBins): + os.mkdir(dirBins) + drvDst = gdal.GetDriverByName(drvNameDst) + assert isinstance(drvDst, gdal.Driver) + for pathVRT in pathVRTs: + bn = os.path.basename(pathVRT) + bn = os.path.splitext(bn)[0] + pathDst = os.path.join(dirBins, '{}.{}'.format(bn, drvDst.GetMetadataItem('DMD_EXTENSION'))) + dsSrc = gdal.Open(pathVRT) + assert isinstance(dsSrc, gdal.Dataset) + if overwrite or not os.path.exists(pathDst): + options = gdal.TranslateOptions(format=drvDst.ShortName) + print('Write {}...'.format(pathDst)) + dsDst = gdal.Translate(pathDst, dsSrc, options=options) + assert isinstance(dsDst, gdal.Dataset) + copyMetadataDomains(dsSrc, dsDst, ['ENVI']) + + + +def testdataMultitemp2017(): + + dirSrcCBERS = r'O:\SenseCarbonProcessing\BJ_Multitemp2017\01_Data\CBERS\Data' + dirVRTCBERS = r'O:\SenseCarbonProcessing\BJ_Multitemp2017\01_Data\CBERS' + dirSrcL8 = r'O:\SenseCarbonProcessing\BJ_Multitemp2017\01_Data\Landsat\L1T' + dirVRTL8 = r'O:\SenseCarbonProcessing\BJ_Multitemp2017\01_Data\Landsat' + dirSrcRE = r'O:\SenseCarbonProcessing\BJ_Multitemp2017\01_Data\RapidEye\3A' + dirVRTRE = r'O:\SenseCarbonProcessing\BJ_Multitemp2017\01_Data\RapidEye' + + if False: + groupCBERS(dirSrcCBERS, dirVRTCBERS, '*CBERS*167_108_*.tif') + + if False: + groupLandsat(dirSrcL8, dirVRTL8) + + if True: + #groupRapidEyeTiles(dirSrcRE, dirVRTRE) + addPyramids(dirVRTRE, '*.vrt', levels=[2,4,8]) + + if True: + dirBinL8 = r'O:\SenseCarbonProcessing\BJ_Multitemp2017\01_Data\Landsat\L1TS' + gdal.SetCacheMax(100*2*20) + vrt2Binary(dirVRTL8, dirBinL8, overwrite=False) + + +if __name__ == '__main__': + testdataMultitemp2017() + + + print('done') \ No newline at end of file diff --git a/timeseriesviewer/__init__.py b/timeseriesviewer/__init__.py index 918ea9a039450c7b74f36382d3e32e7fdbc9d630..8c33c18dc326fe683ac7de45d93d4b647bcb86ec 100644 --- a/timeseriesviewer/__init__.py +++ b/timeseriesviewer/__init__.py @@ -7,6 +7,8 @@ REPOSITORY = 'https://bitbucket.org/jakimowb/hub-timeseriesviewer.git' import os, sys, fnmatch, site import six, logging +from qgis.core import * +from qgis.gui import * logger = logging.getLogger(__name__) diff --git a/timeseriesviewer/dateparser.py b/timeseriesviewer/dateparser.py index 3f9a3620800eb3ead1ef1eb34c65ecdc8e2326f6..9784cde076296cac5477ac65f40adbe70e6e0b56 100644 --- a/timeseriesviewer/dateparser.py +++ b/timeseriesviewer/dateparser.py @@ -8,13 +8,16 @@ logger = logging.getLogger(__file__) #thanks user "funkwurm" in #http://stackoverflow.com/questions/28020805/regex-validate-correct-iso8601-date-string-with-time -regISODate = 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)') - +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>[0-9]{4})(?P<hyphen>-?)(?P<month>1[0-2]|0[1-9])(?P=hyphen)(?P<day>3[01]|0[1-9]|[12][0-9])') +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])') +regMissingHypen = re.compile('^\d{8}') regYYYYMM = re.compile(r'([0-9]{4})-(1[0-2]|0[1-9])') -regYYYYDOY = re.compile(r'(?P<year>[0-9]{4})-?(?P<day>36[0-6]|3[0-5][0-9]|[12][0-9]{2}|0[1-9][0-9]|00[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])') def matchOrNone(regex, text): match = regex.search(text) @@ -23,23 +26,42 @@ def matchOrNone(regex, text): else: return None -def extractDateTimeGroup(regex, text): +def extractDateTimeGroup(text): + match = regISODate1.search(text) + if match: + matchedText = match.group() + if regMissingHypen.search(matchedText): + matchedText = '{}-{}-{}'.format(matchedText[0:4],matchedText[4:6],matchedText[6:]) + return np.datetime64(matchedText) - match = regex.search(text) - if match is not None: + match = regYYYYMMDD.search(text) + if match: + return datetime64FromYYYYMMDD(match.group()) + + match = regYYYYDOY.search(text) + if match: + return datetime64FromYYYYDOY(match.group()) + + match = regYYYYMM.search(text) + if match: return np.datetime64(match.group()) - else: - return None + return None + +def datetime64FromYYYYMMDD(yyyymmdd): + if re.search('^\d{8}$', yyyymmdd): + #insert hyphens + yyyymmdd = '{}-{}-{}'.format(yyyymmdd[0:4],yyyymmdd[4:6],yyyymmdd[6:8]) + return np.datetime64(yyyymmdd) -def getDateTime64FromYYYYDOY(yyyydoy): - return getDatetime64FromDOY(yyyydoy[0:4], yyyydoy[4:7]) +def datetime64FromYYYYDOY(yyyydoy): + return datetime64FromDOY(yyyydoy[0:4], yyyydoy[4:7]) -def getDOYfromDatetime64(dt): +def DOYfromDatetime64(dt): return (dt.astype('datetime64[D]') - dt.astype('datetime64[Y]')).astype(int)+1 -def getDatetime64FromDOY(year, doy): +def datetime64FromDOY(year, doy): if type(year) is str: year = int(year) if type(doy) is str: @@ -87,12 +109,12 @@ class ImageDateReaderDefault(ImageDateReader): # search for ISO date in basename # search in basename - for reg in [regISODate, regYYYYMMDD, regYYYYDOY, regYYYYMM]: - dtg = extractDateTimeGroup(reg, self.baseName) - if dtg: return dtg - for reg in [regISODate, regYYYYMMDD, regYYYYDOY, regYYYYMM]: - dtg = extractDateTimeGroup(reg, self.dirName) - if dtg: return dtg + dtg = extractDateTimeGroup(self.baseName) + if dtg: + return dtg + dtg = extractDateTimeGroup(self.dirName) + if dtg: + return dtg return None class ImageDateReaderPLEIADES(ImageDateReader): @@ -140,7 +162,7 @@ class ImageDateParserLandsat(ImageDateReader): #search for LandsatSceneID (old) and Landsat Product IDs (new) sceneID = matchOrNone(ImageDateParserLandsat.regLandsatSceneID, self.baseName) if sceneID: - return getDateTime64FromYYYYDOY(sceneID[9:16]) + return datetime64FromYYYYDOY(sceneID[9:16]) productID = matchOrNone(ImageDateParserLandsat.regLandsatProductID, self.baseName) if productID: @@ -166,4 +188,6 @@ if __name__ == '__main__': p = r'E:\_EnMAP\temp\temp_bj\landsat\37S\EB\LE71720342015009SG100\LE71720342015009SG100_sr.tif' p = r'D:\Repositories\QGIS_Plugins\hub-timeseriesviewer\example\Images\2012-04-07_LE72270652012098EDC00_BOA.bsq' ds = gdal.Open(p) + + print(datetime64FromYYYYMMDD('20141212')) print(parseDateFromDataSet(ds)) \ No newline at end of file diff --git a/timeseriesviewer/main.py b/timeseriesviewer/main.py index 99275de378612f3706f447f0d0fc280198d704d4..bf1cdf540787f5dbbfa20243aab6b97fb5bab237 100644 --- a/timeseriesviewer/main.py +++ b/timeseriesviewer/main.py @@ -156,12 +156,16 @@ class QgisTsvBridge(QObject): self.syncBlocked = False from main import TimeSeriesViewerUI + from timeseriesviewer.ui.docks import RenderingDockUI assert isinstance(self.ui, TimeSeriesViewerUI) + assert isinstance(self.ui.dockRendering, RenderingDockUI) + + self.ui.dockRendering.sigQgisInteractionRequest.connect(self.onQgisInteractionRequest) self.cbQgsVectorLayer = self.ui.dockRendering.cbQgsVectorLayer self.gbQgsVectorLayer = self.ui.dockRendering.gbQgsVectorLayer - self.cbQgsVectorLayer.setEnabled(True) - self.gbQgsVectorLayer.setEnabled(True) + #self.cbQgsVectorLayer.setEnabled(True) + #self.gbQgsVectorLayer.setEnabled(True) self.qgsMapCanvas = self.iface.mapCanvas() assert isinstance(self.qgsMapCanvas, QgsMapCanvas) @@ -178,6 +182,47 @@ class QgisTsvBridge(QObject): self.cbQgsVectorLayer.layerChanged.connect(self.onQgsVectorLayerChanged) self.onQgsVectorLayerChanged(None) + def onQgisInteractionRequest(self, request): + assert isinstance(self.qgsMapCanvas, QgsMapCanvas) + extQgs = SpatialExtent.fromMapCanvas(self.qgsMapCanvas) + + assert isinstance(self.TSV, TimeSeriesViewer) + extTsv = self.TSV.spatialTemporalVis.spatialExtent() + + assert request in ['tsvCenter2qgsCenter', + 'tsvExtent2qgsExtent', + 'qgisCenter2tsvCenter', + 'qgisExtent2tsvExtent'] + + if request == 'tsvCenter2qgsCenter': + center = SpatialPoint.fromSpatialExtent(extTsv) + center = center.toCrs(extQgs.crs()) + if center: + self.qgsMapCanvas.setCenter(center) + self.qgsMapCanvas.refresh() + + if request == 'qgisCenter2tsvCenter': + center = SpatialPoint.fromSpatialExtent(extQgs) + center = center.toCrs(extTsv.crs()) + if center: + self.TSV.spatialTemporalVis.setSpatialCenter(center) + + + if request == 'tsvExtent2qgsExtent': + extent = extTsv.toCrs(extQgs.crs()) + if extent: + self.qgsMapCanvas.setExtent(extent) + self.qgsMapCanvas.refresh() + + if request == 'qgisExtent2tsvExtent': + extent = extQgs.toCrs(extTsv.crs()) + if extent: + self.TSV.spatialTemporalVis.setSpatialExtent(extent) + + + + + def syncTsvWithQgs(self, *args): if self.syncBlocked: return @@ -340,8 +385,6 @@ class TimeSeriesViewerUI(QMainWindow, #todo: move to QGS_TSV_Bridge self.dockRendering.cbQgsVectorLayer.setFilters(QgsMapLayerProxyModel.VectorLayer) - #define subset-size behaviour - self.restoreSettings() @@ -357,8 +400,8 @@ class TimeSeriesViewerUI(QMainWindow, from timeseriesviewer import QGIS_TSV_BRIDGE from timeseriesviewer.main import QgisTsvBridge b = isinstance(QGIS_TSV_BRIDGE, QgisTsvBridge) - self.dockRendering.gbSyncQgs.setEnabled(b) - self.dockRendering.gbQgsVectorLayer.setEnabled(b) + self.dockRendering.enableQgisSyncronization(b) + #self.dockRendering.gbQgsVectorLayer.setEnabled(b) def _blockSignals(self, widgets, block=True): states = dict() @@ -691,8 +734,14 @@ class TimeSeriesViewer: def addTimeSeriesImages(self, files=None): if files is None: - files = QFileDialog.getOpenFileNames() - + s = QSettings('HU-Berlin','HUB TSV') + defDir = s.value('DIR_FILESEARCH') + files = QFileDialog.getOpenFileNames(directory=defDir) + + if len(files) > 0 and os.path.exists(files[0]): + dn = os.path.dirname(files[0]) + s.setValue('DIR_FILESEARCH', dn) + s = "" #collect sublayers, if existing diff --git a/timeseriesviewer/mapcanvas.py b/timeseriesviewer/mapcanvas.py index bdf89e8809bae5add9c479782df38337e93270d6..619574b02c83886d98dbe8f204473c9551b2981c 100644 --- a/timeseriesviewer/mapcanvas.py +++ b/timeseriesviewer/mapcanvas.py @@ -8,14 +8,13 @@ from qgis.gui import * from PyQt4.QtCore import * from PyQt4.QtGui import * from timeseriesviewer import SETTINGS -from timeseriesviewer.timeseries import TimeSeriesDatum -from mapvisualization import MapView from timeseriesviewer.utils import * from timeseriesviewer.ui.widgets import TsvScrollArea +class MapCanvas(QgsMapCanvas): + -class MapCanvas(QgsMapCanvas): from timeseriesviewer.main import SpatialExtent, SpatialPoint saveFileDirectories = dict() sigShowProfiles = pyqtSignal(SpatialPoint) @@ -34,6 +33,7 @@ class MapCanvas(QgsMapCanvas): self.setCanvasColor(SETTINGS.value('CANVAS_BACKGROUND_COLOR', QColor(0, 0, 0))) self.setContextMenuPolicy(Qt.DefaultContextMenu) + #refreshTimer.timeout.connect(self.onTimerRefresh) self.extentsChanged.connect(lambda : self.sigSpatialExtentChanged.emit(self.spatialExtent())) @@ -51,7 +51,7 @@ class MapCanvas(QgsMapCanvas): - self.renderMe = False + self.mDataRefreshRequired = False @@ -78,7 +78,7 @@ class MapCanvas(QgsMapCanvas): assert isinstance(crs, QgsCoordinateReferenceSystem) if self.crs() != crs: self.setDestinationCrs(crs) - + self.mDataRefreshRequired = True def crs(self): return self.mapSettings().destinationCrs() @@ -122,16 +122,22 @@ class MapCanvas(QgsMapCanvas): def setLayers(self, mapLayers): reg = QgsMapLayerRegistry.instance() - for l in mapLayers: - reg.addMapLayer(l, False) + reg.addMapLayers(mapLayers, False) + self.mLayers = mapLayers[:] + self.mDataRefreshRequired = True super(MapCanvas, self).setLayerSet([QgsMapCanvasLayer(l) for l in self.mLayers]) - s = "" + + #self.refresh() + def refresh(self): - self.setLayers(self.mapLayersToRender()) - self.setRenderMe() - super(MapCanvas, self).refresh() - self.refreshAllLayers() + #low-level, only performed if MapCanvas is visible + self.checkRenderFlag() + if self.renderFlag(): + print('REFRESH {}'.format(self.objectName())) + self.setLayers(self.mapLayersToRender()) + #super(MapCanvas, self).refresh() + self.refreshAllLayers() def setCrosshairStyle(self,crosshairStyle): @@ -146,14 +152,21 @@ class MapCanvas(QgsMapCanvas): def setShowCrosshair(self,b): self.crosshairItem.setShow(b) - def setRenderMe(self): - oldFlag = self.renderFlag() + def onTimerRefresh(self): + self.checkRenderFlag() + self.refresh() - newFlag = self.visibleRegion().boundingRect().isValid() \ + def checkRenderFlag(self): + isVisible = self.visibleRegion().boundingRect().isValid() \ and self.isVisible() - #and self.tsdView.timeSeriesDatum.isVisible() - if oldFlag != newFlag: - self.setRenderFlag(newFlag) + if not isVisible: + self.setRenderFlag(False) + else: + isRequired = self.renderFlag() or self.mDataRefreshRequired and not self.signalsBlocked() + self.setRenderFlag(isRequired) + + def layerPaths(self): + return [str(l.source()) for l in self.layers()] def pixmap(self): """ @@ -195,7 +208,7 @@ class MapCanvas(QgsMapCanvas): action = m.addAction('Map to Clipboard') action.triggered.connect(lambda: QApplication.clipboard().setPixmap(self.pixmap())) action = m.addAction('Image Path') - action.triggered.connect(lambda: QApplication.clipboard().setText(self.tsdView.TSD.pathImg)) + action.triggered.connect(lambda: QApplication.clipboard().setText('\n'.join(self.layerPaths()))) action = m.addAction('Image Style') #action.triggered.connect(lambda: QApplication.clipboard().setPixmap(self.tsdView.TSD.pathImg)) @@ -234,7 +247,7 @@ class MapCanvas(QgsMapCanvas): def stretchToCurrentExtent(self): se = self.spatialExtent() - + ceAlg = QgsContrastEnhancement.StretchToMinimumMaximum for l in self.layers(): if isinstance(l, QgsRasterLayer): r = l.renderer() @@ -248,7 +261,10 @@ class MapCanvas(QgsMapCanvas): def getCE(band, ce): stats = dp.bandStatistics(band, QgsRasterBandStats.All, extent, 500) - ce = QgsContrastEnhancement(ce) + if stats.maximumValue is None: + s = "" + ce = QgsContrastEnhancement(dp.dataType(band)) + ce.setContrastEnhancementAlgorithm(ceAlg) ce.setMinimumValue(stats.minimumValue) ce.setMaximumValue(stats.maximumValue) return ce @@ -278,7 +294,8 @@ class MapCanvas(QgsMapCanvas): elif key in self.mMapTools.keys(): super(MapCanvas, self).setMapTool(self.mMapTools[key]) else: - logger.error('unknown map tool key "{}"'.format(key)) + s = "" + #logger.error('unknown map tool key "{}"'.format(key)) def saveMapImageDialog(self, fileType): lastDir = SETTINGS.value('CANVAS_SAVE_IMG_DIR', os.path.expanduser('~')) @@ -290,6 +307,8 @@ class MapCanvas(QgsMapCanvas): SETTINGS.setValue('CANVAS_SAVE_IMG_DIR', os.path.dirname(path)) + + def setRenderer(self, renderer): #lyrs = [l for l in self.mapLayersToRender() if str(l.source()) == targetLayerUri] @@ -318,8 +337,7 @@ class MapCanvas(QgsMapCanvas): for lyr in lyrs: lyr.setRenderer(renderer) - if not self.signalsBlocked(): - self.refresh() + self.refresh() def setSpatialExtent(self, spatialExtent): assert isinstance(spatialExtent, SpatialExtent) @@ -327,9 +345,7 @@ class MapCanvas(QgsMapCanvas): spatialExtent = spatialExtent.toCrs(self.crs()) if spatialExtent: self.setExtent(spatialExtent) - - if not self.signalsBlocked(): - self.refresh() + self.mDataRefreshRequired = True def spatialExtent(self): diff --git a/timeseriesviewer/maptools.py b/timeseriesviewer/maptools.py index 9bfead0c5b959ca78bbeb2723442aceb4b1b2d53..606d215b4b7c7c7acb58fa7be8b1b47773f74877 100644 --- a/timeseriesviewer/maptools.py +++ b/timeseriesviewer/maptools.py @@ -26,7 +26,7 @@ class CursorLocationMapTool(QgsMapToolEmitPoint): self.rubberband = QgsRubberBand(self.canvas, QGis.Polygon) color = QColor('red') - + self.mButtons = [Qt.LeftButton] self.rubberband.setLineStyle(Qt.SolidLine) self.rubberband.setColor(color) self.rubberband.setWidth(2) @@ -38,9 +38,15 @@ class CursorLocationMapTool(QgsMapToolEmitPoint): self.marker.setIconSize(5) self.marker.setIconType(QgsVertexMarker.ICON_CROSS) # or ICON_CROSS, ICON_X + def setMouseButtons(self, listOfButtons): + assert isinstance(listOfButtons) + self.mButtons = listOfButtons + def canvasPressEvent(self, e): - geoPoint = self.toMapCoordinates(e.pos()) - self.marker.setCenter(geoPoint) + assert isinstance(e, QgsMapMouseEvent) + if e.button() in self.mButtons: + geoPoint = self.toMapCoordinates(e.pos()) + self.marker.setCenter(geoPoint) #self.marker.show() def setStyle(self, color=None, brushStyle=None, fillColor=None, lineStyle=None): @@ -53,28 +59,28 @@ class CursorLocationMapTool(QgsMapToolEmitPoint): if lineStyle: self.rubberband.setLineStyle(lineStyle) def canvasReleaseEvent(self, e): - - - pixelPoint = e.pixelPoint() - - crs = self.canvas.mapSettings().destinationCrs() - self.marker.hide() - geoPoint = self.toMapCoordinates(pixelPoint) - if self.mShowCrosshair: - #show a temporary crosshair - ext = SpatialExtent.fromMapCanvas(self.canvas) - cen = geoPoint - geom = QgsGeometry() - geom.addPart([QgsPoint(ext.upperLeftPt().x(),cen.y()), QgsPoint(ext.lowerRightPt().x(), cen.y())], - QGis.Line) - geom.addPart([QgsPoint(cen.x(), ext.upperLeftPt().y()), QgsPoint(cen.x(), ext.lowerRightPt().y())], - QGis.Line) - self.rubberband.addGeometry(geom, None) - self.rubberband.show() - #remove crosshair after 0.25 sec - QTimer.singleShot(250, self.hideRubberband) - - self.sigLocationRequest.emit(SpatialPoint(crs, geoPoint)) + if e.button() in self.mButtons: + + pixelPoint = e.pixelPoint() + + crs = self.canvas.mapSettings().destinationCrs() + self.marker.hide() + geoPoint = self.toMapCoordinates(pixelPoint) + if self.mShowCrosshair: + #show a temporary crosshair + ext = SpatialExtent.fromMapCanvas(self.canvas) + cen = geoPoint + geom = QgsGeometry() + geom.addPart([QgsPoint(ext.upperLeftPt().x(),cen.y()), QgsPoint(ext.lowerRightPt().x(), cen.y())], + QGis.Line) + geom.addPart([QgsPoint(cen.x(), ext.upperLeftPt().y()), QgsPoint(cen.x(), ext.lowerRightPt().y())], + QGis.Line) + self.rubberband.addGeometry(geom, None) + self.rubberband.show() + #remove crosshair after 0.25 sec + QTimer.singleShot(250, self.hideRubberband) + + self.sigLocationRequest.emit(SpatialPoint(crs, geoPoint)) def hideRubberband(self): self.rubberband.reset() diff --git a/timeseriesviewer/mapvisualization.py b/timeseriesviewer/mapvisualization.py index 740dd074b90d2772f221e99fb1a5fadbcf46b056..119f55416ef976381fa00aeb4af143b905f8a4cc 100644 --- a/timeseriesviewer/mapvisualization.py +++ b/timeseriesviewer/mapvisualization.py @@ -12,7 +12,7 @@ from timeseriesviewer.main import TimeSeriesViewer from timeseriesviewer.timeseries import SensorInstrument, TimeSeriesDatum from timeseriesviewer.ui.docks import TsvDockWidgetBase, load from timeseriesviewer.ui.widgets import TsvMimeDataUtils, maxWidgetSizes - +from timeseriesviewer.mapcanvas import MapCanvas class MapView(QObject): @@ -38,7 +38,7 @@ class MapView(QObject): self.ui = MapViewDefinitionUI(self, parent=parent) self.ui.create() - self.mMapCanvases = [] + self.mMapCanvases = dict() self.setVisibility(True) self.vectorLayer = None @@ -123,16 +123,19 @@ class MapView(QObject): #set basic settings sensorView = self.sensorViews[sensor] + assert isinstance(sensorView, MapViewSensorSettings) + sensorView.registerMapCanvas(mapCanvas) + + #register signals sensor specific signals mapCanvas.setRenderer(sensorView.layerRenderer()) - #register signals - sensorView.sigSensorRendererChanged.connect(mapCanvas.setRenderer) + #register non-sensor specific signals for this mpa view self.sigMapViewVisibility.connect(mapCanvas.refresh) self.sigCrosshairStyleChanged.connect(mapCanvas.setCrosshairStyle) self.sigShowCrosshair.connect(mapCanvas.setShowCrosshair) self.sigVectorLayerChanged.connect(mapCanvas.refresh) self.sigVectorVisibility.connect(mapCanvas.refresh) - self.mMapCanvases.append(mapCanvas) + @@ -148,9 +151,13 @@ class MapView(QObject): #w.showSensorName(False) self.sensorViews[sensor] = w - l = self.ui.sensorList + l = self.ui.sensorList.layout() i = l.count() - l.addWidget(w.ui) + lastWidgetIndex = 0 + for i in range(l.count()): + if isinstance(l.itemAt(i), QWidget): + lastWidgetIndex = i + l.insertWidget(lastWidgetIndex, w.ui) self.ui.resize(self.ui.sizeHint()) def getSensorWidget(self, sensor): @@ -195,7 +202,7 @@ class MapViewSensorSettings(QObject): Describes the rendering of images of one Sensor """ - sigSensorRendererChanged = pyqtSignal(QgsRasterRenderer) + #sigSensorRendererChanged = pyqtSignal(QgsRasterRenderer) def __init__(self, sensor, parent=None): """Constructor.""" @@ -208,7 +215,7 @@ class MapViewSensorSettings(QObject): self.ui.create() self.sensor.sigNameChanged.connect(self.onSensorNameChanged) self.onSensorNameChanged(self.sensor.name()) - + self.mMapCanvases = [] self.ui.bandNames = sensor.bandNames self.multiBandMinValues = [self.ui.tbRedMin, self.ui.tbGreenMin, self.ui.tbBlueMin] @@ -311,7 +318,7 @@ class MapViewSensorSettings(QObject): self.ui.actionSetSWIR.triggered.connect(lambda: self.setBandSelection('swIR')) - self.ui.actionApplyStyle.triggered.connect(lambda : self.sigSensorRendererChanged.emit(self.layerRenderer())) + self.ui.actionApplyStyle.triggered.connect(self.applyStyle) self.ui.actionCopyStyle.triggered.connect(lambda : QApplication.clipboard().setMimeData(self.mimeDataStyle())) self.ui.actionPasteStyle.triggered.connect(lambda : self.pasteStyleFromClipboard()) @@ -342,6 +349,13 @@ class MapViewSensorSettings(QObject): QApplication.clipboard().dataChanged.connect(self.onClipboardChange) self.onClipboardChange() + def registerMapCanvas(self, mapCanvas): + + assert isinstance(mapCanvas, MapCanvas) + self.mMapCanvases.append(mapCanvas) + mapCanvas.sigChangeSVRequest.connect(self.onMapCanvasRendererChangeRequest) + + def onSensorNameChanged(self, newName): self.sensor.sigNameChanged.connect(self.ui.labelTitle.setText) self.ui.labelTitle.setText(self.sensor.name()) @@ -354,13 +368,19 @@ class MapViewSensorSettings(QObject): if renderer is not None: self.setLayerRenderer(renderer) - def applyStyle(self): - self.sigSensorRendererChanged.emit(self.layerRenderer()) + def applyStyle(self, *args): + #self.sigSensorRendererChanged.emit(self.layerRenderer()) + r = self.layerRenderer() + for mapCanvas in self.mMapCanvases: + assert isinstance(mapCanvas, MapCanvas) + mapCanvas.setRenderer(r) def onClipboardChange(self): utils = TsvMimeDataUtils(QApplication.clipboard().mimeData()) self.ui.btnPasteStyle.setEnabled(utils.hasRasterStyle()) + def onMapCanvasRendererChangeRequest(self, mapCanvas, renderer): + self.setLayerRenderer(renderer) def setBandSelection(self, key): @@ -407,7 +427,8 @@ class MapViewSensorSettings(QObject): self.ui.labelSummary.setText(text) if MapViewSensorSettings.SignalizeImmediately: - self.sigSensorRendererChanged.emit(self.layerRenderer()) + #self.sigSensorRendererChanged.emit(self.layerRenderer()) + self.applyStyle() def setLayerRenderer(self, renderer): ui = self.ui @@ -426,13 +447,19 @@ class MapViewSensorSettings(QObject): for s in self.multiBandSliders: s.blockSignals(False) + ceRed = renderer.redContrastEnhancement() ceGreen = renderer.greenContrastEnhancement() ceBlue = renderer.blueContrastEnhancement() + if ceRed is None: + ceRed = ceGreen = ceBlue = QgsContrastEnhancement(self.sensor.bandDataType) + s = "" for i, ce in enumerate([ceRed, ceGreen, ceBlue]): - self.multiBandMinValues[i].setText(str(ce.minimumValue())) - self.multiBandMaxValues[i].setText(str(ce.maximumValue())) + vMin = ce.minimumValue() + vMax = ce.maximumValue() + self.multiBandMinValues[i].setText(str(vMin)) + self.multiBandMaxValues[i].setText(str(vMax)) idx = self.ceAlgs.values().index(ceRed.contrastEnhancementAlgorithm()) ui.comboBoxContrastEnhancement.setCurrentIndex(idx) @@ -456,7 +483,8 @@ class MapViewSensorSettings(QObject): self.updateUi() if updated and MapViewSensorSettings.SignalizeImmediately: - self.sigSensorRendererChanged.emit(renderer.clone()) + #self.sigSensorRendererChanged.emit(renderer.clone()) + self.applyStyle() def mimeDataStyle(self): r = self.layerRenderer() @@ -603,16 +631,19 @@ class DatumView(QObject): self.adjustBaseMinSize() def refresh(self): + if self.ui.isVisible(): for c in self.mapCanvases.values(): if c.isVisible(): - c.refreshAllLayers() + #c.refreshAllLayers() + c.refresh() def insertMapView(self, mapView): assert isinstance(mapView, MapView) from timeseriesviewer.mapcanvas import MapCanvas mapCanvas = MapCanvas(self.ui) + mapCanvas.setObjectName('MapCanvas {} {}'.format(mapView.title(), self.TSD.date)) mapCanvas.blockSignals(True) self.registerMapCanvas(mapView, mapCanvas) @@ -621,8 +652,8 @@ class DatumView(QObject): # register MapCanvas on STV level self.STV.registerMapCanvas(mapCanvas) mapCanvas.blockSignals(False) - mapCanvas.refreshAllLayers() - mapCanvas.refresh() + #mapCanvas.refreshAllLayers() + #mapCanvas.refresh() def registerMapCanvas(self, mapView, mapCanvas): from timeseriesviewer.mapcanvas import MapCanvas @@ -639,13 +670,14 @@ class DatumView(QObject): mapCanvas.renderStarting.connect(lambda: self.sigLoadingStarted.emit(mapView, self.TSD)) mapCanvas.mapCanvasRefreshed.connect(lambda: self.sigLoadingFinished.emit(mapView, self.TSD)) mapCanvas.sigShowProfiles.connect(mapView.sigShowProfiles.emit) + mapCanvas.sigChangeDVRequest.connect(self.onMapCanvasRequest) + def onMapCanvasRequest(self, mapCanvas, key): + if key == 'hide_date': + self.TSD.setVisibility(False) - - pass - def __lt__(self, other): assert isinstance(other, DatumView) return self.TSD < other.TSD @@ -679,10 +711,20 @@ class SpatialTemporalVisualization(QObject): self.mColor = Qt.black self.mMapCanvases = [] self.ui = timeSeriesViewer.ui + from timeseriesviewer.ui.widgets import TsvScrollArea self.scrollArea = self.ui.scrollAreaSubsets + assert isinstance(self.scrollArea, TsvScrollArea) + #self.scrollArea.sigResized.connect(self.refresh) + #self.scrollArea.horizontalScrollBar().valueChanged.connect(lambda:QTimer.singleShot(2000,self.refresh)) + + self.TSV = timeSeriesViewer self.TS = timeSeriesViewer.TS self.targetLayout = self.ui.scrollAreaSubsetContent.layout() + + + + self.dockMapViews = self.ui.dockMapViews self.MVC = MapViewCollection(self) self.MVC.sigShowProfiles.connect(self.sigShowProfiles.emit) @@ -717,12 +759,6 @@ class SpatialTemporalVisualization(QObject): #register on map canvas signals mapCanvas.sigSpatialExtentChanged.connect(lambda e: self.setSpatialExtent(e, mapCanvas)) - from timeseriesviewer.ui.widgets import TsvScrollArea - assert isinstance(self.scrollArea, TsvScrollArea) - self.scrollArea.sigResized.connect(mapCanvas.setRenderMe) - self.scrollArea.horizontalScrollBar().valueChanged.connect(mapCanvas.setRenderMe) - - def setCrosshairStyle(self, crosshairStyle): @@ -761,6 +797,7 @@ class SpatialTemporalVisualization(QObject): def subsetSize(self): return QSize(self.mSize) + def refresh(self): for tsdView in self.DVC: tsdView.refresh() @@ -795,6 +832,26 @@ class SpatialTemporalVisualization(QObject): self.nMaxTSDViews = n #todo: remove views + def setSpatialCenter(self, center, mapCanvas0=None): + if self.mBlockCanvasSignals: + return True + + assert isinstance(center, SpatialPoint) + center = center.toCrs(self.mCRS) + if not isinstance(center, SpatialPoint): + return + + self.mBlockCanvasSignals = True + self.mSpatialExtent.setCenter(center) + for mapCanvas in self.mMapCanvases: + if mapCanvas != mapCanvas0: + oldState = mapCanvas.blockSignals(True) + mapCanvas.setCenter(center) + mapCanvas.blockSignals(oldState) + self.mBlockCanvasSignals = False + self.sigSpatialExtentChanged.emit(self.mSpatialExtent) + + def setSpatialExtent(self, extent, mapCanvas0=None): if self.mBlockCanvasSignals: return True @@ -811,12 +868,14 @@ class SpatialTemporalVisualization(QObject): self.mBlockCanvasSignals = True self.mSpatialExtent = extent for mapCanvas in self.mMapCanvases: - print('STV set EXTENT', str(mapCanvas)) if mapCanvas != mapCanvas0: oldState = mapCanvas.blockSignals(True) mapCanvas.setExtent(extent) mapCanvas.blockSignals(oldState) + self.mBlockCanvasSignals = False + #for mapCanvas in self.mMapCanvases: + # mapCanvas.refresh() self.sigSpatialExtentChanged.emit(extent) def setBackgroundColor(self, color): @@ -860,6 +919,7 @@ class SpatialTemporalVisualization(QObject): assert isinstance(self.scrollArea, QScrollArea) self.scrollArea.ensureWidgetVisible(tsdv.ui) + def setMapViewVisibility(self, bandView, isVisible): assert isinstance(bandView, MapView) assert isinstance(isVisible, bool) @@ -1195,15 +1255,15 @@ class MapViewDefinitionUI(QGroupBox, load('mapviewdefinition.ui')): self.actionToggleVisibility.toggled.connect(lambda: self.setVisibility(not self.actionToggleVisibility.isChecked())) self.actionToggleVectorVisibility.toggled.connect(lambda : self.sigVectorVisibility.emit(self.actionToggleVectorVisibility.isChecked())) - def sizeHint(self): + def DEPRsizeHint(self): #m = self.layout().contentsMargins() #sl = maxWidgetSizes(self.sensorList) #sm = self.buttonList.size() #w = sl.width() + m.left()+ m.right() + sm.width() #h = sl.height() + m.top() + m.bottom() + sm.height() - return maxWidgetSizes(self.sensorList) - return QSize(w,h) + return maxWidgetSizes(self.sensorList.layout()) + def mapViewDefinition(self): diff --git a/timeseriesviewer/plotstyling.py b/timeseriesviewer/plotstyling.py index 32a22ea674fcc85d3d5b7e51a532b50ca93a5b73..3240b1a5e509e42170fd8dd84429ba6b54516297 100644 --- a/timeseriesviewer/plotstyling.py +++ b/timeseriesviewer/plotstyling.py @@ -243,11 +243,12 @@ class PlotStyleButton(QPushButton): return PlotStyle(plotStyle=self.mPlotStyle) def setPlotStyle(self, plotStyle): - #assert isinstance(plotStyle, PlotStyle) if isinstance(plotStyle, PlotStyle): - self.mPlotStyle = plotStyle + self.mPlotStyle.copyFrom(plotStyle) self._updateIcon() self.sigPlotStyleChanged.emit(self.mPlotStyle) + else: + s = "" def showDialog(self): @@ -307,6 +308,8 @@ class PlotStyleDialog(QgsDialog): buttonBar = QHBoxLayout() #buttonBar.addWidget(self.btCancel) #buttonBar.addWidget(self.btOk) + if plotStyle: + self.setPlotStyle(plotStyle) l = self.layout() l.addWidget(self.w) l.addLayout(buttonBar) diff --git a/timeseriesviewer/profilevisualization.py b/timeseriesviewer/profilevisualization.py index 1812bb3c623ca444ca8c5a5e8c2a9345e681d92e..33e38433392ba1085e4b8c980e58c2d5589d4516 100644 --- a/timeseriesviewer/profilevisualization.py +++ b/timeseriesviewer/profilevisualization.py @@ -658,6 +658,12 @@ class ProfileViewDockUI(TsvDockWidgetBase, load('profileviewdock.ui')): super(ProfileViewDockUI, self).__init__(parent) self.setupUi(self) from timeseriesviewer import OPENGL_AVAILABLE, SETTINGS + + #TBD. + self.line.setVisible(False) + self.listWidget.setVisible(False) + self.stackedWidget.setCurrentWidget(self.page2D) + if OPENGL_AVAILABLE: l = self.page3D.layout() l.removeWidget(self.labelDummy3D) @@ -667,7 +673,6 @@ class ProfileViewDockUI(TsvDockWidgetBase, load('profileviewdock.ui')): else: self.plotWidget3D = None - #pi = self.plotWidget2D.plotItem #ax = DateAxis(orientation='bottom', showValues=True) #pi.layout.addItem(ax, 3,2) @@ -948,7 +953,7 @@ class SpectralTemporalVisualization(QObject): -def testPixelLoader(): +def examplePixelLoader(): # prepare QGIS environment if sys.platform == 'darwin': diff --git a/timeseriesviewer/sandbox.py b/timeseriesviewer/sandbox.py index e666e6ef3c95c64a9642115e3928f97467bd9ef6..4f605460c9168909d9f6d294d527639246b8f06e 100644 --- a/timeseriesviewer/sandbox.py +++ b/timeseriesviewer/sandbox.py @@ -280,10 +280,35 @@ def sandboxTestdata(): S.spatialTemporalVis.MVC.createMapView() import example.Images searchDir = jp(DIR_EXAMPLES, 'Images') - imgs = file_search(searchDir, '*.bsq', recursive=True) # [0:5] + imgs = file_search(searchDir, '*.bsq', recursive=True)#[0:1] # [0:5] S.addTimeSeriesImages(imgs) +def sandboxMultitemp2017(): + from timeseriesviewer.main import TimeSeriesViewer + + from timeseriesviewer import PATH_EXAMPLE_TIMESERIES + S = TimeSeriesViewer(None) + S.ui.show() + S.run() + + S.spatialTemporalVis.MVC.createMapView() + import example.Images + + if True: + searchDir = r'O:\SenseCarbonProcessing\BJ_Multitemp2017\01_Data\Landsat' + imgs = file_search(searchDir, 'LC8*.vrt', recursive=True)[0:10] + S.addTimeSeriesImages(imgs) + + if True: + searchDir = r'O:\SenseCarbonProcessing\BJ_Multitemp2017\01_Data\CBERS' + imgs = file_search(searchDir, 'CBERS*.vrt', recursive=True)#[0:1] + S.addTimeSeriesImages(imgs) + + searchDir = r'O:\SenseCarbonProcessing\BJ_Multitemp2017\01_Data\RapidEye' + imgs = file_search(searchDir, 're*.vrt', recursive=True)#[0:1] + S.addTimeSeriesImages(imgs) + if __name__ == '__main__': import site, sys, pyqtgraph @@ -294,8 +319,10 @@ if __name__ == '__main__': #run tests if False: gdal_qgis_benchmark() if False: sandboxQgisBridge() - if True: sandboxGui() + if False: sandboxGui() if False: sandboxTestdata() + if True: sandboxMultitemp2017() + #close QGIS qgsApp.exec_() qgsApp.exitQgis() diff --git a/timeseriesviewer/sensorvisualization.py b/timeseriesviewer/sensorvisualization.py index 3247b181239c33b0b576cdd9ff618057c639c845..0cdea7e02f3a5e2f56048ce3cfe40fb47daa253e 100644 --- a/timeseriesviewer/sensorvisualization.py +++ b/timeseriesviewer/sensorvisualization.py @@ -116,7 +116,7 @@ class SensorTableModel(QAbstractTableModel): elif columnName == 'id': value = sensor.id() elif columnName == 'wl': - if sensor.wavelengths.ndim == 0: + if sensor.wavelengths is None or sensor.wavelengths.ndim == 0: value = 'undefined' else: value = ','.join([str(w) for w in sensor.wavelengths]) +sensor.wavelengthUnits diff --git a/timeseriesviewer/timeseries.py b/timeseriesviewer/timeseries.py index 2fc30be0b5dec859c7359df0dd40dc7ce6021a2a..a007012f6f28ef13afb52936533d038b1d35c2af 100644 --- a/timeseriesviewer/timeseries.py +++ b/timeseriesviewer/timeseries.py @@ -98,9 +98,12 @@ class SensorInstrument(QObject): #find wavelength wl, wlu = parseWavelengthFromDataSet(ds) - self.wavelengths = np.asarray(wl) - self.wavelengthUnits = wlu - + if wl is None: + self.wavelengths = None + self.wavelengthUnits = None + else: + self.wavelengths = np.asarray(wl) + self.wavelengthUnits = wlu self._id = '{}b{}m'.format(self.nb, self.px_size_x) if wl is not None: self._id += ';'.join([str(w) for w in self.wavelengths])+ wlu @@ -190,12 +193,19 @@ def verifyInputImage(path, vrtInspection=''): if ds.RasterCount == 0 and len(ds.GetSubDatasets()) > 0: logger.error('Can not open container {}.\nPlease specify a subdataset'.format(path)) return False + if ds.GetDriver().ShortName == 'VRT': vrtInspection = 'VRT Inspection {}\n'.format(path) nextFiles = set(ds.GetFileList()) - set([path]) validSrc = [verifyInputImage(p, vrtInspection=vrtInspection) for p in nextFiles] if not all(validSrc): return False + + from timeseriesviewer.dateparser import parseDateFromDataSet + date = parseDateFromDataSet(ds) + if date is None: + return False + return True def pixel2coord(gt, x, y): @@ -249,8 +259,8 @@ class TimeSeriesDatum(QObject): self.date = parseDateFromDataSet(ds) assert self.date is not None, 'Unable to find acquisition date of {}'.format(pathImg) - from timeseriesviewer.dateparser import getDOYfromDatetime64 - self.doy = getDOYfromDatetime64(self.date) + from timeseriesviewer.dateparser import DOYfromDatetime64 + self.doy = DOYfromDatetime64(self.date) gt = ds.GetGeoTransform() @@ -563,10 +573,10 @@ def parseWavelengthFromDataSet(ds): wlu = None #see http://www.harrisgeospatial.com/docs/ENVIHeaderFiles.html for supported wavelength units - regWLkey = re.compile('.*wavelength[_ ]*$') - regWLUkey = re.compile('.*wavelength[_ ]*units?$') - regNumeric = re.compile(r"([-+]?\d*\.\d+|[-+]?\d+)") - regWLU = re.compile('((micro|nano|centi)meters)|(um|nm|mm|cm|m|GHz|MHz)') + regWLkey = re.compile('.*wavelength[_ ]*$', re.I) + regWLUkey = re.compile('.*wavelength[_ ]*units?$', re.I) + regNumeric = re.compile(r"([-+]?\d*\.\d+|[-+]?\d+)", re.I) + regWLU = re.compile('((micro|nano|centi)meters)|(um|nm|mm|cm|m|GHz|MHz)', re.I) for domain in ds.GetMetadataDomainList(): md = ds.GetMetadata_Dict(domain) for key, value in md.items(): @@ -578,7 +588,7 @@ def parseWavelengthFromDataSet(ds): if wlu is None and regWLUkey.search(key): match = regWLU.search(value) if match: - wlu = match.group() + wlu = match.group().lower() names = ['nanometers', 'micrometers', 'millimeters', 'centimeters', 'decimeters'] si = ['nm', 'um', 'mm', 'cm', 'dm'] if wlu in names: diff --git a/timeseriesviewer/ui/docks.py b/timeseriesviewer/ui/docks.py index cb03c932030f1ae9835b148c1344907259f3253f..5103b4a32973cd9f08cac77d2807cbc4581804d3 100644 --- a/timeseriesviewer/ui/docks.py +++ b/timeseriesviewer/ui/docks.py @@ -37,6 +37,7 @@ class RenderingDockUI(TsvDockWidgetBase, load('renderingdock.ui')): sigCrsChanged = pyqtSignal(QgsCoordinateReferenceSystem) sigMapSizeChanged = pyqtSignal(QSize) sigQgisSyncStateChanged = pyqtSignal(QgisTsvBridge.SyncState) + sigQgisInteractionRequest = pyqtSignal(str) def __init__(self, parent=None): super(RenderingDockUI, self).__init__(parent) @@ -53,6 +54,11 @@ class RenderingDockUI(TsvDockWidgetBase, load('renderingdock.ui')): self.btnCrs.crsChanged.connect(self.sigCrsChanged) #default: disable QgsSync box + + #todo: realt-time syncing? + self.frameRTSync.setVisible(False) + self.progressBar.setVisible(False) + self.enableQgisSyncronization(False) self.mLastSyncState = self.qgsSyncState() @@ -60,8 +66,19 @@ class RenderingDockUI(TsvDockWidgetBase, load('renderingdock.ui')): self.cbSyncQgsMapCenter.stateChanged.connect(self.onSyncStateChanged) self.cbSyncQgsCRS.stateChanged.connect(self.onSyncStateChanged) + self.btnSetQGISCenter.clicked.connect(lambda : self.sigQgisInteractionRequest.emit('tsvCenter2qgsCenter')) + self.btnSetQGISExtent.clicked.connect(lambda: self.sigQgisInteractionRequest.emit('tsvExtent2qgsExtent')) + self.btnGetQGISCenter.clicked.connect(lambda: self.sigQgisInteractionRequest.emit('qgisCenter2tsvCenter')) + self.btnGetQGISExtent.clicked.connect(lambda: self.sigQgisInteractionRequest.emit('qgisExtent2tsvExtent')) + def enableQgisSyncronization(self, b): + self.gbSyncQgs.setEnabled(b) + if b: + self.gbSyncQgs.setTitle('QGIS') + else: + self.gbSyncQgs.setTitle('QGIS (not available)') + #self.gbQgsVectorLayer.setEnabled(b) def onSyncStateChanged(self, *args): @@ -239,7 +256,6 @@ class TimeSeriesDockUI(TsvDockWidgetBase, load('timeseriesdock.ui')): msg += ', {} to {}'.format(str(self.TS[0].date), str(self.TS[-1].date)) self.progressInfo.setText(msg) - def setProgressInfo(self, nDone, nMax, message=None): if self.progressBar.maximum() != nMax: self.progressBar.setMaximum(nMax) @@ -249,13 +265,9 @@ class TimeSeriesDockUI(TsvDockWidgetBase, load('timeseriesdock.ui')): if nDone == nMax: QTimer.singleShot(3000, lambda: self.setStatus()) - def onSelectionChanged(self, *args): self.btnRemoveTSD.setEnabled(self.SM is not None and len(self.SM.selectedRows()) > 0) - - s = "" - def selectedTimeSeriesDates(self): if self.SM is not None: return [self.TSM.data(idx, Qt.UserRole) for idx in self.SM.selectedRows()] diff --git a/timeseriesviewer/ui/mapviewdefinition.ui b/timeseriesviewer/ui/mapviewdefinition.ui index 360e9082aaf42c83354c6ddf9f44a44d55049f41..cbc9c2aeeb7c08add641e29eba4e48006595907b 100644 --- a/timeseriesviewer/ui/mapviewdefinition.ui +++ b/timeseriesviewer/ui/mapviewdefinition.ui @@ -6,24 +6,36 @@ <rect> <x>0</x> <y>0</y> - <width>253</width> - <height>159</height> + <width>325</width> + <height>136</height> </rect> </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> <property name="windowTitle"> <string/> </property> <property name="title"> <string/> </property> - <layout class="QFormLayout" name="formLayout"> - <property name="horizontalSpacing"> + <layout class="QHBoxLayout" name="horizontalLayout"> + <property name="leftMargin"> + <number>1</number> + </property> + <property name="topMargin"> <number>1</number> </property> - <property name="margin"> + <property name="rightMargin"> <number>1</number> </property> - <item row="0" column="0"> + <property name="bottomMargin"> + <number>6</number> + </property> + <item> <widget class="QFrame" name="buttonList"> <property name="sizePolicy"> <sizepolicy hsizetype="Fixed" vsizetype="Preferred"> @@ -129,6 +141,12 @@ </item> <item> <widget class="QToolButton" name="btnShowCrosshair"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> <property name="text"> <string>...</string> </property> @@ -176,25 +194,30 @@ </layout> </widget> </item> - <item row="0" column="1"> - <layout class="QHBoxLayout" name="sensorList"> - <property name="margin"> - <number>1</number> - </property> - </layout> - </item> - <item row="1" column="1"> - <spacer name="verticalSpacer"> - <property name="orientation"> - <enum>Qt::Vertical</enum> + <item> + <widget class="QFrame" name="sensorList"> + <property name="frameShape"> + <enum>QFrame::StyledPanel</enum> </property> - <property name="sizeHint" stdset="0"> - <size> - <width>0</width> - <height>0</height> - </size> + <property name="frameShadow"> + <enum>QFrame::Raised</enum> </property> - </spacer> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>267</width> + <height>50</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> </item> </layout> <action name="actionRemoveMapView"> diff --git a/timeseriesviewer/ui/mapviewdock.ui b/timeseriesviewer/ui/mapviewdock.ui index 902829b7d91046f7a9e132e3f6a3f0362a35d280..2ce3eb174aa8d062ba4f165cce716446303bb180 100644 --- a/timeseriesviewer/ui/mapviewdock.ui +++ b/timeseriesviewer/ui/mapviewdock.ui @@ -6,8 +6,8 @@ <rect> <x>0</x> <y>0</y> - <width>504</width> - <height>189</height> + <width>480</width> + <height>175</height> </rect> </property> <property name="minimumSize"> @@ -30,8 +30,8 @@ <item> <widget class="QScrollArea" name="scrollAreaMapViews"> <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Expanding"> - <horstretch>1</horstretch> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> <verstretch>0</verstretch> </sizepolicy> </property> @@ -51,7 +51,7 @@ <number>1</number> </property> <property name="verticalScrollBarPolicy"> - <enum>Qt::ScrollBarAsNeeded</enum> + <enum>Qt::ScrollBarAlwaysOff</enum> </property> <property name="horizontalScrollBarPolicy"> <enum>Qt::ScrollBarAsNeeded</enum> @@ -67,8 +67,8 @@ <rect> <x>0</x> <y>0</y> - <width>504</width> - <height>167</height> + <width>480</width> + <height>153</height> </rect> </property> <property name="sizePolicy"> diff --git a/timeseriesviewer/ui/mapviewrendersettings.ui b/timeseriesviewer/ui/mapviewrendersettings.ui index 2483cfd31e950cf921dcc12beba28f083dce6ea4..06d88855a69815b6c342947ae5dd3efeec41bf20 100644 --- a/timeseriesviewer/ui/mapviewrendersettings.ui +++ b/timeseriesviewer/ui/mapviewrendersettings.ui @@ -6,8 +6,8 @@ <rect> <x>0</x> <y>0</y> - <width>300</width> - <height>216</height> + <width>260</width> + <height>135</height> </rect> </property> <property name="sizePolicy"> @@ -47,19 +47,10 @@ </property> <layout class="QVBoxLayout" name="verticalLayout"> <property name="spacing"> - <number>6</number> - </property> - <property name="leftMargin"> - <number>6</number> - </property> - <property name="topMargin"> - <number>6</number> + <number>2</number> </property> - <property name="rightMargin"> - <number>6</number> - </property> - <property name="bottomMargin"> - <number>6</number> + <property name="margin"> + <number>2</number> </property> <item> <layout class="QHBoxLayout" name="horizontalLayout_2"> @@ -67,7 +58,7 @@ <widget class="QLabel" name="labelTitle"> <property name="font"> <font> - <pointsize>11</pointsize> + <pointsize>8</pointsize> <weight>50</weight> <italic>false</italic> <bold>false</bold> @@ -95,7 +86,7 @@ </property> <property name="font"> <font> - <pointsize>11</pointsize> + <pointsize>8</pointsize> </font> </property> <item> @@ -126,7 +117,7 @@ </property> <property name="font"> <font> - <pointsize>11</pointsize> + <pointsize>8</pointsize> </font> </property> <property name="text"> @@ -164,26 +155,23 @@ <number>0</number> </property> <widget class="QWidget" name="pageMultiBand"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Minimum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> <property name="font"> <font> - <pointsize>11</pointsize> + <pointsize>8</pointsize> </font> </property> <layout class="QGridLayout" name="gridLayout_2"> - <property name="leftMargin"> - <number>6</number> - </property> - <property name="topMargin"> - <number>6</number> - </property> - <property name="rightMargin"> - <number>6</number> - </property> - <property name="bottomMargin"> - <number>6</number> + <property name="margin"> + <number>2</number> </property> <property name="spacing"> - <number>6</number> + <number>2</number> </property> <item row="1" column="1"> <widget class="QSlider" name="sliderRed"> @@ -224,6 +212,11 @@ </item> <item row="0" column="2"> <widget class="QLabel" name="labelMax"> + <property name="font"> + <font> + <pointsize>8</pointsize> + </font> + </property> <property name="text"> <string>Min</string> </property> @@ -245,7 +238,7 @@ </property> <property name="font"> <font> - <pointsize>11</pointsize> + <pointsize>8</pointsize> </font> </property> <property name="text"> @@ -257,7 +250,7 @@ <widget class="QLineEdit" name="tbGreenMin"> <property name="font"> <font> - <pointsize>11</pointsize> + <pointsize>8</pointsize> </font> </property> <property name="text"> @@ -281,7 +274,7 @@ </property> <property name="font"> <font> - <pointsize>11</pointsize> + <pointsize>8</pointsize> </font> </property> <property name="text"> @@ -291,6 +284,11 @@ </item> <item row="0" column="3"> <widget class="QLabel" name="labelMin"> + <property name="font"> + <font> + <pointsize>8</pointsize> + </font> + </property> <property name="text"> <string>Max</string> </property> @@ -306,7 +304,7 @@ </property> <property name="font"> <font> - <pointsize>11</pointsize> + <pointsize>8</pointsize> </font> </property> <property name="text"> @@ -318,7 +316,7 @@ <widget class="QLabel" name="labelGreen"> <property name="font"> <font> - <pointsize>11</pointsize> + <pointsize>8</pointsize> </font> </property> <property name="styleSheet"> @@ -364,7 +362,7 @@ <widget class="QLabel" name="labelRed"> <property name="font"> <font> - <pointsize>11</pointsize> + <pointsize>8</pointsize> </font> </property> <property name="styleSheet"> @@ -379,7 +377,7 @@ <widget class="QLabel" name="labelBlue"> <property name="font"> <font> - <pointsize>11</pointsize> + <pointsize>8</pointsize> </font> </property> <property name="text"> @@ -416,7 +414,7 @@ <widget class="QLineEdit" name="tbBlueMin"> <property name="font"> <font> - <pointsize>11</pointsize> + <pointsize>8</pointsize> </font> </property> <property name="text"> @@ -434,7 +432,7 @@ </property> <property name="font"> <font> - <pointsize>11</pointsize> + <pointsize>8</pointsize> </font> </property> <property name="text"> @@ -460,23 +458,14 @@ <property name="spacing"> <number>3</number> </property> - <property name="leftMargin"> - <number>0</number> - </property> - <property name="topMargin"> - <number>0</number> - </property> - <property name="rightMargin"> - <number>0</number> - </property> - <property name="bottomMargin"> + <property name="margin"> <number>0</number> </property> <item> <widget class="QToolButton" name="btnDefaultMB"> <property name="font"> <font> - <pointsize>11</pointsize> + <pointsize>8</pointsize> <italic>false</italic> </font> </property> @@ -492,7 +481,7 @@ <widget class="QToolButton" name="btnTrueColor"> <property name="font"> <font> - <pointsize>11</pointsize> + <pointsize>8</pointsize> <italic>false</italic> </font> </property> @@ -508,7 +497,7 @@ <widget class="QToolButton" name="btnCIR"> <property name="font"> <font> - <pointsize>11</pointsize> + <pointsize>8</pointsize> <italic>false</italic> </font> </property> @@ -524,7 +513,7 @@ <widget class="QToolButton" name="btn453"> <property name="font"> <font> - <pointsize>11</pointsize> + <pointsize>8</pointsize> <italic>false</italic> </font> </property> @@ -557,28 +546,19 @@ <widget class="QWidget" name="pageSingleBand"> <property name="font"> <font> - <pointsize>11</pointsize> + <pointsize>8</pointsize> </font> </property> <layout class="QGridLayout" name="gridLayout"> - <property name="leftMargin"> - <number>0</number> - </property> - <property name="topMargin"> - <number>0</number> - </property> - <property name="rightMargin"> - <number>0</number> - </property> - <property name="bottomMargin"> - <number>0</number> - </property> <property name="horizontalSpacing"> <number>2</number> </property> <property name="verticalSpacing"> <number>0</number> </property> + <property name="margin"> + <number>0</number> + </property> <item row="3" column="1"> <widget class="QSlider" name="sliderSingleBand"> <property name="minimumSize"> @@ -625,24 +605,15 @@ <enum>QFrame::Raised</enum> </property> <layout class="QGridLayout" name="gridLayout_4"> - <property name="leftMargin"> - <number>0</number> - </property> - <property name="topMargin"> - <number>0</number> - </property> - <property name="rightMargin"> - <number>0</number> - </property> - <property name="bottomMargin"> - <number>0</number> - </property> <property name="horizontalSpacing"> <number>1</number> </property> <property name="verticalSpacing"> <number>0</number> </property> + <property name="margin"> + <number>0</number> + </property> <item row="1" column="2"> <widget class="QComboBox" name="cbSingleBandColorRampType"> <property name="sizePolicy"> @@ -703,16 +674,7 @@ <property name="spacing"> <number>0</number> </property> - <property name="leftMargin"> - <number>0</number> - </property> - <property name="topMargin"> - <number>0</number> - </property> - <property name="rightMargin"> - <number>0</number> - </property> - <property name="bottomMargin"> + <property name="margin"> <number>0</number> </property> <item> @@ -882,7 +844,7 @@ <widget class="QLabel" name="label"> <property name="font"> <font> - <pointsize>11</pointsize> + <pointsize>8</pointsize> </font> </property> <property name="text"> diff --git a/timeseriesviewer/ui/renderingdock.ui b/timeseriesviewer/ui/renderingdock.ui index 9274a5c9bc1d1fad72fa645f3a7885a33174b756..77d15969c61908a9adf55bb161e5ea1dbb183432 100644 --- a/timeseriesviewer/ui/renderingdock.ui +++ b/timeseriesviewer/ui/renderingdock.ui @@ -6,8 +6,8 @@ <rect> <x>0</x> <y>0</y> - <width>206</width> - <height>386</height> + <width>327</width> + <height>359</height> </rect> </property> <property name="windowTitle"> @@ -235,124 +235,220 @@ </item> <item> <widget class="QGroupBox" name="gbSyncQgs"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> <property name="minimumSize"> <size> <width>0</width> - <height>60</height> + <height>25</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>230</height> </size> </property> <property name="title"> - <string>Sync with QGIS Map on...</string> + <string>QGIS</string> </property> <property name="flat"> <bool>false</bool> </property> - <layout class="QGridLayout" name="gridLayout_2"> - <item row="0" column="0"> - <widget class="QCheckBox" name="cbSyncQgsMapCenter"> + <layout class="QGridLayout" name="gridLayout_2" rowstretch="1,1,0,0" rowminimumheight="23,23,0,0"> + <property name="sizeConstraint"> + <enum>QLayout::SetMinimumSize</enum> + </property> + <property name="margin"> + <number>6</number> + </property> + <property name="spacing"> + <number>2</number> + </property> + <item row="1" column="2"> + <widget class="QToolButton" name="btnGetQGISExtent"> + <property name="toolTip"> + <string>Take the spatial map extent from QGIS</string> + </property> <property name="text"> - <string>Center</string> + <string>Get Extent</string> </property> </widget> </item> - <item row="0" column="3"> - <spacer name="horizontalSpacer_3"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> + <item row="1" column="1"> + <widget class="QToolButton" name="btnGetQGISCenter"> + <property name="toolTip"> + <string>Take the map center from QGIS</string> </property> - </spacer> - </item> - <item row="0" column="2"> - <widget class="QCheckBox" name="cbSyncQgsCRS"> <property name="text"> - <string>CRS</string> + <string>Get Center</string> </property> </widget> </item> - <item row="0" column="1"> - <widget class="QCheckBox" name="cbSyncQgsMapExtent"> + <item row="0" column="2"> + <widget class="QToolButton" name="btnSetQGISExtent"> + <property name="toolTip"> + <string>Try to set the QGIS map extent to the same spatial extent</string> + </property> <property name="text"> - <string>Exent</string> + <string>Set Extent</string> </property> </widget> </item> - </layout> - </widget> - </item> - <item> - <widget class="QgsCollapsibleGroupBox" name="gbQgsVectorLayer"> - <property name="title"> - <string>Show Vector Layer</string> - </property> - <property name="checkable"> - <bool>true</bool> - </property> - <property name="checked"> - <bool>false</bool> - </property> - <property name="collapsed"> - <bool>false</bool> - </property> - <property name="saveCheckedState"> - <bool>true</bool> - </property> - <layout class="QFormLayout" name="formLayout_5"> - <property name="fieldGrowthPolicy"> - <enum>QFormLayout::AllNonFixedFieldsGrow</enum> - </property> - <item row="0" column="1"> - <widget class="QgsMapLayerComboBox" name="cbQgsVectorLayer"> + <item row="3" column="0" colspan="4"> + <widget class="QFrame" name="frameRTSync"> <property name="enabled"> <bool>false</bool> </property> <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> <horstretch>0</horstretch> <verstretch>0</verstretch> </sizepolicy> </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>50</height> + </size> + </property> + <property name="frameShape"> + <enum>QFrame::StyledPanel</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Raised</enum> + </property> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QCheckBox" name="cbSyncQgsMapCenter"> + <property name="text"> + <string>Center</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="cbSyncQgsMapExtent"> + <property name="text"> + <string>Exent</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="cbSyncQgsCRS"> + <property name="text"> + <string>CRS</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + <zorder>cbSyncQgsMapCenter</zorder> + <zorder>cbSyncQgsCRS</zorder> + <zorder>cbSyncQgsMapExtent</zorder> + <zorder>horizontalSpacer_3</zorder> </widget> </item> - </layout> - </widget> - </item> - <item> - <widget class="QGroupBox" name="groupBox_2"> - <property name="title"> - <string>Render progress</string> - </property> - <layout class="QVBoxLayout" name="verticalLayout_2"> - <item> - <widget class="QProgressBar" name="progressBar"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> + <item row="0" column="1"> + <widget class="QToolButton" name="btnSetQGISCenter"> + <property name="toolTip"> + <string>Set QGIS Map to the same map center</string> </property> - <property name="value"> - <number>0</number> + <property name="text"> + <string>Set Center</string> </property> + </widget> + </item> + <item row="0" column="3"> + <spacer name="horizontalSpacer"> <property name="orientation"> <enum>Qt::Horizontal</enum> </property> - <property name="invertedAppearance"> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="2" column="0" colspan="4"> + <widget class="QgsCollapsibleGroupBox" name="gbQgsVectorLayer"> + <property name="title"> + <string>Show Vector Layer</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="checked"> <bool>false</bool> </property> - <property name="textDirection"> - <enum>QProgressBar::TopToBottom</enum> + <property name="collapsed"> + <bool>true</bool> + </property> + <property name="saveCheckedState"> + <bool>true</bool> </property> + <layout class="QFormLayout" name="formLayout_5"> + <property name="fieldGrowthPolicy"> + <enum>QFormLayout::AllNonFixedFieldsGrow</enum> + </property> + <item row="0" column="1"> + <widget class="QgsMapLayerComboBox" name="cbQgsVectorLayer"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + </layout> </widget> </item> </layout> </widget> </item> + <item> + <widget class="QProgressBar" name="progressBar"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="value"> + <number>0</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="invertedAppearance"> + <bool>false</bool> + </property> + <property name="textDirection"> + <enum>QProgressBar::TopToBottom</enum> + </property> + </widget> + </item> <item> <spacer name="verticalSpacer"> <property name="orientation"> @@ -361,7 +457,7 @@ <property name="sizeHint" stdset="0"> <size> <width>20</width> - <height>40</height> + <height>0</height> </size> </property> </spacer> diff --git a/timeseriesviewer/ui/widgets.py b/timeseriesviewer/ui/widgets.py index edb01bcb7efdc5754e2a2b57c959190512d28b58..60442ec8fd7b250c302a5813d4fa309eb0f90aca 100644 --- a/timeseriesviewer/ui/widgets.py +++ b/timeseriesviewer/ui/widgets.py @@ -125,7 +125,7 @@ PATH_RENDERINGDOCK_UI = jp(DIR_UI, 'renderingdock.ui') -def maxWidgetSizes(layout): +def maxWidgetSizes(layout, onHint=True): assert isinstance(layout, QBoxLayout) p = layout.parentWidget() @@ -137,10 +137,15 @@ def maxWidgetSizes(layout): for item in [layout.itemAt(i) for i in range(layout.count())]: wid = item.widget() + ly = item.layout() if wid: - s = wid.sizeHint() - elif isinstance(item, QLayout): - s = "" + if onHint: + s = wid.sizeHint() + else: + s = wid.size() + elif ly: + continue + else: continue if horizontal: sizeX += s.width() + layout.spacing() diff --git a/timeseriesviewer/utils.py b/timeseriesviewer/utils.py index 8d9e4969a2087e7ac294019a0ac7b825236ea301..c982afade4652b43478dca3b7d627b02dd5d8328 100644 --- a/timeseriesviewer/utils.py +++ b/timeseriesviewer/utils.py @@ -45,6 +45,12 @@ class SpatialPoint(QgsPoint): crs = mapCanvas.mapSettings().destinationCrs() return SpatialPoint(crs, mapCanvas.center()) + @staticmethod + def fromSpatialExtent(spatialExtent): + assert isinstance(spatialExtent, SpatialExtent) + crs = spatialExtent.crs() + return SpatialPoint(crs, spatialExtent.center()) + def __init__(self, crs, *args): assert isinstance(crs, QgsCoordinateReferenceSystem) super(SpatialPoint, self).__init__(*args) @@ -105,7 +111,7 @@ def saveTransform(geom, crs1, crs2): transform = QgsCoordinateTransform(crs1, crs2); try: - pt = transform.transformBoundingBox(geom); + pt = transform.transform(geom); result = SpatialPoint(crs2, pt) except: print('Can not transform from {} to {} on QgsPoint {}'.format( \ diff --git a/timeseriesviewer/virtualrasters.py b/timeseriesviewer/virtualrasters.py new file mode 100644 index 0000000000000000000000000000000000000000..d59059e823d06f7a77bf0140f66d150e52fb365a --- /dev/null +++ b/timeseriesviewer/virtualrasters.py @@ -0,0 +1,294 @@ +import os, sys, re +import tempfile +from osgeo import gdal +from timeseriesviewer import file_search + +class VirtualBandInputSource(object): + def __init__(self, path, bandIndex): + self.path = path + self.bandIndex = bandIndex + self.noData = None + + + + +class VirtualBand(object): + + def __init__(self, name=''): + self.sources = [] + self.mName = name + + def addSourceBand(self, path, bandIndex): + self.sources.append(VirtualBandInputSource(path, bandIndex)) + + def sourceFiles(self): + files = set([inputSource.path for inputSource in self.sources]) + return sorted(list(files)) + + def __repr__(self): + infos = ['VirtualBand name="{}"'.format(self.mName)] + for i, info in enumerate(self.sources): + assert isinstance(info, VirtualBandInputSource) + infos.append('\t{} SourceFileName {} SourceBand {}'.format(i+1, info.path, info.bandIndex)) + return '\n'.join(infos) + +class VirtualRasterBuilder(object): + + def __init__(self): + self.vBands = [] + self.vMetadata = dict() + + def addVirtualBand(self, virtualBand): + assert isinstance(virtualBand, VirtualBand) + self.vBands.append(virtualBand) + + def insertVirtualBand(self, i, virtualBand): + assert isinstance(virtualBand, VirtualBand) + self.vBands.insert(i, virtualBand) + + def addFilesAsMosaic(self, files): + pass + + def addFilesAsStack(self, files): + pass + + def sourceFiles(self): + files = set() + for vBand in self.vBands: + assert isinstance(vBand, VirtualBand) + files.update(set(vBand.sourceFiles())) + return sorted(list(files)) + + def saveVRT(self, pathVRT): + + dn = os.path.dirname(pathVRT) + if not os.path.isdir(dn): + os.mkdir(dn) + + srcFiles = self.sourceFiles() + srcNodata = None + for src in srcFiles: + ds = gdal.Open(src) + band = ds.GetRasterBand(1) + noData = band.GetNoDataValue() + if noData and srcNodata is None: + srcNodata = noData + + vro = gdal.BuildVRTOptions(separate=True, + srcNodata=srcNodata) + #1. build a temporary VRT that described the spatial shifts of all input sources + gdal.BuildVRT(pathVRT, srcFiles, options=vro) + dsVRTDst = gdal.Open(pathVRT) + assert isinstance(dsVRTDst, gdal.Dataset) + assert len(srcFiles) == dsVRTDst.RasterCount + ns, nl = dsVRTDst.RasterXSize, dsVRTDst.RasterYSize + gt = dsVRTDst.GetGeoTransform() + crs = dsVRTDst.GetProjectionRef() + eType = dsVRTDst.GetRasterBand(1).DataType + SOURCE_TEMPLATES = dict() + for i, srcFile in enumerate(srcFiles): + vrt_sources = dsVRTDst.GetRasterBand(i+1).GetMetadata('vrt_sources') + assert len(vrt_sources) == 1 + srcXML = vrt_sources.values()[0] + assert os.path.basename(srcFile)+'</SourceFilename>' in srcXML + assert '<SourceBand>1</SourceBand>' in srcXML + SOURCE_TEMPLATES[srcFile] = srcXML + dsVRTDst = None + #remove the temporary VRT, we don't need it any more + os.remove(pathVRT) + + #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 + assert isinstance(dsVRTDst, gdal.Dataset) + dsVRTDst.SetProjection(crs) + dsVRTDst.SetGeoTransform(gt) + + #2.2. add virtual bands + for i, vBand in enumerate(self.vBands): + assert isinstance(vBand, VirtualBand) + assert dsVRTDst.AddBand(eType, options=['subClass=VRTSourcedRasterBand']) == 0 + vrtBandDst = dsVRTDst.GetRasterBand(i+1) + assert isinstance(vrtBandDst, gdal.Band) + vrtBandDst.SetDescription(vBand.mName) + md = {} + #add all input sources for this virtual band + for iSrc, sourceInfo in enumerate(vBand.sources): + assert isinstance(sourceInfo, VirtualBandInputSource) + bandIndex = sourceInfo.bandIndex + xml = SOURCE_TEMPLATES[sourceInfo.path] + xml = re.sub('<SourceBand>1</SourceBand>','<SourceBand>{}</SourceBand>'.format(bandIndex+1), xml) + md['source_{}'.format(iSrc)] = xml + vrtBandDst.SetMetadata(md,'vrt_sources') + if False: + vrtBandDst.ComputeBandStats(1) + + + dsVRTDst = None + + #check if we get what we like to get + dsCheck = gdal.Open(pathVRT) + + s = "" + return dsCheck + + def __repr__(self): + + info = ['VirtualRasterBuilder: {} bands, {} source files'.format( + len(self.vBands), len(self.sourceFiles()))] + for vBand in self.vBands: + info.append(str(vBand)) + return '\n'.join(info) + +def createVirtualBandMosaic(bandFiles, pathVRT): + drv = gdal.GetDriverByName('VRT') + + refPath = bandFiles[0] + refDS = gdal.Open(refPath) + ns, nl, nb = refDS.RasterXSize, refDS.RasterYSize, refDS.RasterCount + noData = refDS.GetRasterBand(1).GetNoDataValue() + + vrtOptions = gdal.BuildVRTOptions( + # here we can use the options known from http://www.gdal.org/gdalbuildvrt.html + separate=False + ) + if len(bandFiles) > 1: + s ="" + vrtDS = gdal.BuildVRT(pathVRT, bandFiles, options=vrtOptions) + vrtDS.FlushCache() + + assert vrtDS.RasterCount == nb + return vrtDS + +def createVirtualBandStack(bandFiles, pathVRT): + + nb = len(bandFiles) + + drv = gdal.GetDriverByName('VRT') + + refPath = bandFiles[0] + refDS = gdal.Open(refPath) + ns, nl = refDS.RasterXSize, refDS.RasterYSize + noData = refDS.GetRasterBand(1).GetNoDataValue() + + vrtOptions = gdal.BuildVRTOptions( + # here we can use the options known from http://www.gdal.org/gdalbuildvrt.html + separate=True + ) + vrtDS = gdal.BuildVRT(pathVRT, bandFiles, options=vrtOptions) + vrtDS.FlushCache() + + assert vrtDS.RasterCount == nb + + #copy band metadata from + for i in range(nb): + band = vrtDS.GetRasterBand(i+1) + band.SetDescription(bandFiles[i]) + band.ComputeBandStats() + + if noData: + band.SetNoDataValue(noData) + + return vrtDS + + +def groupRapidEyeTiles(dirIn, dirOut): + """ + + :param dirIn: + :param dirOut: + :return: + """ + + files = file_search(dirIn, '*_RE*_3A_*2.tif', recursive=True) + + if not os.path.exists(dirOut): + os.mkdir(dirOut) + + sources = dict() + for file in files: + if not file.endswith('.tif'): + continue + dn = os.path.dirname(file) + bn = os.path.basename(file) + print(bn) + id, date, sensor, product, _ = tuple(bn.split('_')) + + if not date in sources.keys(): + sources[date] = [] + sources[date].append(file) + for date, files in sources.items(): + pathVRT = os.path.join(dirOut, 're_{}.vrt'.format(date)) + createVirtualBandMosaic(files, pathVRT) + +def groupCBERS(dirIn, dirOut): + files = file_search(dirIn, 'CBERS*.tif', recursive=True) + + if not os.path.exists(dirOut): + os.mkdir(dirOut) + + CONTAINERS = dict() + for file in files: + dn = os.path.dirname(file) + bn = os.path.basename(file) + #basenames like CBERS_4_MUX_20150603_167_107_L4_BAND5_GRID_SURFACE.tif + splitted = bn.split('_') + id = '_'.join(splitted[:4]) + bandName = splitted[7] + + if id not in CONTAINERS.keys(): + CONTAINERS[id] = dict() + + bandSources = CONTAINERS[id] + if bandName not in bandSources.keys(): + bandSources[bandName] = list() + bandSources[bandName].append(file) + + #mosaic all scenes of same date + # and stack all bands related to the same channel + for id, bandSources in CONTAINERS.items(): + + pathVRT = id + '.vrt' + pathVRT = os.path.join(dirOut, pathVRT) + V = VirtualRasterBuilder() + + #vrt = createVirtualBandStack(bandSources, pathVRT) + #add bands in sorted order + for bandName in sorted(bandSources.keys()): + vBandSources = bandSources[bandName] + VB = VirtualBand(name=bandName) + for path in vBandSources: + VB.addSourceBand(path, 0) #it's always one band only + + V.addVirtualBand(VB) + #print(V) + V.saveVRT(pathVRT) + s = "" + #add ISO time stamp + + pass + +def groupLandsat(dirIn, dirOut): + + pass + + +if __name__ == '__main__': + if True: + dirIn = r'H:\CBERS\hugo\Download20170523' + dirOut = r'H:\CBERS\VRTs' + + groupCBERS(dirIn, dirOut) + exit(0) + + if True: + dirIn = r'H:\CBERS\hugo\Download20170523' + dirOut = r'H:\CBERS\VRTs' + groupCBERS(dirIn, dirOut) + + if True: + dirIn = r'H:\RapidEye\3A' + dirOut = r'H:\RapidEye\VRTs' + groupRapidEyeTiles(dirIn, dirOut) \ No newline at end of file