Skip to content
Snippets Groups Projects
mapvisualization.py 62.8 KiB
Newer Older
# -*- 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
Benjamin Jakimow's avatar
Benjamin Jakimow committed
from __future__ import absolute_import, unicode_literals
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 *
Benjamin Jakimow's avatar
Benjamin Jakimow committed
from timeseriesviewer.timeseries import SensorInstrument, TimeSeriesDatum, TimeSeries
from timeseriesviewer.ui.docks import TsvDockWidgetBase, loadUI
from timeseriesviewer.main import TsvMimeDataUtils
from timeseriesviewer.ui.mapviewscrollarea import MapViewScrollArea
from timeseriesviewer.mapcanvas import MapCanvas
Benjamin Jakimow's avatar
Benjamin Jakimow committed
from timeseriesviewer.crosshair import CrosshairStyle

class MapViewUI(QFrame, loadUI('mapviewdefinition.ui')):
Benjamin Jakimow's avatar
Benjamin Jakimow committed


    def __init__(self, parent=None):
        super(MapViewUI, self).__init__(parent)
        self.setupUi(self)
        self.mSensors = collections.OrderedDict()

        m = QMenu(self.btnToggleCrosshair)
        m.addAction(self.actionSetCrosshairStyle)
        #a = m.addAction('Set Crosshair Style')

        self.btnToggleCrosshair.setMenu(m)

        #connect the QActions with the QgsCollapsibleGroupBoxes
        self.gbVectorRendering.toggled.connect(self.actionToggleVectorVisibility.toggle)
        self.gbRasterRendering.toggled.connect(self.actionToggleRasterVisibility.toggle)

        self.actionToggleVectorVisibility.toggled.connect(self.gbVectorRendering.setChecked)
        self.actionToggleRasterVisibility.toggled.connect(self.gbRasterRendering.setChecked)
        #self.actionToggleRasterVisibility.toggled.connect(self.dummy)

Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.btnToggleCrosshair.setDefaultAction(self.actionToggleCrosshairVisibility)
        self.btnToggleMapViewVisibility.setDefaultAction(self.actionToggleMapViewVisibility)

    def dummy(self, *args):
        print((self.sender(), args))
        print(args)
Benjamin Jakimow's avatar
Benjamin Jakimow committed

    def addSensor(self, sensor):
        assert isinstance(sensor, SensorInstrument)

        w = MapViewSensorSettings(sensor)
        #sizePolicy = QSizePolicy(QSize)
        #w.ui.
        l = self.renderSettingsLayout
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        assert sensor not in self.mSensors.keys()

Benjamin Jakimow's avatar
Benjamin Jakimow committed
        l.insertWidget(lastWidgetIndex, w.ui)
        self.mSensors[sensor] = w
        #self.resize(self.sizeHint())

        return w


    def removeSensor(self, sensor):

        assert isinstance(sensor, SensorInstrument)
        sensorSettings = self.mSensors.pop(sensor)
        assert isinstance(sensorSettings, MapViewSensorSettings)

Benjamin Jakimow's avatar
Benjamin Jakimow committed
        l.removeWidget(sensorSettings.ui)
        sensorSettings.ui.close()
        #self.resize(self.sizeHint())
class MapView(QObject):

    sigRemoveMapView = pyqtSignal(object)
    sigMapViewVisibility = pyqtSignal(bool)
    #sigVectorVisibility = pyqtSignal(bool)
    #sigRasterVisibility = pyqtSignal(bool)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    sigTitleChanged = pyqtSignal([str],[unicode])
    sigSensorRendererChanged = pyqtSignal(SensorInstrument, QgsRasterRenderer)
Benjamin Jakimow's avatar
Benjamin Jakimow committed

    sigCrosshairStyleChanged = pyqtSignal(CrosshairStyle)
    sigShowCrosshair = pyqtSignal(bool)
    sigVectorLayerChanged = pyqtSignal()

    sigShowProfiles = pyqtSignal(SpatialPoint, MapCanvas, str)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def __init__(self, mapViewCollection, name='Map View', recommended_bands=None, parent=None):
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        assert isinstance(mapViewCollection, MapViewCollectionDock)

        self.ui = MapViewUI(mapViewCollection.stackedWidget)
        self.ui.show()
        self.ui.cbQgsVectorLayer.setFilters(QgsMapLayerProxyModel.VectorLayer)
        self.ui.cbQgsVectorLayer.layerChanged.connect(self.setVectorLayer)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.ui.tbName.textChanged.connect(self.sigTitleChanged)
        from timeseriesviewer.crosshair import getCrosshairStyle
        self.ui.actionSetCrosshairStyle.triggered.connect(
            lambda : self.setCrosshairStyle(getCrosshairStyle(
                parent=self.ui,
                crosshairStyle=self.mCrosshairStyle))
        )

        self.mapViewCollection = mapViewCollection
        self.sensorViews = collections.OrderedDict()
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.mCrosshairStyle = CrosshairStyle()
        self.mShowCrosshair = True
