Skip to content
Snippets Groups Projects
mapvisualization.py 67.4 KiB
Newer Older
  • Learn to ignore specific revisions
  • # noinspection PyPep8Naming
    
    """
    /***************************************************************************
    
                                  -------------------
            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.                                   *
     *                                                                         *
     ***************************************************************************/
    """
    
    
    import os, sys, re, fnmatch, collections, copy, traceback, bisect
    
    from qgis.core import QgsContrastEnhancement, QgsRasterShader, QgsColorRampShader,  QgsProject, QgsCoordinateReferenceSystem, \
        QgsRasterLayer, QgsVectorLayer, QgsMapLayer, QgsMapLayerProxyModel, QgsColorRamp, QgsSingleBandPseudoColorRenderer
    
    
    from qgis.gui import *
    from qgis.gui import QgsDockWidget, QgsMapCanvas, QgsMapTool, QgsCollapsibleGroupBox
    
    from PyQt5.QtXml import *
    from PyQt5.QtCore import *
    from PyQt5.QtGui import *
    
    from eotimeseriesviewer.utils import *
    from eotimeseriesviewer import Option, OptionListModel
    
    from eotimeseriesviewer.timeseries import SensorInstrument, TimeSeriesDatum, TimeSeries, SensorProxyLayer
    
    from eotimeseriesviewer.utils import loadUI
    from eotimeseriesviewer.mapviewscrollarea import MapViewScrollArea
    from eotimeseriesviewer.mapcanvas import MapCanvas
    
    from eotimeseriesviewer.externals.qps.crosshair.crosshair import getCrosshairStyle, CrosshairStyle
    
    from eotimeseriesviewer.externals.qps.layerproperties import showLayerPropertiesDialog
    
    
    #assert os.path.isfile(dummyPath)
    #lyr = QgsRasterLayer(dummyPath)
    #assert lyr.isValid()
    DUMMY_RASTERINTERFACE = QgsSingleBandGrayRenderer(None, 0)
    
    
    KEY_LOCKED_LAYER = 'eotsv/locked'
    KEY_SENSOR_GROUP = 'eotsv/sensorgroup'
    KEY_SENSOR_LAYER = 'eotsv/sensorlayer'
    
    class MapViewLayerTreeViewMenuProvider(QgsLayerTreeViewMenuProvider):
    
    
        def __init__(self, mapView, view: QgsLayerTreeView, canvas: QgsMapCanvas):
    
            super(MapViewLayerTreeViewMenuProvider, self).__init__()
            assert isinstance(view, QgsLayerTreeView)
            assert isinstance(canvas, QgsMapCanvas)
            self.mLayerTreeView = view
            self.mDummyCanvas = canvas
            self.mDefActions = QgsLayerTreeViewDefaultActions(self.mLayerTreeView)
    
    
            self.actionAddGroup = self.mDefActions.actionAddGroup()
            self.actionRename = self.mDefActions.actionRenameGroupOrLayer()
            self.actionRemove = self.mDefActions.actionRemoveGroupOrLayer()
    
            #self.actionZoomToLayer = self.mDefActions.actionZoomToGroup(self.mDummyCanvas)
    
            self.actionCheckAndAllChildren = self.mDefActions.actionCheckAndAllChildren()
            self.actionShowFeatureCount = self.mDefActions.actionShowFeatureCount()
    
            #self.actionZoomToLayer = self.mDefActions.actionZoomToLayer(self.mDummyCanvas)
            #self.actionZoomToSelected = self.mDefActions.actionZoomToSelection(self.mDummyCanvas)
            #self.actionZoomToGroup = self.mDefActions.actionZoomToGroup(self.mDummyCanvas)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            self.actionAddEOTSVSpectralProfiles = QAction('Add Spectral Profile Layer')
    
            self.actionAddEOTSVTemporalProfiles = QAction('Add Temporal Profile Layer')
    
        def mapView(self):
            return self.mMapView
    
    
        def layerTreeView(self)->QgsLayerTreeView:
            return self.mLayerTreeView
    
        def layerTree(self)->QgsLayerTree:
            return self.layerTreeModel().rootGroup()
    
        def layerTreeModel(self)->QgsLayerTreeModel:
            return self.layerTreeView().model()
    
        def createContextMenu(self)->QMenu:
    
            model = self.layerTreeModel()
            ltree = self.layerTree()
            view = self.layerTreeView()
            g = view.currentGroupNode()
            l = view.currentLayer()
            i = view.currentIndex()
            #fixedNodes = len([l for l in view.selectedLayersRecursive() if l.property(KEY_LOCKED_LAYER) == True]) > 0 or \
            #             isinstance(g, QgsLayerTreeGroup) and g.property(KEY_LOCKED_LAYER) == True
    
            # disable actions
            #self.actionRemove.setEnabled(fixedNodes == False)
    
            menu = QMenu(view)
            isSensorGroup = isinstance(g, QgsLayerTreeGroup) and g.customProperty(KEY_SENSOR_GROUP) in [True, 'true']
            isSensorLayer = isinstance(l, QgsRasterLayer) and l.customProperty(KEY_SENSOR_LAYER) in [True, 'true']
            self.actionRemove.setEnabled(not (isSensorGroup or isSensorLayer))
            self.actionAddGroup.setEnabled(not (isSensorGroup or isSensorLayer))
            menu.addAction(self.actionAddGroup)
            menu.addAction(self.actionRename)
            menu.addAction(self.actionRemove)
    
    
            #menu.addAction(self.actionZoomToGroup)
            #menu.addAction(self.actionZoomToLayer)
            #menu.addAction(self.actionZoomToSelected)
    
            menu.addSeparator()
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            menu.addAction(self.actionAddEOTSVSpectralProfiles)
            menu.addAction(self.actionAddEOTSVTemporalProfiles)
    
    
            centerCanvas = None
            if isinstance(self.mapView(), MapView):
                visibleCanvases = self.mapView().visibleMapCanvases()
                if len(visibleCanvases) > 0:
                    i = int(len(visibleCanvases) / 2)
                    centerCanvas = visibleCanvases[i]
    
    
    
            a = menu.addAction('Set Properties')
    
            a.triggered.connect(lambda *args, canvas=centerCanvas, lyr=l:
                                showLayerPropertiesDialog(lyr, canvas))
            a.setEnabled(isinstance(centerCanvas, QgsMapCanvas))
    
    
    
    
            #a = menu.addAction('Settings')
            #from qps.layerproperties import showLayerPropertiesDialog
            #a.triggered.connect(lambda *args, lyr=l:showLayerPropertiesDialog(lyr, self._canvas))
    
            return menu
    
    
    class MapViewLayerTreeModel(QgsLayerTreeModel):
        """
        Layer Tree as shown in a MapView
        """
        def __init__(self, rootNode, parent=None):
            super(MapViewLayerTreeModel, self).__init__(rootNode, parent=parent)
    
        def dataXXX(self, index:QModelIndex, role=Qt.DisplayRole):
            node = self.index2node(index)
            # if node.name() == 'testlayer':
            #     s = ""
    
            if True:
                if isinstance(node, QgsLayerTreeGroup) and node.customProperty(KEY_SENSOR_GROUP) in ['true', True]:
                    if role == Qt.FontRole:
                        f = super(MapViewLayerTreeModel, self).data(index, role=role)
                        f.setBold(True)
                        return f
                if isinstance(node, QgsLayerTreeLayer) and node.customProperty(KEY_SENSOR_LAYER) in ['true', True]:
    
                    if role == Qt.FontRole:
                        f = super(MapViewLayerTreeModel, self).data(index, role=role)
                        assert isinstance(f, QFont)
                        f.setItalic(True)
                        return f
    
                    if role == Qt.DecorationRole:
                        return QIcon(':/timeseriesviewer/icons/icon.svg')
    
            return super(MapViewLayerTreeModel, self).data(index, role=role)
    
        def flagsXXX(self, index:QModelIndex):
    
            f = super(MapViewLayerTreeModel, self).flags(index)
    
            node = self.index2node(index)
            if isinstance(node, QgsLayerTreeNode) and ( \
                    node.customProperty(KEY_SENSOR_LAYER) in ['true', True] or \
                    node.customProperty(KEY_SENSOR_GROUP) in ['true', True]):
                f = f ^ Qt.ItemIsDragEnabled
                f = f ^ Qt.ItemIsDropEnabled
    
            return f
    
    class MapView(QFrame, loadUIFormClass(jp(DIR_UI, 'mapview.ui'))):
        """
        A MapView defines how a single map canvas visualizes sensor specific EOTS data plus additional vector overlays
        """
    
        sigRemoveMapView = pyqtSignal(object)
        sigMapViewVisibility = pyqtSignal(bool)
    
    
        sigTitleChanged = pyqtSignal(str)
    
        sigSensorRendererChanged = pyqtSignal(SensorInstrument, QgsRasterRenderer)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
        sigShowProfiles = pyqtSignal(SpatialPoint, MapCanvas, str)
    
        def __init__(self, name='Map View', parent=None):
            super(MapView, self).__init__(parent)
            self.setupUi(self)
    
            m = QMenu(self.btnToggleCrosshair)
            m.addAction(self.actionSetCrosshairStyle)
    
            self.btnToggleCrosshair.setMenu(m)
            self.btnToggleCrosshair.setDefaultAction(self.actionToggleCrosshairVisibility)
            self.btnToggleMapViewVisibility.setDefaultAction(self.actionToggleMapViewHidden)
            self.tbName.textChanged.connect(self.onTitleChanged)
            self.actionSetCrosshairStyle.triggered.connect(self.onChangeCrosshairStyle)
    
            self.actionToggleMapViewHidden.toggled.connect(lambda isHidden: self.setVisibility(not isHidden))
    
            self.actionToggleCrosshairVisibility.toggled.connect(self.setCrosshairVisibility)
    
    
            self.actionAddOgrLayer.triggered.connect(self.onAddOgrLayer)
            self.btnAddOgrLayer.setDefaultAction(self.actionAddOgrLayer)
    
    
            self.btnHighlightMapView.setDefaultAction(self.actionHighlightMapView)
            self.actionHighlightMapView.triggered.connect(lambda: self.setHighlighted(True, timeout=500))
    
            self.mCurrentLayer = None
    
    
            self.mSensorLayerList = list()
            self.mMapCanvases = list()
            assert isinstance(self.mLayerTreeView, QgsLayerTreeView)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
            self.mDummyCanvas = QgsMapCanvas() # dummy map canvas for dummy layers
    
            self.mDummyCanvas.setVisible(False)
    
            self.mLayerTree = QgsLayerTree()
            self.mLayerTreeMapCanvasBridget = QgsLayerTreeMapCanvasBridge(self.mLayerTree, self.mDummyCanvas)
    
            # self.mLayerTreeModel = QgsLayerTreeModel(self.mLayerTree)
            self.mLayerTreeModel = MapViewLayerTreeModel(self.mLayerTree)
    
            self.mLayerTreeModel.setFlags(QgsLayerTreeModel.AllowNodeChangeVisibility |
                                          QgsLayerTreeModel.AllowNodeRename |
                                          QgsLayerTreeModel.AllowNodeReorder)
    
            self._createSensorNode()
    
            self.mLayerTreeView.setModel(self.mLayerTreeModel)
    
            self.mMapLayerTreeViewMenuProvider = MapViewLayerTreeViewMenuProvider(self, self.mLayerTreeView, self.mDummyCanvas)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
            # register some actions that interact with other GUI elements
            self.mMapLayerTreeViewMenuProvider.actionAddEOTSVSpectralProfiles.triggered.connect(self.addSpectralProfileLayer)
            self.mMapLayerTreeViewMenuProvider.actionAddEOTSVTemporalProfiles.triggered.connect(self.addTemporalProfileLayer)
    
    
            self.mLayerTreeView.setMenuProvider(self.mMapLayerTreeViewMenuProvider)
    
            self.mLayerTreeView.currentLayerChanged.connect(self.setCurrentLayer)
    
            self.mLayerTree.removedChildren.connect(self.onChildNodesRemoved)
    
            self.mIsVisible = True
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            self.setTitle(name)
    
        def visibleMapCanvases(self)->list:
            """
            Returns the currently visible mapcanvases
            :return: [list-of-MapCanvases]
            """
            return [m for m in self.mapCanvases() if m.isVisibleToViewport()]
    
    
        def onAddOgrLayer(self):
    
            from .externals.qps.utils import SelectMapLayersDialog
    
            d = SelectMapLayersDialog()
            d.setWindowTitle('Select Vector Layer')
            d.addLayerDescription('Vector Layer', QgsMapLayerProxyModel.VectorLayer)
    
            if d.exec() == QDialog.Accepted:
                for l in d.mapLayers():
                    self.addLayer(l)
            else:
                s = ""
    
        def setCurrentLayer(self, layer):
            assert layer is None or isinstance(layer, QgsMapLayer)
    
            self.mCurrentLayer = layer
    
            if layer not in self.mSensorLayerList:
                for c in self.mapCanvases():
                    c.setCurrentLayer(layer)
            else:
                s = ""
    
    
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def addSpectralProfileLayer(self):
            """Adds the EOTSV Spectral Profile Layer"""
            from eotimeseriesviewer.main import TimeSeriesViewer
            tsv = TimeSeriesViewer.instance()
            if isinstance(tsv, TimeSeriesViewer):
                lyr = tsv.spectralLibrary()
                if lyr not in self.layers():
                    self.addLayer(lyr)
    
        def addTemporalProfileLayer(self):
            """Adds the EOTSV Temporal Profile Layer"""
            from eotimeseriesviewer.main import TimeSeriesViewer
            tsv = TimeSeriesViewer.instance()
            if isinstance(tsv, TimeSeriesViewer):
                lyr = tsv.temporalProfileLayer()
                if lyr not in self.layers():
                    self.addLayer(lyr)
    
    
    
        def addLayer(self, layer:QgsMapLayer):
            """
            Add a QgsMapLayer to the MapView layer tree
            :param layer: QgsMapLayer
            """
            if isinstance(layer, QgsVectorLayer):
                self.mLayerTree.insertLayer(0, layer)
            else:
                self.mLayerTree.addLayer(layer)
    
        def _createSensorNode(self):
            self.mLayerTreeSensorNode = QgsLayerTreeGroup(name='Raster Time Series', checked=True)
            self.mLayerTreeSensorNode.setCustomProperty(KEY_LOCKED_LAYER, True)
            self.mLayerTreeSensorNode.setCustomProperty(KEY_SENSOR_GROUP, True)
            self.mLayerTree.addChildNode(self.mLayerTreeSensorNode)
    
        def _containsSensorNode(self, root:QgsLayerTreeGroup)->bool:
            assert isinstance(root, QgsLayerTreeGroup)
            if root.customProperty(KEY_SENSOR_GROUP) in [True, 'true']:
                return True
            for grp in root.findGroups():
                if self._containsSensorNode(grp):
                    return True
            return False
    
        def onChildNodesRemoved(self, node, idxFrom, idxTo):
            if not self._containsSensorNode(self.mLayerTreeModel.rootGroup()):
                self._createSensorNode()
    
        def onChangeCrosshairStyle(self):
            style = getCrosshairStyle(parent=self, crosshairStyle=self.crosshairStyle())
            if isinstance(style, CrosshairStyle):
                self.setCrosshairStyle(style)
    
    
        def setVisibility(self, b: bool):
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            assert isinstance(b, bool)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
    benjamin.jakimow@geo.hu-berlin.de's avatar
    benjamin.jakimow@geo.hu-berlin.de committed
            for mapCanvas in self.mapCanvases():
                assert isinstance(mapCanvas, MapCanvas)
    
    benjamin.jakimow@geo.hu-berlin.de's avatar
    benjamin.jakimow@geo.hu-berlin.de committed
                if not mapCanvas.isVisible() == b:
    
    benjamin.jakimow@geo.hu-berlin.de's avatar
    benjamin.jakimow@geo.hu-berlin.de committed
                    mapCanvas.setVisible(b)
    
    
            if self.actionToggleMapViewHidden.isChecked() == b:
                self.actionToggleMapViewHidden.setChecked(not b)
    
    benjamin.jakimow@geo.hu-berlin.de's avatar
    benjamin.jakimow@geo.hu-berlin.de committed
            if changed:
                self.sigMapViewVisibility.emit(b)
    
    
        def isVisible(self)->bool:
            """
            Returns the map view visibility
            :return: bool
            """
    
            return not self.actionToggleMapViewHidden.isChecked()
    
        def mapCanvases(self)->list:
            """
            Returns the MapCanvases related to this map view
            :return: [list-of-MapCanvases]
            """
    
            return self.mMapCanvases[:]
    
        def onTitleChanged(self, *args):
            self.setWindowTitle('Map View "{}"'.format(self.title()))
    
            self.sigTitleChanged.emit(self.title())
    
        def setTimeSeries(self, timeSeries:TimeSeries):
            """
            Conntects the MapView with a TimeSeries.
            :param timeSeries: TimeSeries
            """
            assert isinstance(timeSeries, TimeSeries)
    
            for s in self.sensors():
                self.removeSensor(s)
    
            self.mTimeSeries = timeSeries
            self.mTimeSeries.sigSensorAdded.connect(self.addSensor)
            self.mTimeSeries.sigSensorRemoved.connect(self.removeSensor)
            for s in timeSeries.sensors():
                self.addSensor(s)
    
    
        def setTitle(self, title:str):
            """
            Sets the widget title
            :param title: str
            """
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            old = self.title()
            if old != title:
    
                self.tbName.setText(title)
    
        def layers(self)->list:
            """
            Returns the visible layers, including proxy layer for time-series data
            :return: [list-of-QgsMapLayers]
            """
    
            return [l for l in self.mLayerTree.checkedLayers() if isinstance(l, QgsMapLayer)]
    
            """
            Returns the MapView title
            :return: str
            """
            return self.tbName.text()
    
            for mapCanvas in self.mapCanvases():
                if isinstance(mapCanvas, MapCanvas):
                    mapCanvas.refresh()
    
        def setCrosshairStyle(self, crosshairStyle:CrosshairStyle):
            """
    
            :param crosshairStyle: CrosshairStyle
            """
    
            from eotimeseriesviewer import CrosshairStyle
            assert isinstance(crosshairStyle, CrosshairStyle)
            srcCanvas = self.sender()
            if isinstance(srcCanvas, MapCanvas):
                dstCanvases = [c for c in self.mapCanvases() if c != srcCanvas]
            else:
                dstCanvases = [c for c in self.mapCanvases()]
    
            for mapCanvas in dstCanvases:
                mapCanvas.setCrosshairStyle(crosshairStyle, emitSignal=False)
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
        def setHighlighted(self, b=True, timeout=1000):
    
            """
            Activates or deactivates a red-line border of the MapCanvases
            :param b: True | False to activate / deactivate the highlighted lines-
            :param timeout: int, milliseconds how long the highlighted frame should appear
            """
    
            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)
    
    
    
        def registerMapCanvas(self, mapCanvas:MapCanvas):
    
            """
            Registers a new MapCanvas to this MapView
            :param sensor:
            :param mapCanvas:
            :return:
            """
    
            from eotimeseriesviewer.mapcanvas import MapCanvas
    
            assert isinstance(mapCanvas, MapCanvas)
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            mapCanvas.setMapView(self)
    
            mapCanvas.sigCrosshairVisibilityChanged.connect(self.setCrosshairVisibility)
            mapCanvas.sigCrosshairStyleChanged.connect(self.setCrosshairStyle)
    
            self.mMapCanvases.append(mapCanvas)
    
            self.sigMapViewVisibility.connect(mapCanvas.setEnabled)
    
        def crosshairStyle(self)->CrosshairStyle:
            """
            Returns the CrosshairStyle
            :return:
            """
            for c in self.mapCanvases():
                assert isinstance(c, MapCanvas)
                style = c.crosshairStyle()
                if isinstance(style, CrosshairStyle):
                    return style
            return None
    
        def setCrosshairVisibility(self, b:bool):
    
            Enables / diables the map canvas crosshair.
            :param b: bool
    
            """
            srcCanvas = self.sender()
            if isinstance(srcCanvas, MapCanvas):
                dstCanvases = [c for c in self.mapCanvases() if c != srcCanvas]
            else:
                dstCanvases = [c for c in self.mapCanvases()]
    
    
            for mapCanvas in dstCanvases:
                mapCanvas.setCrosshairVisibility(b, emitSignal=False)
    
        def sensorProxyLayers(self)->list:
            layers = [n.layer() for n in self.mLayerTreeSensorNode.findLayers()]
            return [l for l in layers if isinstance(l, SensorProxyLayer)]
    
        def sensorProxyLayer(self, sensor:SensorInstrument)->SensorProxyLayer:
    
            Returns the proxy layer related to a SensorInstrument
            :param sensor: SensorInstrument
            :return: SensorLayer
            """
            for l in self.sensorProxyLayers():
                if l.sensor() == sensor:
                    return l
            return None
    
        def sensors(self)->list:
            """
            Returns a list of SensorsInstruments
            :return: [list-of-SensorInstruments]
            """
    
            return [t[0] for t in self.mSensorLayerList]
    
        def addSensor(self, sensor:SensorInstrument):
            """
            Adds a SensorInstrument to be shown in this MapView. Each sensor will be represented as a Raster Layer in the
            Tree Model.
            :param sensor: SensorInstrument
            """
            assert isinstance(sensor, SensorInstrument)
            if sensor not in self.sensors():
                dummyLayer = sensor.proxyLayer()
    
                dummyLayer.rendererChanged.connect(lambda sensor=sensor: self.onSensorRendererChanged(sensor))
    
                layerTreeLayer = self.mLayerTreeSensorNode.addLayer(dummyLayer)
                assert isinstance(layerTreeLayer, QgsLayerTreeLayer)
                layerTreeLayer.setCustomProperty(KEY_LOCKED_LAYER, True)
                layerTreeLayer.setCustomProperty(KEY_SENSOR_LAYER, True)
                self.mSensorLayerList.append((sensor, dummyLayer))
    
    
        def onSensorRendererChanged(self, sensor:SensorInstrument):
            for c in self.sensorCanvases(sensor):
                assert isinstance(c, MapCanvas)
                c.addToRefreshPipeLine(MapCanvas.Command.RefreshRenderer)
    
        def sensorCanvases(self, sensor:SensorInstrument)->list:
            """
            Returns the MapCanvases that show a layer with data for the given ``sensor``
            :param sensor: SensorInstrument
            :return:
            """
            assert isinstance(sensor, SensorInstrument)
            return [c for c in self.mapCanvases() if isinstance(c, MapCanvas) and c.tsd().sensor() == sensor]
    
    
    
        def sensorLayer(self, sensor: SensorInstrument):
            """
            Returns the QgsRasterLayer that is used a proxy to specify the QgsRasterRenderer for a sensor
            :param sensor: SensorInstrument
            :return: QgsRasterLayer
            """
            assert isinstance(sensor, SensorInstrument)
            for t in self.mSensorLayerList:
                s, l = t
                assert isinstance(s, SensorInstrument)
                assert isinstance(l, QgsRasterLayer)
                if s == sensor:
                    return l
            raise Exception('Sensor "{}" not registered to MapView "{}"'.format(sensor.name(), self.title()))
    
        def removeSensor(self, sensor:SensorInstrument):
            """
            Removes a sensor from this map view
    
            pair = None
            for i, t in enumerate(self.mSensorLayerList):
                if t[0] == sensor:
                    pair = t
                    break
            assert pair is not None, 'Sensor "{}" not found'.format(sensor.name())
            self.mLayerTreeSensorNode.removeLayer(pair[1])
            self.mSensorLayerList.remove(pair)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
        def hasSensor(self, sensor)->bool:
            """
            :param sensor:
            :return:
            """
            assert isinstance(sensor, SensorInstrument)
            return sensor in self.sensors()
    
    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()
    
                    if w.isVisible():
                        total.setWidth(max([total.width(), ws.width()]))
                        total.setHeight(total.height() +  ws.height())
    
    benjamin.jakimow@geo.hu-berlin.de's avatar
    benjamin.jakimow@geo.hu-berlin.de committed
                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())
    
    benjamin.jakimow@geo.hu-berlin.de's avatar
    benjamin.jakimow@geo.hu-berlin.de committed
            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)
    
    
        def __init__(self, timeSeriesDatum: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
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            self.mSensor = self.TSD.mSensor
    
            self.mSensor.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.mMapCanvases = dict()
    
            self.mRenderState = dict()
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            labelTxt = '{}\n{}'.format(str(self.TSD.mDate), self.TSD.mSensor.name())
            tooltip = '\n'.join([tss.uri()for tss in self.TSD.sources()])
    
    
            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)
    
    
            canvas = self.mMapCanvases.pop(mapView)
    
            self.ui.layout().removeWidget(canvas)
    
        def mapCanvases(self)->list:
            """
            Retuns the MapCanvases of this DataView
            :return: [list-of-MapCanvases]
            """
            return self.mMapCanvases.values()
    
        def refresh(self):
            """
            Refreshes the MapCanvases in this DatumView, if they are not hidden behind a scroll area.
            """
    
    
        def insertMapView(self, mapView):
            assert isinstance(mapView, MapView)
    
            from eotimeseriesviewer.mapcanvas import MapCanvas
    
            mapCanvas = MapCanvas(self.ui)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            mapCanvas.setObjectName('MapCanvas {} {}'.format(mapView.title(), self.TSD.mDate))
    
            self.registerMapCanvas(mapView, mapCanvas)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            mapCanvas.setMapView(mapView)
            mapCanvas.setTSD(self.TSD)
    
            mapView.registerMapCanvas(mapCanvas)
    
            self.STV.registerMapCanvas(mapCanvas)
    
            mapCanvas.renderComplete.connect(lambda : self.onRenderingChange(False))
            mapCanvas.renderStarting.connect(lambda : self.onRenderingChange(True))
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            #mapCanvas.sigMapRefreshed[float, float].connect(
            #    lambda dt: self.STV.TSV.ui.dockSystemInfo.addTimeDelta('Map {}'.format(self.mSensor.name()), dt))
            #mapCanvas.sigMapRefreshed.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.mMapCanvases.values()]
            drawFlags = [m.isDrawing() for m in self.mMapCanvases.values()]
    
            isLoading = any(renderFlags)
    
            self.showLoading(isLoading)
    
            s = ""
    
        def registerMapCanvas(self, mapView, mapCanvas):
    
            from eotimeseriesviewer.mapcanvas import MapCanvas
    
            assert isinstance(mapCanvas, MapCanvas)
    
            assert isinstance(mapView, MapView)
    
            self.mMapCanvases[mapView] = mapCanvas
    
            #mapView.sigTitleChanged.connect(lambda title : mapCanvas.setSaveFileName('{}_{}'.format(self.TSD.date, title)))
    
            #mapCanvas.mapLayerModel().addMapLayerSources(self.TSD.qgsMimeDataUtilsUris())
    
            #self.ui.layout().insertWidget(self.wOffset + len(self.mapCanvases), mapCanvas)
            self.ui.layout().insertWidget(self.ui.layout().count() - 1, 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(lambda c, t : mapView.sigShowProfiles.emit(c,mapCanvas, t))
            #mapCanvas.sigChangeDVRequest.connect(self.onMapCanvasRequest)
    
        def onMapCanvasRequest(self, mapCanvas, key):
    
            if key == 'hide_date':
                self.TSD.setVisibility(False)
    
            if key == 'copy_sensor':
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                QApplication.clipboard().setText(self.TSD.mSensor.name())
    
            if key == 'copy_date':
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                QApplication.clipboard().setText(str(self.TSD.date()))
    
            if key == 'copy_path':
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                QApplication.clipboard().setText('\n'.join(self.TSD.sourceUris()))
    
        def __lt__(self, other):
            assert isinstance(other, DatumView)
            return self.TSD < other.TSD
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            """
            :param other:
            :return:
            """
    
            assert isinstance(other, DatumView)
            return self.TSD == other.TSD
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    class DateViewCollection(QObject):
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        sigResizeRequired = pyqtSignal()
        sigLoadingStarted = pyqtSignal(MapView, TimeSeriesDatum)
        sigLoadingFinished = pyqtSignal(MapView, TimeSeriesDatum)
        sigShowProfiles = pyqtSignal(SpatialPoint)
        sigSpatialExtentChanged = pyqtSignal(SpatialExtent)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def __init__(self, STViz):
            assert isinstance(STViz, SpatialTemporalVisualization)
            super(DateViewCollection, self).__init__()
            #self.tsv = tsv
            #self.timeSeries = tsv.TS
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            self.mViews = list()
            self.STV = STViz
            self.ui = self.STV.targetLayout.parentWidget()
            self.scrollArea = self.ui.parentWidget().parentWidget()
            #potentially there are many more dates than views.
            #therefore we implement the addinng/removing of mapviews here
            #we reduce the number of layout refresh calls by
            #suspending signals, adding the new map view canvases, and sending sigResizeRequired
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            self.STV.MVC.sigMapViewAdded.connect(self.addMapView)
            self.STV.MVC.sigMapViewRemoved.connect(self.removeMapView)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            self.setFocusView(None)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def tsdFromMapCanvas(self, mapCanvas):
            assert isinstance(mapCanvas, MapCanvas)
            for view in self.mViews:
                assert isinstance(view, DatumView)
                if mapCanvas in view.mMapCanvases.values():
                    return view.TSD
            return None
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def tsdView(self, tsd):
            r = [v for v in self.mViews if v.TSD == tsd]
            if len(r) == 1:
                return r[0]
            else:
                raise Exception('TSD not in list')
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def addMapView(self, mapView):
            assert isinstance(mapView, MapView)
            w = self.ui
            #w.setUpdatesEnabled(False)
            #for tsdv in self.mViews:
            #    tsdv.ui.setUpdatesEnabled(False)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            for tsdv in self.mViews:
                tsdv.insertMapView(mapView)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            #for tsdv in self.mViews:
            #    tsdv.ui.setUpdatesEnabled(True)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            #mapView.sigSensorRendererChanged.connect(lambda *args : self.setRasterRenderer(mapView, *args))
            mapView.sigMapViewVisibility.connect(lambda: self.sigResizeRequired.emit())
            mapView.sigShowProfiles.connect(self.sigShowProfiles.emit)
            #w.setUpdatesEnabled(True)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            self.sigResizeRequired.emit()
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def removeMapView(self, mapView):
            assert isinstance(mapView, MapView)
            for tsdv in self.mViews:
                tsdv.removeMapView(mapView)
            self.sigResizeRequired.emit()
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def highlightDate(self, tsd):
            """
            Highlights a time series data for a specific time our
            :param tsd:
            :return:
            """
            tsdView = self.tsdView(tsd)
            if isinstance(tsdView, DatumView):
                tsdView.setHighlight(True)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def setFocusView(self, tsd):
            self.focusView = tsd
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def orderedViews(self):
            #returns the
            if self.focusView is not None:
                assert isinstance(self.focusView, DatumView)
                return sorted(self.mViews, key=lambda v: np.abs(v.TSD.date - self.focusView.TSD.date))
            else:
                return self.mViews
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        """
        def setSubsetSize(self, size):
            assert isinstance(size, QSize)
            self.subsetSize = size
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            for tsdView in self.orderedViews():
                tsdView.blockSignals(True)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            for tsdView in self.orderedViews():
                tsdView.setSubsetSize(size)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            for tsdView in self.orderedViews():
                tsdView.blockSignals(False)
        """
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def addDates(self, tsdList):
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            Create a new TSDView
            :param tsdList:
            :return:
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            for tsd in tsdList:
                assert isinstance(tsd, TimeSeriesDatum)
                DV = DatumView(tsd, self.STV, parent=self.ui)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                DV.sigLoadingStarted.connect(self.sigLoadingStarted.emit)
                DV.sigLoadingFinished.connect(self.sigLoadingFinished.emit)
                DV.sigVisibilityChanged.connect(lambda: self.STV.adjustScrollArea())
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                for i, mapView in enumerate(self.STV.MVC):
                    DV.insertMapView(mapView)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                bisect.insort(self.mViews, DV)
                i = self.mViews.index(DV)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                DV.ui.setParent(self.STV.targetLayout.parentWidget())
                self.STV.targetLayout.insertWidget(i, DV.ui)
                DV.ui.show()
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            if len(tsdList) > 0:
                self.sigResizeRequired.emit()
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def removeDates(self, tsdList):
            toRemove = [v for v in self.mViews if v.TSD in tsdList]
            removedDates = []
            for DV in toRemove:
                self.mViews.remove(DV)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                for mapCanvas in DV.mMapCanvases.values():
                    toRemove = mapCanvas.layers()
                    mapCanvas.setLayers([])
                    toRemove = [l for l in toRemove if isinstance(l, QgsRasterLayer)]
                    if len(toRemove) > 0:
                        QgsProject.instance().removeMapLayers([l.id() for l in toRemove])
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                DV.ui.parent().layout().removeWidget(DV.ui)