Skip to content
Snippets Groups Projects
mapvisualization.py 56.4 KiB
Newer Older
  • Learn to ignore specific revisions
  • # -*- coding: utf-8 -*-
    """
    /***************************************************************************
                                  HUB TimeSeriesViewer
                                  -------------------
            begin                : 2015-08-20
            git sha              : $Format:%H$
            copyright            : (C) 2017 by HU-Berlin
            email                : benjamin.jakimow@geo.hu-berlin.de
     ***************************************************************************/
    
    /***************************************************************************
     *                                                                         *
     *   This program is free software; you can redistribute it and/or modify  *
     *   it under the terms of the GNU General Public License as published by  *
     *   the Free Software Foundation; either version 2 of the License, or     *
     *   (at your option) any later version.                                   *
     *                                                                         *
     ***************************************************************************/
    """
    # noinspection PyPep8Naming
    from __future__ import absolute_import
    
    import os, sys, re, fnmatch, collections, copy, traceback, six, bisect
    import logging
    logger = logging.getLogger(__name__)
    from qgis.core import *
    
    from PyQt4.QtXml import *
    from PyQt4.QtCore import *
    from PyQt4.QtGui import *
    
    import numpy as np
    from timeseriesviewer.utils import *
    from timeseriesviewer.main import TimeSeriesViewer
    from timeseriesviewer.timeseries import SensorInstrument, TimeSeriesDatum
    
    from timeseriesviewer.ui.docks import TsvDockWidgetBase, loadUi
    
    from timeseriesviewer.ui.widgets import TsvMimeDataUtils, maxWidgetSizes
    
    from timeseriesviewer.mapcanvas import MapCanvas
    
    class MapView(QObject):
    
        sigRemoveMapView = pyqtSignal(object)
        sigMapViewVisibility = pyqtSignal(bool)
        sigVectorVisibility = pyqtSignal(bool)
    
        sigTitleChanged = pyqtSignal(str)
        sigSensorRendererChanged = pyqtSignal(SensorInstrument, QgsRasterRenderer)
        from timeseriesviewer.crosshair import CrosshairStyle
        sigCrosshairStyleChanged = pyqtSignal(CrosshairStyle)
        sigShowCrosshair = pyqtSignal(bool)
        sigVectorLayerChanged = pyqtSignal()
    
    
    
        def __init__(self, mapViewCollection, recommended_bands=None, parent=None):
            super(MapView, self).__init__()
            assert isinstance(mapViewCollection, MapViewCollection)
    
            self.mapViewCollection = mapViewCollection
    
            self.sensorViews = collections.OrderedDict()
    
            self.spatTempVis = mapViewCollection.STV
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            self.ui = None
            #self.ui = MapViewDefinitionUI(self, parent=parent)
            #self.ui.create()
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            #self.setVisibility(True)
    
            self.setVectorLayer(None)
    
            #forward actions with reference to this band view
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
            #self.ui.actionRemoveMapView.triggered.connect(lambda: self.sigRemoveMapView.emit(self))
            #self.ui.actionApplyStyles.triggered.connect(self.applyStyles)
            #self.ui.actionShowCrosshair.toggled.connect(self.setShowCrosshair)
            #self.ui.sigShowMapView.connect(lambda: self.sigMapViewVisibility.emit(True))
            #self.ui.sigHideMapView.connect(lambda: self.sigMapViewVisibility.emit(False))
            #self.ui.sigVectorVisibility.connect(self.sigVectorVisibility.emit)
    
    
        def mapCanvases(self):
            m = []
            for sensor, sensorView in self.sensorViews.items():
                m.extend(sensorView.mapCanvases())
            return m
    
        def vectorLayerRenderer(self):
            if isinstance(self.mVectorLayer, QgsVectorLayer):
                return self.mVectorLayer.rendererV2()
            return None
    
        def setVectorLayerRenderer(self, renderer):
            if isinstance(renderer, QgsFeatureRendererV2) and \
                isinstance(self.mVectorLayer, QgsVectorLayer):
                self.mVectorLayer.setRendererV2(renderer)
    
    
        def setVectorLayer(self, lyr):
            if isinstance(lyr, QgsVectorLayer):
    
                #add vector layer
                self.mVectorLayer = lyr
                self.mVectorLayer.rendererChanged.connect(self.sigVectorLayerChanged)
    
                for mapCanvas in self.mapCanvases():
                    assert isinstance(mapCanvas, MapCanvas)
    
                    mapCanvas.setLayers([l for l in mapCanvas.layers() if isinstance(l, QgsRasterLayer)])
                    mapCanvas.setLazyVectorSources([lyr])
    
                #remove vector layers
                self.mVectorLayer = None
    
                self.ui.btnVectorOverlayVisibility.setEnabled(False)
    
                for mapCanvas in self.mapCanvases():
                    mapCanvas.setLayers([l for l in mapCanvas.mLayers if not isinstance(l, QgsVectorLayer)])
                    #mapCanvas.refresh()
    
        def applyStyles(self):
            for sensorView in self.sensorViews.values():
                sensorView.applyStyle()
            s = ""
    
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        #def setVisibility(self, isVisible):
        #    self.ui.setVisibility(isVisible)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        #def visibility(self):
        #    return self.ui.visibility()
    
            return isinstance(self.mVectorLayer, QgsVectorLayer) and \
                   self.ui.btnVectorOverlayVisibility.isChecked()
    
    
    
    
        def setTitle(self, title):
            self.mTitle = title
            #self.ui.setTitle('Map View' + title)
            self.sigTitleChanged.emit(self.mTitle)
    
        def title(self):
            return self.mTitle
    
        def setCrosshairStyle(self, crosshairStyle):
            self.sigCrosshairStyleChanged.emit(crosshairStyle)
        def setShowCrosshair(self, b):
            self.sigShowCrosshair.emit(b)
    
        def removeSensor(self, sensor):
            assert type(sensor) is SensorInstrument
            if sensor in self.sensorViews.keys():
                w = self.sensorViews.pop(sensor)
                assert isinstance(w, MapViewSensorSettings)
    
                l = self.ui.sensorList.layout()
    
                l.removeWidget(w.ui)
                w.ui.close()
                self.ui.adjustSize()
                return True
            else:
                return False
    
        def hasSensor(self, sensor):
            assert type(sensor) is SensorInstrument
            return sensor in self.sensorViews.keys()
    
    
        def registerMapCanvas(self, sensor, mapCanvas):
            from timeseriesviewer.mapcanvas import MapCanvas
            assert isinstance(mapCanvas, MapCanvas)
            assert isinstance(sensor, SensorInstrument)
    
            #set basic settings
            sensorView = self.sensorViews[sensor]
    
            assert isinstance(sensorView, MapViewSensorSettings)
            sensorView.registerMapCanvas(mapCanvas)
    
            #register signals sensor specific signals
    
            mapCanvas.setRenderer(sensorView.rasterLayerRenderer())
    
            #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)
    
        def addSensor(self, sensor):
            """
            :param sensor:
            :return:
            """
            assert type(sensor) is SensorInstrument
            assert sensor not in self.sensorViews.keys()
    
            w = MapViewSensorSettings(sensor)
    
            #w.showSensorName(False)
            self.sensorViews[sensor] = w
    
            l = self.ui.sensorList.layout()
    
            lastWidgetIndex = 0
            for i in range(l.count()):
                if isinstance(l.itemAt(i), QWidget):
                    lastWidgetIndex = i
            l.insertWidget(lastWidgetIndex, w.ui)
    
    
        def getSensorWidget(self, sensor):
            assert type(sensor) is SensorInstrument
            return self.sensorViews[sensor]
    
    
    
    
    class MapViewRenderSettingsUI(QGroupBox, loadUi('mapviewrendersettings.ui')):
    
    
        def __init__(self, parent=None):
            """Constructor."""
            super(MapViewRenderSettingsUI, self).__init__(parent)
            # Set up the user interface from Designer.
            # After setupUI you can access any designer object by doing
            # self.<objectname>, and you can use autoconnect slots - see
            # http://qt-project.org/doc/qt-4.8/designer-using-a-ui-file.html
            # #widgets-and-dialogs-with-auto-connect
    
            self.setupUi(self)
    
            self.btnDefaultMB.setDefaultAction(self.actionSetDefaultMB)
            self.btnTrueColor.setDefaultAction(self.actionSetTrueColor)
            self.btnCIR.setDefaultAction(self.actionSetCIR)
            self.btn453.setDefaultAction(self.actionSet453)
    
            self.btnSingleBandDef.setDefaultAction(self.actionSetDefaultSB)
            self.btnSingleBandBlue.setDefaultAction(self.actionSetB)
            self.btnSingleBandGreen.setDefaultAction(self.actionSetG)
            self.btnSingleBandRed.setDefaultAction(self.actionSetR)
            self.btnSingleBandNIR.setDefaultAction(self.actionSetNIR)
            self.btnSingleBandSWIR.setDefaultAction(self.actionSetSWIR)
    
            self.btnPasteStyle.setDefaultAction(self.actionPasteStyle)
            self.btnCopyStyle.setDefaultAction(self.actionCopyStyle)
            self.btnApplyStyle.setDefaultAction(self.actionApplyStyle)
    
    
    
    class MapViewSensorSettings(QObject):
        """
        Describes the rendering of images of one Sensor
        """
    
    
        #sigSensorRendererChanged = pyqtSignal(QgsRasterRenderer)
    
    
        def __init__(self, sensor, parent=None):
            """Constructor."""
            super(MapViewSensorSettings, self).__init__(parent)
            from timeseriesviewer.timeseries import SensorInstrument
            assert isinstance(sensor, SensorInstrument)
            self.sensor = sensor
    
            self.ui = MapViewRenderSettingsUI(parent)
            self.ui.create()
    
            self.ui.stackedWidget.currentChanged.connect(self.updateUi)
    
            self.sensor.sigNameChanged.connect(self.onSensorNameChanged)
            self.onSensorNameChanged(self.sensor.name())
    
            self.ui.bandNames = sensor.bandNames
    
            self.multiBandMinValues = [self.ui.tbRedMin, self.ui.tbGreenMin, self.ui.tbBlueMin]
            self.multiBandMaxValues = [self.ui.tbRedMax, self.ui.tbGreenMax, self.ui.tbBlueMax]
            self.multiBandSliders = [self.ui.sliderRed, self.ui.sliderGreen, self.ui.sliderBlue]
    
            for tb in self.multiBandMinValues + self.multiBandMaxValues + [self.ui.tbSingleBandMin, self.ui.tbSingleBandMax]:
                tb.setValidator(QDoubleValidator())
            for sl in self.multiBandSliders + [self.ui.sliderSingleBand]:
                sl.setMinimum(1)
                sl.setMaximum(sensor.nb)
                sl.valueChanged.connect(self.updateUi)
    
    
            self.ceAlgs = collections.OrderedDict()
    
            self.ceAlgs["No enhancement"] = QgsContrastEnhancement.NoEnhancement
            self.ceAlgs["Stretch to MinMax"] = QgsContrastEnhancement.StretchToMinimumMaximum
            self.ceAlgs["Stretch and clip to MinMax"] = QgsContrastEnhancement.StretchAndClipToMinimumMaximum
            self.ceAlgs["Clip to MinMax"] = QgsContrastEnhancement.ClipToMinimumMaximum
    
            self.colorRampType = collections.OrderedDict()
            self.colorRampType['Interpolated'] = QgsColorRampShader.INTERPOLATED
            self.colorRampType['Discrete'] = QgsColorRampShader.DISCRETE
            self.colorRampType['Exact'] = QgsColorRampShader.EXACT
    
            self.colorRampClassificationMode = collections.OrderedDict()
            self.colorRampClassificationMode['Continuous'] = 1
            self.colorRampClassificationMode['Equal Interval'] = 2
            self.colorRampClassificationMode['Quantile'] = 3
    
            def populateCombobox(cb, d):
                for key, value in d.items():
                    cb.addItem(key, value)
                cb.setCurrentIndex(0)
    
            populateCombobox(self.ui.comboBoxContrastEnhancement, self.ceAlgs)
            populateCombobox(self.ui.cbSingleBandColorRampType, self.colorRampType)
            populateCombobox(self.ui.cbSingleBandMode, self.colorRampClassificationMode)
    
            self.ui.cbSingleBandColorRamp.populate(QgsStyleV2.defaultStyle())
    
    
            nb = self.sensor.nb
            lyr = QgsRasterLayer(self.sensor.pathImg)
    
            #define default renderers:
            bands = [min([b,nb-1]) for b in range(3)]
            extent = lyr.extent()
    
            bandStats = [lyr.dataProvider().bandStatistics(b, QgsRasterBandStats.All, extent, 500) for b in range(nb)]
    
            def createEnhancement(bandIndex):
                bandIndex = min([nb - 1, bandIndex])
                e = QgsContrastEnhancement(self.sensor.bandDataType)
                e.setMinimumValue(bandStats[bandIndex].Min)
                e.setMaximumValue(bandStats[bandIndex].Max)
                e.setContrastEnhancementAlgorithm(QgsContrastEnhancement.StretchToMinimumMaximum)
                return e
    
            self.defaultMB = QgsMultiBandColorRenderer(lyr.dataProvider(), bands[0], bands[1], bands[2])
            self.defaultMB.setRedContrastEnhancement(createEnhancement(bands[0]))
            self.defaultMB.setGreenContrastEnhancement(createEnhancement(bands[1]))
            self.defaultMB.setBlueContrastEnhancement(createEnhancement(bands[2]))
    
            self.defaultSB = QgsSingleBandPseudoColorRenderer(lyr.dataProvider(), 0, None)
    
            colorRamp = self.ui.cbSingleBandColorRamp.currentColorRamp()
    
            #fix: QGIS 3.0 constructor
            shaderFunc = QgsColorRampShader(bandStats[0].Min, bandStats[0].Max)
            shaderFunc.setColorRampType(QgsColorRampShader.INTERPOLATED)
            shaderFunc.setClip(True)
            nSteps = 5
            colorRampItems = []
            diff = bandStats[0].Max - bandStats[0].Min
            for  i in range(nSteps+1):
                f = float(i) / nSteps
                color = colorRamp.color(f)
                value = bandStats[0].Min + diff * f
                colorRampItems.append(QgsColorRampShader.ColorRampItem(value, color))
            shaderFunc.setColorRampItemList(colorRampItems)
            shader = QgsRasterShader()
    
            shader.setMaximumValue(bandStats[0].Max)
            shader.setMinimumValue(bandStats[0].Min)
    
            shader.setRasterShaderFunction(shaderFunc)
            self.defaultSB.setShader(shader)
            self.defaultSB.setClassificationMin(shader.minimumValue())
            self.defaultSB.setClassificationMax(shader.maximumValue())
            #init connect signals
            self.ui.actionSetDefaultMB.triggered.connect(lambda : self.setBandSelection('defaultMB'))
            self.ui.actionSetTrueColor.triggered.connect(lambda: self.setBandSelection('TrueColor'))
            self.ui.actionSetCIR.triggered.connect(lambda: self.setBandSelection('CIR'))
            self.ui.actionSet453.triggered.connect(lambda: self.setBandSelection('453'))
    
            self.ui.actionSetDefaultSB.triggered.connect(lambda: self.setBandSelection('defaultSB'))
            self.ui.actionSetB.triggered.connect(lambda: self.setBandSelection('B'))
            self.ui.actionSetG.triggered.connect(lambda: self.setBandSelection('G'))
            self.ui.actionSetR.triggered.connect(lambda: self.setBandSelection('R'))
            self.ui.actionSetNIR.triggered.connect(lambda: self.setBandSelection('nIR'))
            self.ui.actionSetSWIR.triggered.connect(lambda: self.setBandSelection('swIR'))
    
    
    
            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())
    
            #self.ui.stackedWidget
    
            if not self.sensor.wavelengthsDefined():
                self.ui.btnTrueColor.setEnabled(False)
                self.ui.btnCIR.setEnabled(False)
                self.ui.btn453.setEnabled(False)
    
                self.ui.btnSingleBandBlue.setEnabled(False)
                self.ui.btnSingleBandGreen.setEnabled(False)
                self.ui.btnSingleBandRed.setEnabled(False)
                self.ui.btnSingleBandNIR.setEnabled(False)
                self.ui.btnSingleBandSWIR.setEnabled(False)
    
            #apply recent or default renderer
            renderer = lyr.renderer()
    
            #set defaults
            self.setLayerRenderer(self.defaultSB)
            self.setLayerRenderer(self.defaultMB)
    
            if type(renderer) in [QgsMultiBandColorRenderer, QgsSingleBandPseudoColorRenderer]:
                self.setLayerRenderer(renderer)
    
    
            QApplication.clipboard().dataChanged.connect(self.onClipboardChange)
            self.onClipboardChange()
    
    
        def mapCanvases(self):
            return self.mMapCanvases[:]
    
    
        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())
            self.ui.actionApplyStyle.setToolTip('Apply style to all map view images from "{}"'.format(self.sensor.name()))
    
        def pasteStyleFromClipboard(self):
            utils = TsvMimeDataUtils(QApplication.clipboard().mimeData())
            if utils.hasRasterStyle():
                renderer = utils.rasterStyle(self.sensor.bandDataType)
                if renderer is not None:
                    self.setLayerRenderer(renderer)
    
    
            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):
    
    
            if key == 'defaultMB':
                bands = [self.defaultMB.redBand(), self.defaultMB.greenBand(), self.defaultMB.blueBand()]
            elif key == 'defaultSB':
                bands = [self.defaultSB.band()]
    
            else:
                if key in ['R','G','B','nIR','swIR']:
                    colors = [key]
                elif key == 'TrueColor':
                    colors = ['R','G','B']
                elif key == 'CIR':
                    colors = ['nIR', 'R', 'G']
                elif key == '453':
                    colors = ['nIR','swIR', 'R']
                bands = [self.sensor.bandClosestToWavelength(c) for c in colors]
    
            if len(bands) == 1:
                self.ui.sliderSingleBand.setValue(bands[0]+1)
            elif len(bands) == 3:
                for i, b in enumerate(bands):
                    self.multiBandSliders[i].setValue(b+1)
    
    
    
            cw = self.ui.stackedWidget.currentWidget()
            text = ''
            if cw == self.ui.pageMultiBand:
                text = 'RGB {} {} {}'.format(
                    self.ui.sliderRed.value(),
                    self.ui.sliderGreen.value(),
                    self.ui.sliderBlue.value()
                )
            elif cw == self.ui.pageSingleBand:
                text = 'Band {}'.format(self.ui.sliderSingleBand.value())
    
    
    
            self.ui.labelSummary.setText(text)
    
    
        def setLayerRenderer(self, renderer):
            ui = self.ui
            assert isinstance(renderer, QgsRasterRenderer)
    
            from timeseriesviewer.utils import niceNumberString
    
            updated = False
            if isinstance(renderer, QgsMultiBandColorRenderer):
                self.ui.cbRenderType.setCurrentIndex(0)
                #self.ui.stackedWidget.setcurrentWidget(self.ui.pageMultiBand)
    
                for s in self.multiBandSliders:
                    s.blockSignals(True)
                ui.sliderRed.setValue(renderer.redBand())
                ui.sliderGreen.setValue(renderer.greenBand())
                ui.sliderBlue.setValue(renderer.blueBand())
                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]):
    
                    vMin = ce.minimumValue()
                    vMax = ce.maximumValue()
    
                    self.multiBandMinValues[i].setText(niceNumberString(vMin))
                    self.multiBandMaxValues[i].setText(niceNumberString(vMax))
    
    
                idx = self.ceAlgs.values().index(ceRed.contrastEnhancementAlgorithm())
                ui.comboBoxContrastEnhancement.setCurrentIndex(idx)
                #self.updateUi()
                updated = True
    
            if isinstance(renderer, QgsSingleBandPseudoColorRenderer):
                self.ui.cbRenderType.setCurrentIndex(1)
                #self.ui.stackedWidget.setCurrentWidget(self.ui.pageSingleBand)
    
                self.ui.sliderSingleBand.setValue(renderer.band())
                shader = renderer.shader()
                cmin = shader.minimumValue()
                cmax = shader.maximumValue()
                self.ui.tbSingleBandMin.setText(str(cmin))
                self.ui.tbSingleBandMax.setText(str(cmax))
    
                shaderFunc = shader.rasterShaderFunction()
                self.ui.cbSingleBandColorRampType.setCurrentIndex(shaderFunc.colorRampType())
                updated = True
    
            self.updateUi()
    
            if updated and MapViewSensorSettings.SignalizeImmediately:
    
                #self.sigSensorRendererChanged.emit(renderer.clone())
                self.applyStyle()
    
            doc = QDomDocument()
            root = doc.createElement('qgis')
    
            return None
    
        def currentComboBoxItem(self, cb):
            d = cb.itemData(cb.currentIndex(), Qt.UserRole)
            return d
    
    
            ui = self.ui
            r = None
            if ui.stackedWidget.currentWidget() == ui.pageMultiBand:
                r = QgsMultiBandColorRenderer(None,
                    ui.sliderRed.value(), ui.sliderGreen.value(), ui.sliderBlue.value())
    
                i = self.ui.comboBoxContrastEnhancement.currentIndex()
                alg = self.ui.comboBoxContrastEnhancement.itemData(i)
    
                if alg == QgsContrastEnhancement.NoEnhancement:
                    r.setRedContrastEnhancement(None)
                    r.setGreenContrastEnhancement(None)
                    r.setBlueContrastEnhancement(None)
                else:
                    rgbEnhancements = []
                    for i in range(3):
                        e = QgsContrastEnhancement(self.sensor.bandDataType)
                        minmax = [float(self.multiBandMinValues[i].text()), float(self.multiBandMaxValues[i].text())]
                        cmin = min(minmax)
                        cmax = max(minmax)
                        e.setMinimumValue(cmin)
                        e.setMaximumValue(cmax)
                        e.setContrastEnhancementAlgorithm(alg)
                        rgbEnhancements.append(e)
                    r.setRedContrastEnhancement(rgbEnhancements[0])
                    r.setGreenContrastEnhancement(rgbEnhancements[1])
                    r.setBlueContrastEnhancement(rgbEnhancements[2])
    
            if ui.stackedWidget.currentWidget() == ui.pageSingleBand:
                r = QgsSingleBandPseudoColorRenderer(None, ui.sliderSingleBand.value(), None)
                minmax = [float(ui.tbSingleBandMin.text()), float(ui.tbSingleBandMax.text())]
                cmin = min(minmax)
                cmax = max(minmax)
                r.setClassificationMin(cmin)
                r.setClassificationMax(cmax)
                colorRamp = self.ui.cbSingleBandColorRamp.currentColorRamp()
    
                # fix: QGIS 3.0 constructor
                shaderFunc = QgsColorRampShader(cmin, cmax)
                shaderFunc.setColorRampType(self.currentComboBoxItem(ui.cbSingleBandColorRampType))
                shaderFunc.setClip(True)
                nSteps = 10
                colorRampItems = []
                diff = cmax - cmin
                for i in range(nSteps + 1):
                    f = float(i) / nSteps
                    color = colorRamp.color(f)
                    value = cmin + diff * f
                    colorRampItems.append(QgsColorRampShader.ColorRampItem(value, color))
                shaderFunc.setColorRampItemList(colorRampItems)
                shader = QgsRasterShader()
                shader.setMaximumValue(cmax)
                shader.setMinimumValue(cmin)
                shader.setRasterShaderFunction(shaderFunc)
                r.setShader(shader)
    
                s = ""
            return r
    
    
    class DatumView(QObject):
    
    
        sigRenderProgress = pyqtSignal(int,int)
        sigLoadingStarted = pyqtSignal(MapView, TimeSeriesDatum)
        sigLoadingFinished = pyqtSignal(MapView, TimeSeriesDatum)
        sigVisibilityChanged = pyqtSignal(bool)
    
    
        def __init__(self, timeSeriesDatum, timeSeriesDateViewCollection, mapViewCollection, parent=None):
            assert isinstance(timeSeriesDatum, TimeSeriesDatum)
    
            assert isinstance(timeSeriesDateViewCollection, DateViewCollection)
    
            assert isinstance(mapViewCollection, MapViewCollection)
    
    
            super(DatumView, self).__init__()
    
            from timeseriesviewer.ui.widgets import TimeSeriesDatumViewUI
            self.ui = TimeSeriesDatumViewUI(parent=parent)
            self.ui.create()
    
            self.showLoading(False)
    
            self.L = self.ui.layout()
            self.wOffset = self.L.count()-1
            self.minHeight = self.ui.height()
            self.minWidth = 50
            self.renderProgress = dict()
    
            assert isinstance(mapViewCollection.STV, SpatialTemporalVisualization)
            self.STV = mapViewCollection.STV
    
            self.TSD = timeSeriesDatum
    
            self.scrollArea = timeSeriesDateViewCollection.scrollArea
    
            self.Sensor = self.TSD.sensor
    
            self.Sensor.sigNameChanged.connect(lambda :self.setColumnInfo())
    
            self.TSD.sigVisibilityChanged.connect(self.setVisibility)
    
            self.DVC = timeSeriesDateViewCollection
    
            self.mRenderState = dict()
    
            labelTxt = '{}\n{}'.format(str(self.TSD.date), self.TSD.sensor.name())
            tooltip = '{}'.format(self.TSD.pathImg)
    
    
            self.ui.labelTitle.setText(labelTxt)
            self.ui.labelTitle.setToolTip(tooltip)
    
    
        def setVisibility(self, b):
            self.ui.setVisible(b)
            self.sigVisibilityChanged.emit(b)
    
    
        def setMapViewVisibility(self, bandView, isVisible):
            self.mapCanvases[bandView].setVisible(isVisible)
    
    
        def sizeHint(self):
    
            if not self.ui.isVisible():
                return QSize(0,0)
    
    
            size = self.ui.sizeHint()
    
            m = self.ui.layout().contentsMargins()
            dx = m.left() + m.right()
            dy = self.ui.layout().spacing()
    
            n = len(self.mapCanvases)
            if n > 0:
    
                baseSize = self.mapCanvases.values()[0].size()
    
                size = QSize(baseSize.width()+ dx, \
                             size.height()+ n*dy)
    
            return size
    
    
    
        def removeMapView(self, mapView):
            canvas = self.mapCanvases.pop(mapView)
            self.L.removeWidget(canvas)
            canvas.close()
            self.adjustBaseMinSize()
    
        def refresh(self):
    
            if self.ui.isVisible():
                for c in self.mapCanvases.values():
                    if c.isVisible():
    
    
        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)
    
            # register MapCanvas on MV level
            mapView.registerMapCanvas(self.Sensor, mapCanvas)
            # register MapCanvas on STV level
            self.STV.registerMapCanvas(mapCanvas)
    
            mapCanvas.blockSignals(False)
    
            mapCanvas.renderComplete.connect(lambda : self.onRenderingChange(False))
            mapCanvas.renderStarting.connect(lambda : self.onRenderingChange(True))
    
    
            mapCanvas.sigDataLoadingFinished.connect(
                lambda dt: self.STV.TSV.ui.dockSystemInfo.addTimeDelta('Map {}'.format(self.Sensor.name()), dt))
            mapCanvas.sigDataLoadingFinished.connect(
                lambda dt: self.STV.TSV.ui.dockSystemInfo.addTimeDelta('All Sensors', dt))
    
    
        def showLoading(self, b):
            if b:
                self.ui.progressBar.setRange(0,0)
                self.ui.progressBar.setValue(-1)
            else:
                self.ui.progressBar.setRange(0,1)
                self.ui.progressBar.setValue(0)
    
        def onRenderingChange(self, b):
            mc = self.sender()
            #assert isinstance(mc, QgsMapCanvas)
            self.mRenderState[mc] = b
            self.showLoading(any(self.mRenderState.values()))
    
        def onRendering(self, *args):
            renderFlags = [m.renderFlag() for m in self.mapCanvases.values()]
            drawFlags = [m.isDrawing() for m in self.mapCanvases.values()]
            print((renderFlags, drawFlags))
            isLoading = any(renderFlags)
    
            self.showLoading(isLoading)
    
            s = ""
    
        def registerMapCanvas(self, mapView, mapCanvas):
            from timeseriesviewer.mapcanvas import MapCanvas
            assert isinstance(mapCanvas, MapCanvas)
            self.mapCanvases[mapView] = mapCanvas
    
            mapCanvas.setLazyRasterSources([self.TSD.pathImg])
    
            #mapCanvas.setLayers([QgsRasterLayer(self.TSD.pathImg)])
    
            self.L.insertWidget(self.wOffset + len(self.mapCanvases), mapCanvas)
            self.ui.update()
    
            #register signals handled on (this) DV level
            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)
    
            if key == 'copy_sensor':
                QApplication.clipboard().setText(self.TSD.sensor.name())
            if key == 'copy_date':
                QApplication.clipboard().setText(str(self.TSD.date))
            if key == 'copy_path':
                QApplication.clipboard().setText(str(self.TSD.pathImg))
    
        def __lt__(self, other):
            assert isinstance(other, DatumView)
            return self.TSD < other.TSD
    
            assert isinstance(other, DatumView)
            return self.TSD == other.TSD
    
        sigLoadingStarted = pyqtSignal(DatumView, MapView)
        sigLoadingFinished = pyqtSignal(DatumView, MapView)
    
        sigShowMapLayerInfo = pyqtSignal(dict)
        sigSpatialExtentChanged = pyqtSignal(SpatialExtent)
    
        sigMapSizeChanged = pyqtSignal(QSize)
    
        sigCRSChanged = pyqtSignal(QgsCoordinateReferenceSystem)
    
        sigActivateMapTool = pyqtSignal(str)
    
    
        def __init__(self, timeSeriesViewer):
            super(SpatialTemporalVisualization, self).__init__()
    
            #assert isinstance(timeSeriesViewer, TimeSeriesViewer), timeSeriesViewer
    
            self.mBlockCanvasSignals = False
    
            self.mSpatialExtent = SpatialExtent.world()
    
            self.mCRS = self.mSpatialExtent.crs()
            self.mSize = QSize(200,200)
            self.mColor = Qt.black
    
            self.mMapCanvases = []
    
            from timeseriesviewer.ui.widgets import TsvScrollArea
    
            assert isinstance(self.scrollArea, TsvScrollArea)
    
    
    
            self.mRefreshTimer = QTimer(self)
            self.mRefreshTimer.setInterval(1000)
            self.mRefreshTimer.timeout.connect(self.refresh)
    
            self.scrollArea.sigResized.connect(self.mRefreshTimer.start)
            self.scrollArea.horizontalScrollBar().valueChanged.connect(self.mRefreshTimer.start)
    
            self.TSV = timeSeriesViewer
            self.TS = timeSeriesViewer.TS
            self.targetLayout = self.ui.scrollAreaSubsetContent.layout()
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            #self.MVC = MapViewCollection(self)
            #self.MVC.sigShowProfiles.connect(self.sigShowProfiles.emit)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            self.MVC = self.ui.dockMapViewsV2
    
            self.MVC.sigShowProfiles.connect(self.sigShowProfiles.emit)
    
            self.vectorOverlay = None
    
    
            self.DVC = DateViewCollection(self)
    
            self.DVC.sigResizeRequired.connect(self.adjustScrollArea)
            self.DVC.sigLoadingStarted.connect(self.ui.dockRendering.addStartedWork)
            self.DVC.sigLoadingFinished.connect(self.ui.dockRendering.addFinishedWork)
            #self.timeSeriesDateViewCollection.sigSpatialExtentChanged.connect(self.setSpatialExtent)
            self.TS.sigTimeSeriesDatesAdded.connect(self.DVC.addDates)
            self.TS.sigTimeSeriesDatesRemoved.connect(self.DVC.removeDates)
    
            self.DVC.addDates(self.TS[:])
    
            if len(self.TS) > 0:
                self.setSpatialExtent(self.TS.getMaxSpatialExtent())
    
            #self.setSubsetSize(QSize(100,50))
    
        def registerMapCanvas(self, mapCanvas):
            from timeseriesviewer.mapcanvas import MapCanvas
            assert isinstance(mapCanvas, MapCanvas)
    
            self.mMapCanvases.append(mapCanvas)
    
            #set general canvas properties
            mapCanvas.setFixedSize(self.mSize)
            mapCanvas.setCrs(self.mCRS)
            mapCanvas.setSpatialExtent(self.mSpatialExtent)
    
    
            #register on map canvas signals
            mapCanvas.sigSpatialExtentChanged.connect(lambda e: self.setSpatialExtent(e, mapCanvas))
    
    
    
    
            from timeseriesviewer.mapcanvas import MapCanvas
            for mapCanvas in self.mMapCanvases:
                assert isinstance(mapCanvas, MapCanvas)
                mapCanvas.setCrosshairStyle(crosshairStyle)
    
            #self.MVC.setCrosshairStyle(crosshairStyle)
    
    
        def setShowCrosshair(self, b):
            self.MVC.setShowCrosshair(b)
    
        def setVectorLayer(self, lyr):
            self.MVC.setVectorLayer(lyr)
    
        def createMapView(self):
            self.MVC.createMapView()
    
        def activateMapTool(self, key):
    
            from timeseriesviewer.mapcanvas import MapCanvas
            for mapCanvas in self.mMapCanvases:
                assert isinstance(mapCanvas, MapCanvas)
                mapCanvas.activateMapTool(key)
    
        def setMapSize(self, size):
    
            self.mSize = size
    
            from timeseriesviewer.mapcanvas import MapCanvas
            for mapCanvas in self.mMapCanvases:
                assert isinstance(mapCanvas, MapCanvas)
                mapCanvas.setFixedSize(size)
    
            self.sigMapSizeChanged.emit(self.mSize)
    
        def subsetSize(self):
            return QSize(self.mSize)
    
    
            #print('STV REFRESH')
    
            for tsdView in self.DVC:
    
            self.mRefreshTimer.stop()
    
    
        def adjustScrollArea(self):
            #adjust scroll area widget to fit all visible widgets
            m = self.targetLayout.contentsMargins()
    
            n = len(self.DVC)
    
            for TSDView in [v for v in self.DVC if v.ui.isVisible()]:
                s = s + TSDView.sizeHint()
    
                    r = TSDView.sizeHint()
    
            if r:
                if isinstance(self.targetLayout, QHBoxLayout):
    
                    s = QSize(s.width(), r.height())
                else:
                    s = QSize(r.width(), s.height())
    
                s = s + QSize(m.left() + m.right(), m.top() + m.bottom())
                self.targetLayout.parentWidget().setFixedSize(s)
    
    
    
    
        def setMaxTSDViews(self, n=-1):
            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
    
            assert isinstance(extent, SpatialExtent)
            extent = extent.toCrs(self.mCRS)
            if not isinstance(extent, SpatialExtent) \
                or extent.isEmpty() or not extent.isFinite() \
                or extent.width() <= 0 \
                or extent.height() <= 0 \
                or extent == self.mSpatialExtent:
    
    
            self.mBlockCanvasSignals = True
            self.mSpatialExtent = extent
            for mapCanvas in self.mMapCanvases:
                if mapCanvas != mapCanvas0:
                    oldState = mapCanvas.blockSignals(True)
                    mapCanvas.setExtent(extent)
                    mapCanvas.blockSignals(oldState)
    
            self.mBlockCanvasSignals = False
    
            #for mapCanvas in self.mMapCanvases:
            #    mapCanvas.refresh()
    
            self.mRefreshTimer.start()
    
            self.sigSpatialExtentChanged.emit(extent)
    
        def setBackgroundColor(self, color):
            assert isinstance(color, QColor)
            self.mColor = color
    
        def backgroundColor(self):
            return self.mColor
    
    
        def mapCanvasIterator(self):
            return self.mMapCanvases[:]
    
        def setCrs(self, crs):
            assert isinstance(crs, QgsCoordinateReferenceSystem)
    
    
            if self.mCRS != crs:
    
                from timeseriesviewer.utils import saveTransform