Benjamin Jakimow's avatar
Benjamin Jakimow committed

Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.mIsVisible = True
        self.mVectorsVisible = True
        self.mRastersVisible = True

        self.ui.actionToggleVectorVisibility.setChecked(True)
        self.ui.actionToggleVectorVisibility.toggled.connect(self.setVectorVisibility)

        self.ui.actionToggleRasterVisibility.setChecked(True)
        self.ui.actionToggleRasterVisibility.toggled.connect(self.setRasterVisibility)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.ui.actionToggleCrosshairVisibility.toggled.connect(self.setShowCrosshair)
benjamin.jakimow@geo.hu-berlin.de's avatar
benjamin.jakimow@geo.hu-berlin.de committed
        self.ui.actionToggleMapViewVisibility.toggled.connect(lambda b: self.setIsVisible(not b))
Benjamin Jakimow's avatar
Benjamin Jakimow committed



        self.ui.actionToggleVectorVisibility.setChecked(True)
        self.ui.actionToggleRasterVisibility.setChecked(True)

Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.setTitle(name)
        #forward actions with reference to this band view
    def dummy(self, *args):
        print(args)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def setIsVisible(self, b):
        assert isinstance(b, bool)
benjamin.jakimow@geo.hu-berlin.de's avatar
benjamin.jakimow@geo.hu-berlin.de committed
        changed = b != self.mIsVisible

Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.mIsVisible = b

benjamin.jakimow@geo.hu-berlin.de's avatar
benjamin.jakimow@geo.hu-berlin.de committed
        for mapCanvas in self.mapCanvases():
            assert isinstance(mapCanvas, MapCanvas)
            mapCanvas.setVisible(b)
        if changed:
            self.sigMapViewVisibility.emit(b)


    def isVisible(self):
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        return self.mIsVisible

    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)

Benjamin Jakimow's avatar
Benjamin Jakimow committed

            #add vector layer
            self.mVectorLayer = lyr
            self.mVectorLayer.rendererChanged.connect(self.sigVectorLayerChanged)
            for mapCanvas in self.mapCanvases():
                assert isinstance(mapCanvas, MapCanvas)
                mapCanvas.layerModel().setVectorLayerSources([self.mVectorLayer])
                #mapCanvas.setLayers([l for l in mapCanvas.layers() if isinstance(l, QgsRasterLayer)])
                #mapCanvas.setLazyVectorSources([lyr])
            #remove vector layers
            self.mVectorLayer = None
            for mapCanvas in self.mapCanvases():
                mapCanvas.layerModel().setVectorLayerSources([])
                #mapCanvas.setLayers([l for l in mapCanvas.mLayers if not isinstance(l, QgsVectorLayer)])
                mapCanvas.refresh()
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.sigVectorLayerChanged.emit()
    def applyStyles(self):
        for sensorView in self.sensorViews.values():
            sensorView.applyStyle()

    def setTitle(self, title):
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        old = self.title()
        if old != title:
            self.ui.tbName.setText(title)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        return self.ui.tbName.text()
    def refreshMapView(self, *args):
        for mapCanvas in self.mapCanvases():
            assert isinstance(mapCanvas, MapCanvas)
            mapCanvas.refresh()

        if isinstance(crosshairStyle, CrosshairStyle):
            old = self.mCrosshairStyle
            self.mCrosshairStyle = crosshairStyle
            if old != self.mCrosshairStyle:
                self.sigCrosshairStyleChanged.emit(self.mCrosshairStyle)
Benjamin Jakimow's avatar
Benjamin Jakimow committed

    def setHighlighted(self, b=True, timeout=1000):
        styleOn = """.MapCanvas {
                    border: 4px solid red;
                    border-radius: 4px;
                }"""
        styleOff = """"""
        if b is True:
            for mapCanvas in self.mapCanvases():
                mapCanvas.setStyleSheet(styleOn)
            if timeout > 0:
                QTimer.singleShot(timeout, lambda : self.setHighlighted(False))
        else:
            for mapCanvas in self.mapCanvases():
                mapCanvas.setStyleSheet(styleOff)


Benjamin Jakimow's avatar
Benjamin Jakimow committed
        assert isinstance(b, bool)
        self.mShowCrosshair = b
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def showCrosshair(self):
        return self.mShowCrosshair and self.mCrosshairStyle is not None


    def rasterVisibility(self):
        return self.mRastersVisible

    def vectorVisibility(self):
        return self.mVectorsVisible

    def setRasterVisibility(self, b):
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        assert isinstance(b, bool)
        if self.rasterVisibility() != b:
            self.mRastersVisible = b
            self.ui.actionToggleRasterVisibility.setChecked(b)
Benjamin Jakimow's avatar
Benjamin Jakimow committed

            for mapCanvas in self.mapCanvases():
                assert isinstance(mapCanvas, MapCanvas)
                mapCanvas.layerModel().setRasterLayerVisibility(b)
                mapCanvas.refresh()

            #self.sigRasterVisibility.emit(b)

    def setVectorVisibility(self, b):
        assert isinstance(b, bool)
        if self.vectorVisibility() != b:
            self.mVectorsVisible = b
            self.ui.actionToggleVectorVisibility.setChecked(b)

            for mapCanvas in self.mapCanvases():
                assert isinstance(mapCanvas, MapCanvas)
                mapCanvas.layerModel().setVectorLayerVisibility(b)
                mapCanvas.refresh()
Benjamin Jakimow's avatar
Benjamin Jakimow committed

Benjamin Jakimow's avatar
Benjamin Jakimow committed
        assert sensor in self.sensorViews.keys()
        self.sensorViews.pop(sensor)
        self.ui.removeSensor(sensor)
        return True

    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)

        sensorView = self.sensorViews[sensor]
        assert isinstance(sensorView, MapViewSensorSettings)
        sensorView.registerMapCanvas(mapCanvas)

        #register signals sensor specific signals
        mapCanvas.setRenderer(sensorView.rasterLayerRenderer())
        mapCanvas.setRenderer(self.vectorLayerRenderer())
        #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 = self.ui.addSensor(sensor)
        w.sigSensorRendererChanged.connect(self.onSensorRenderingChanged)
        self.sensorViews[sensor] = w
        s  =""
Benjamin Jakimow's avatar
Benjamin Jakimow committed

    def onSensorRenderingChanged(self, renderer):
        sensorSettings = self.sender()
        assert isinstance(sensorSettings, MapViewSensorSettings)
        for mapCanvas in sensorSettings.mapCanvases():
            mapCanvas.setRenderer(renderer)
            #mapCanvas.refresh()

    def getSensorWidget(self, sensor):
        assert type(sensor) is SensorInstrument
        return self.sensorViews[sensor]



class MapViewRenderSettingsUI(QgsCollapsibleGroupBox, 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.textChanged.connect(self.onValuesChanged)

        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)


        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)


    def onValuesChanged(self, text):
        styleValid = ""
        styleInValid = """.QLineEdit {border-color:red;
                           border-style: outset;
                           border-width: 2px;
                           background-color: yellow }
                        """
        w = self.sender()
        if isinstance(w, QLineEdit):
            validator = w.validator()
            assert isinstance(validator, QDoubleValidator)
            res = validator.validate(text, 0)
            if res[0] == QDoubleValidator.Acceptable:
                w.setStyleSheet(styleValid)
            else:
                w.setStyleSheet(styleInValid)

        cw = self.ui.stackedWidget.currentWidget()
        text = ''
        if cw == self.ui.pageMultiBand:
                self.ui.sliderRed.value(),
                self.ui.sliderGreen.value(),
                self.ui.sliderBlue.value()
            )
        elif cw == self.ui.pageSingleBand:
            text = 'Singleband({})'.format(self.ui.sliderSingleBand.value())
        text = '{} - {}'.format(self.sensor.name(), text)
        self.ui.setTitle(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)
            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:
            self.mLastRenderer = self.rasterLayerRenderer()
        if updated and MapViewSensorSettings.SignalizeImmediately:
            self.sigSensorRendererChanged.emit(renderer.clone())
        if isinstance(r, QgsRasterRenderer):
            doc = QDomDocument()
            lyr = QgsRasterLayer(self.sensor.pathImg)
            lyr.setRenderer(self.rasterLayerRenderer())
            err = ''
            lyr.exportNamedStyle(doc, err)
            if len(err) == 0:
                mimeData.setData('application/qgis.style', doc.toByteArray())
                mimeData.setText(doc.toString())
        return mimeData

    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 = self.rasterRendererMultiBand(ui)

        if ui.stackedWidget.currentWidget() == ui.pageSingleBand:
            r = self.rasterRendererSingleBand(ui)
        return r
    def rasterRendererMultiBand(self, ui):
        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])
    def rasterRendererSingleBand(self, ui):
        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)
        return r
class DatumViewUI(QFrame, loadUI('timeseriesdatumview.ui')):
benjamin.jakimow@geo.hu-berlin.de's avatar
benjamin.jakimow@geo.hu-berlin.de committed
    """
    Widget to host the MapCanvases of all map views that relate to a single Datum-Sensor combinbation.
    """
    def __init__(self, title='<#>', parent=None):
        super(DatumViewUI, self).__init__(parent)
        self.setupUi(self)

    def sizeHint(self):
        m = self.layout().contentsMargins()

        s = QSize(0, 0)

        map = None
        widgets = [self.layout().itemAt(i).widget() for i in range(self.layout().count())]
        widgets = [w for w in widgets if isinstance(w, QWidget)]

        maps = [w for w in widgets if isinstance(w, MapCanvas)]
        others = [w for w in widgets if not isinstance(w, MapCanvas)]

        s = self.layout().spacing()
        m = self.layout().contentsMargins()

        def totalHeight(widgetList):
            total = QSize(0,0)
            for w in widgetList:
                ws = w.size()
                if ws.width() == 0:
                    ws = w.sizeHint()
                total.setWidth(max([total.width(), ws.width()]))
                total.setHeight(total.height() +  ws.height())
            return total

        baseSize = totalHeight(widgets)
        if baseSize.width() == 0:
            for o in others:
                baseSize.setWidth(9999)
        s = QSize(baseSize.width() + m.left() + m.right(),
                  baseSize.height() + m.top() + m.bottom())
        return s

"""
    def sizeHint(self):

        if not self.ui.isVisible():
            return QSize(0,0)
        else:
            #return self.ui.sizeHint()

            size = self.ui.sizeHint()
            s = self.ui.layout().spacing()
            m = self.ui.layout().contentsMargins()
            dx = m.left() + m.right() + s
            dy = self.ui.layout().spacing()

            n = len([m for m in self.mapCanvases.keys() if m.isVisible()])
            if n > 0:
                baseSize = self.mapCanvases.values()[0].size()
                size = QSize(baseSize.width()+ dx, \
                             size.height()+ (n+1)*(dy+2*s))
            else:
                s = ""
            return size


"""



class DatumView(QObject):

    sigRenderProgress = pyqtSignal(int,int)
    sigLoadingStarted = pyqtSignal(MapView, TimeSeriesDatum)
    sigLoadingFinished = pyqtSignal(MapView, TimeSeriesDatum)
    sigVisibilityChanged = pyqtSignal(bool)

Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def __init__(self, timeSeriesDatum, stv, parent=None):
        assert isinstance(timeSeriesDatum, TimeSeriesDatum)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        assert isinstance(stv, SpatialTemporalVisualization)

        super(DatumView, self).__init__()
benjamin.jakimow@geo.hu-berlin.de's avatar
benjamin.jakimow@geo.hu-berlin.de committed
        self.ui = DatumViewUI(parent=parent)
        self.showLoading(False)
        self.wOffset = self.ui.layout().count()-1
        #self.minWidth = 50
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        assert isinstance(stv, SpatialTemporalVisualization)
        self.STV = stv
        self.TSD = timeSeriesDatum
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.scrollArea = stv.scrollArea
        self.Sensor = self.TSD.sensor
        self.Sensor.sigNameChanged.connect(lambda :self.setColumnInfo())
        self.TSD.sigVisibilityChanged.connect(self.setVisibility)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.MVC = stv.MVC
        self.DVC = stv.DVC
        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 setHighlighted(self, b=True, timeout=1000):
        styleOn = """.QFrame {
                    border: 4px solid red;
                    border-radius: 4px;
                }"""
        styleOff = """"""
        if b is True:
            self.ui.setStyleSheet(styleOn)
            if timeout > 0:
                QTimer.singleShot(timeout, lambda : self.setHighlighted(b=False))
        else:
            self.ui.setStyleSheet(styleOff)


    def setMapViewVisibility(self, bandView, isVisible):
        self.mapCanvases[bandView].setVisible(isVisible)


    def removeMapView(self, mapView):
        canvas = self.mapCanvases.pop(mapView)
        self.ui.layout().removeWidget(canvas)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        #self.adjustBaseMinSize()
        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()]
        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.layerModel().setRasterLayerSources([self.TSD.pathImg])

        self.ui.layout().insertWidget(self.wOffset + len(self.mapCanvases), mapCanvas)
        self.ui.update()