Skip to content
Snippets Groups Projects
mapvisualization.py 66.8 KiB
Newer Older
# 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, 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 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)
        a = menu.addAction('Set Properties')
        a.triggered.connect(lambda *args, lyr=l, canvas=self.mDummyCanvas: showLayerPropertiesDialog(lyr, canvas))
        a.setEnabled(not isinstance(l, SensorProxyLayer))
        #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))
Benjamin Jakimow's avatar
Benjamin Jakimow committed

        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.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 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)
            DV.ui.hide()
            DV.ui.close()
            removedDates.append(DV.TSD)
            del DV
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        if len(removedDates) > 0:
            self.sigResizeRequired.emit()
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def __len__(self):
        return len(self.mViews)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def __iter__(self):
        return iter(self.mViews)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def __getitem__(self, slice):
        return self.mViews[slice]
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def __delitem__(self, slice):
        self.removeDates(self.mViews[slice])
Benjamin Jakimow's avatar
Benjamin Jakimow committed
class MapViewListModel(QAbstractListModel):
    """
    A model to store a list of map views.
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    """
    sigMapViewsAdded = pyqtSignal(list)
    sigMapViewsRemoved = pyqtSignal(list)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def __init__(self, parent=None):
        super(MapViewListModel, self).__init__(parent)
        self.mMapViewList = []
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def addMapView(self, mapView):
        i = len(self.mMapViewList)
        self.insertMapView(i, mapView)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def insertMapView(self, i, mapView):
        self.insertMapViews(i, [mapView])
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def insertMapViews(self, i, mapViews):
        assert isinstance(mapViews, list)
        assert i >= 0 and i <= len(self.mMapViewList)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.beginInsertRows(QModelIndex(), i, i + len(mapViews) - 1)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        for j in range(len(mapViews)):
            mapView = mapViews[j]
            assert isinstance(mapView, MapView)
            mapView.sigTitleChanged.connect(
                lambda : self.doRefresh([mapView])
            )
            self.mMapViewList.insert(i + j, mapView)
        self.endInsertRows()
        self.sigMapViewsAdded.emit(mapViews)
Benjamin Jakimow's avatar
Benjamin Jakimow committed

Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def doRefresh(self, mapViews):
        for mapView in mapViews:
            idx = self.mapView2idx(mapView)
            self.dataChanged.emit(idx, idx)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def removeMapView(self, mapView):
        self.removeMapViews([mapView])
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def removeMapViews(self, mapViews):
        assert isinstance(mapViews, list)
        for mv in mapViews:
            assert mv in self.mMapViewList
            idx = self.mapView2idx(mv)
            self.beginRemoveRows(idx.parent(), idx.row(), idx.row())
            self.mMapViewList.remove(mv)
            self.endRemoveRows()
        self.sigMapViewsRemoved.emit(mapViews)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def rowCount(self, parent=None, *args, **kwargs):
        return len(self.mMapViewList)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def columnCount(self, QModelIndex_parent=None, *args, **kwargs):
        return 1
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def idx2MapView(self, index):
        if isinstance(index, QModelIndex):
            if index.isValid():
                index = index.row()
            else:
                return None
        assert index >= 0 and index < len(self.mMapViewList)
        return self.mMapViewList[index]
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def mapView2idx(self, mapView):
        assert isinstance(mapView, MapView)
        row = self.mMapViewList.index(mapView)
        return self.createIndex(row, 0, mapView)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def __len__(self):
        return len(self.mMapViewList)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def __iter__(self):
        return iter(self.mMapViewList)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def __getitem__(self, slice):
        return self.mMapViewList[slice]
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def data(self, index, role=Qt.DisplayRole):
        if not index.isValid():
            return None
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        if (index.row() >= len(self.mMapViewList)) or (index.row() < 0):
            return None
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        mapView = self.idx2MapView(index)
        assert isinstance(mapView, MapView)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        value = None
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        if role == Qt.DisplayRole:
            value = '{} {}'.format(index.row() +1 , mapView.title())
        #if role == Qt.DecorationRole:
            #value = classInfo.icon(QSize(20,20))
        if role == Qt.UserRole:
            value = mapView
        return value
Benjamin Jakimow's avatar
Benjamin Jakimow committed
class MapViewDock(QgsDockWidget, loadUI('mapviewdock.ui')):
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    sigMapViewAdded = pyqtSignal(MapView)
    sigMapViewRemoved = pyqtSignal(MapView)
    sigShowProfiles = pyqtSignal(SpatialPoint, MapCanvas, str)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    sigMapCanvasColorChanged = pyqtSignal(QColor)
    sigSpatialExtentChanged = pyqtSignal(SpatialExtent)
    sigCrsChanged = pyqtSignal(QgsCoordinateReferenceSystem)
    sigMapSizeChanged = pyqtSignal(QSize)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def setTimeSeries(self, timeSeries:TimeSeries):
        assert isinstance(timeSeries, TimeSeries)
        self.mTimeSeries = timeSeries
        self.mTimeSeries.sigSensorAdded.connect(self.addSensor)
        self.mTimeSeries.sigSensorRemoved.connect(self.removeSensor)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def __init__(self, parent=None):
        super(MapViewDock, self).__init__(parent)
        self.setupUi(self)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.baseTitle = self.windowTitle()
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.btnAddMapView.setDefaultAction(self.actionAddMapView)
        self.btnRemoveMapView.setDefaultAction(self.actionRemoveMapView)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.btnCrs.crsChanged.connect(self.sigCrsChanged)
        self.btnMapCanvasColor.colorChanged.connect(self.onMapCanvasBackgroundColorChanged)
        self.btnApplySizeChanges.clicked.connect(lambda : self.sigMapSizeChanged.emit(QSize(self.spinBoxMapSizeX.value(),self.spinBoxMapSizeY.value())))
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.actionAddMapView.triggered.connect(self.createMapView)
        self.actionRemoveMapView.triggered.connect(lambda : self.removeMapView(self.currentMapView()) if self.currentMapView() else None)
        self.actionApplyStyles.triggered.connect(self.refreshCurrentMapView)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.toolBox.currentChanged.connect(self.onToolboxIndexChanged)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.spinBoxMapSizeX.valueChanged.connect(lambda: self.onMapSizeChanged('X'))
        self.spinBoxMapSizeY.valueChanged.connect(lambda: self.onMapSizeChanged('Y'))
        self.mLastMapSize = self.mapSize()
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.mTimeSeries = None
Benjamin Jakimow's avatar
Benjamin Jakimow committed

Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def mapViews(self)->list:
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        Returns the defined MapViews
        :return: [list-of-MapViews]
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        assert isinstance(self.toolBox, QToolBox)
        mapViews = []
        for i in range(self.toolBox.count()):
            item = self.toolBox.widget(i)
            if isinstance(item, MapView):
                mapViews.append(item)
        return mapViews
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def mapCanvases(self)->list:
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        Returns all MapCanvases from all MapViews
        :return: [list-of-MapCanvases]
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        maps = []
        for mapView in self.mapViews():
            assert isinstance(mapView, MapView)
            maps.extend(mapView.mapCanvases())
        return maps
    def setCrs(self, crs):
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        if isinstance(crs, QgsCoordinateReferenceSystem):
            old = self.btnCrs.crs()
            if old != crs:
                self.btnCrs.setCrs(crs)
                self.btnCrs.setLayerCrs(crs)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def setMapSize(self, size):
        assert isinstance(size, QSize)
        ws = [self.spinBoxMapSizeX, self.spinBoxMapSizeY]
        oldSize = self.mapSize()
        b = oldSize != size
        for w in ws:
            w.blockSignals(True)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.spinBoxMapSizeX.setValue(size.width()),
        self.spinBoxMapSizeY.setValue(size.height())
        self.mLastMapSize = QSize(size)
        for w in ws:
            w.blockSignals(False)
        self.mLastMapSize = QSize(size)
        if b:
            self.sigMapSizeChanged.emit(size)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def onMapSizeChanged(self, dim):
        newSize = self.mapSize()
        #1. set size of other dimension accordingly
        if dim is not None:
            if self.checkBoxKeepSubsetAspectRatio.isChecked():
                if dim == 'X':
                    vOld = self.mLastMapSize.width()
                    vNew = newSize.width()
                    targetSpinBox = self.spinBoxMapSizeY
                elif dim == 'Y':
                    vOld = self.mLastMapSize.height()
                    vNew = newSize.height()
                    targetSpinBox = self.spinBoxMapSizeX
Benjamin Jakimow's avatar
Benjamin Jakimow committed
                oldState = targetSpinBox.blockSignals(True)
                targetSpinBox.setValue(int(round(float(vNew) / vOld * targetSpinBox.value())))
                targetSpinBox.blockSignals(oldState)
                newSize = self.mapSize()
            if newSize != self.mLastMapSize:
                self.btnApplySizeChanges.setEnabled(True)
        else:
            self.sigMapSizeChanged.emit(self.mapSize())
            self.btnApplySizeChanges.setEnabled(False)
        self.setMapSize(newSize)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def mapSize(self):
        return QSize(self.spinBoxMapSizeX.value(),
                     self.spinBoxMapSizeY.value())
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def refreshCurrentMapView(self, *args):
        mv = self.currentMapView()
        if isinstance(mv, MapView):
            mv.refreshMapView()
        else:
            s  =""
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def dummySlot(self):
        s  =""
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def onMapViewsRemoved(self, mapViews):
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        for mapView in mapViews:
            idx = self.stackedWidget.indexOf(mapView.ui)
            if idx >= 0:
                self.stackedWidget.removeWidget(mapView.ui)
                mapView.ui.close()
            else:
                s = ""
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.actionRemoveMapView.setEnabled(len(self.mMapViews) > 0)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def mapBackgroundColor(self)->QColor:
        """
        Returns the map canvas background color
        :return: QColor
        """
        return self.btnMapCanvasColor.color()
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def setMapBackgroundColor(self, color:QColor):
        """
        Sets the MapCanvas background color
        :param color: QColor
        """
        if color != self.mapBackgroundColor():
            self.btnMapCanvasColor.setColor(color)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def onMapCanvasBackgroundColorChanged(self, color:QColor):
        """
        Reacts on a changes map color
        :param color: QColor
        """
        assert isinstance(color, QColor)
        self.mColor = color
        for mapCanvas in self.mapCanvases():
            assert isinstance(mapCanvas, MapCanvas)
            mapCanvas.addToRefreshPipeLine(color)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.sigMapCanvasColorChanged.emit(color)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def onMapViewsAdded(self, mapViews):
        nextShown = None
        for mapView in mapViews:
            mapView.sigTitleChanged.connect(self.updateTitle)
            self.stackedWidget.addWidget(mapView.ui)
            if nextShown is None:
                nextShown = mapView
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            contents = mapView.ui.scrollAreaWidgetContents
            size = contents.size()
            hint = contents.sizeHint()
            #mapView.ui.scrollArea.update()
            s = ""
            #setMinimumSize(mapView.ui.scrollAreaWidgetContents.sizeHint())
            #hint = contents.sizeHint()
            #contents.setMinimumSize(hint)
        if isinstance(nextShown, MapView):
            self.setCurrentMapView(nextShown)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        for mapView in mapViews:
            self.sigMapViewAdded.emit(mapView)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def updateButtons(self, *args):
        b = len(self.mMapViews) > 0
        self.actionRemoveMapView.setEnabled(b)
        self.actionApplyStyles.setEnabled(b)
        self.actionHighlightMapView.setEnabled(b)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def createMapView(self)->MapView:
        """
        Create a new MapView
        :return: MapView
        """
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        mapView = MapView()
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        n = len(self.mapViews()) + 1
        title = 'Map View {}'.format(n)
        while title in [m.title() for m in self.mapViews()]:
            n += 1
            title = 'Map View {}'.format(n)
        mapView.setTitle(title)
        mapView.sigShowProfiles.connect(self.sigShowProfiles)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        mapView.setTimeSeries(self.mTimeSeries)
        self.addMapView(mapView)
        return mapView
Benjamin Jakimow's avatar
Benjamin Jakimow committed

    def addMapView(self, mapView:MapView):
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        Adds a MapView
        :param mapView: MapView
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        assert isinstance(mapView, MapView)
        mapView.sigTitleChanged.connect(lambda *args, mv=mapView : self.onMapViewUpdated(mv))
        mapView.sigMapViewVisibility.connect(lambda *args, mv=mapView : self.onMapViewUpdated(mv))
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        i = self.toolBox.addItem(mapView, mapView.windowIcon(), mapView.title())
        self.toolBox.setCurrentIndex(i)
        self.onMapViewUpdated(mapView)
        self.sigMapViewAdded.emit(mapView)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def onToolboxIndexChanged(self):
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        b = isinstance(self.toolBox.currentWidget(), MapView)
        self.actionRemoveMapView.setEnabled(b)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def onMapViewUpdated(self, mapView:MapView):
        """
        Handles updates that react on MapView changes
        :param mapView: MapView to make the update for
        """
        numMV = 0
        for i in range(self.toolBox.count()):
            item = self.toolBox.widget(i)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            if isinstance(item, MapView):
Benjamin Jakimow's avatar
Benjamin Jakimow committed
                numMV += 1
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            if item == mapView:

Benjamin Jakimow's avatar
Benjamin Jakimow committed
                if mapView.isVisible():
                    icon = QIcon(":/timeseriesviewer/icons/mapview.svg")
                else:
                    icon = QIcon(":/timeseriesviewer/icons/mapviewHidden.svg")
Benjamin Jakimow's avatar
Benjamin Jakimow committed
                self.toolBox.setItemIcon(i, icon)
                self.toolBox.setItemText(i, 'Map View {} "{}"'.format(numMV, mapView.title()))
                break
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def removeMapView(self, mapView:MapView)->MapView:
        """
        Removes a MapView
        :param mapView: MapView
        :return: MapView
        """
        assert mapView in self.mapViews()
        for i in range(self.toolBox.count()):
            w = self.toolBox.widget(i)
            if isinstance(w, MapView) and w == mapView:
                self.toolBox.removeItem(i)
                mapView.close()
                if self.toolBox.count() >= i:
                    self.toolBox.setCurrentIndex(min(i, self.toolBox.count()-1))
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.sigMapViewRemoved.emit(mapView)
        return mapView
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def __len__(self)->int:
        """
        Returns the number of MapViews
        :return: int
        """
        return len(self.mapViews())
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        """
        Provides an iterator over all MapViews
        :return:
        """
        return iter(self.mapViews())
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        return self.mapViews()[slice]
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def __contains__(self, mapView):
        return mapView in self.mapViews()
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def index(self, mapView):
        assert isinstance(mapView, MapView)
        return self.mapViews().index(mapView)
Benjamin Jakimow's avatar
Benjamin Jakimow committed

Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def addSensor(self, sensor:SensorInstrument):
        """
        Adds a new SensorInstrument
        :param sensor: SensorInstrument
        """
        for mapView in self.mapViews():
            mapView.addSensor(sensor)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def removeSensor(self, sensor:SensorInstrument):
        """
        Removes a Sensor
        :param sensor: SensorInstrument
        """
        for mapView in self.mMapViews:
            mapView.removeSensor(sensor)
Benjamin Jakimow's avatar
Benjamin Jakimow committed

Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def applyStyles(self):
        for mapView in self.mMapViews:
            mapView.applyStyles()
Benjamin Jakimow's avatar
Benjamin Jakimow committed

Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def setCrosshairStyle(self, crosshairStyle):
        for mapView in self.mMapViews:
            mapView.setCrosshairStyle(crosshairStyle)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def setShowCrosshair(self, b):
        for mapView in self.mMapViews:
            mapView.setCrosshairVisibility(b)
Benjamin Jakimow's avatar
Benjamin Jakimow committed

Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def index(self, mapView):
        assert isinstance(mapView, MapView)
        return self.mapViewsDefinitions.index(mapView)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def setCurrentMapView(self, mapView):
        assert isinstance(mapView, MapView) and mapView in self.mMapViews
        idx = self.stackedWidget.indexOf(mapView.ui)
        if idx >= 0:
            self.stackedWidget.setCurrentIndex(idx)
            self.cbMapView.setCurrentIndex(self.mMapViews.mapView2idx(mapView).row())
Benjamin Jakimow's avatar
Benjamin Jakimow committed

Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.updateTitle()
Benjamin Jakimow's avatar
Benjamin Jakimow committed

Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def updateTitle(self, *args):
        # self.btnToggleMapViewVisibility.setChecked(mapView)
        mapView = self.currentMapView()
        if isinstance(mapView, MapView):
            if mapView in self.mMapViews:
                i = str(self.mMapViews.mapView2idx(mapView).row()+1)
            else:
                i = ''
            #title = '{} | {} "{}"'.format(self.baseTitle, i, mapView.title())
            title = '{} | {}'.format(self.baseTitle, i)
            self.setWindowTitle(title)
Benjamin Jakimow's avatar
Benjamin Jakimow committed

Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def currentMapView(self):
        w = self.toolBox.currentWidget()
        if isinstance(w, MapView):
            return w
        return None
Benjamin Jakimow's avatar
Benjamin Jakimow committed
class SpatialTemporalVisualization(QObject):
    """
Benjamin Jakimow's avatar
Benjamin Jakimow committed

Benjamin Jakimow's avatar
Benjamin Jakimow committed
    """
    sigLoadingStarted = pyqtSignal(DatumView, MapView)
    sigLoadingFinished = pyqtSignal(DatumView, MapView)
    sigShowProfiles = pyqtSignal(SpatialPoint, MapCanvas, str)
    sigShowMapLayerInfo = pyqtSignal(dict)
    sigSpatialExtentChanged = pyqtSignal(SpatialExtent)
    sigMapSizeChanged = pyqtSignal(QSize)
    sigCRSChanged = pyqtSignal(QgsCoordinateReferenceSystem)
    sigActivateMapTool = pyqtSignal(str)
    sigMapViewAdded = pyqtSignal(MapView)
    sigMapViewRemoved = pyqtSignal(MapView)
Benjamin Jakimow's avatar
Benjamin Jakimow committed

    sigVisibleDatesChanged = pyqtSignal(list)

Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def __init__(self, timeSeriesViewer):
        super(SpatialTemporalVisualization, self).__init__()
        # assert isinstance(timeSeriesViewer, TimeSeriesViewer), timeSeriesViewer
Benjamin Jakimow's avatar
Benjamin Jakimow committed

Benjamin Jakimow's avatar
Benjamin Jakimow committed
        # default map settings
        self.mSpatialExtent = SpatialExtent.world()
        self.mCRS = self.mSpatialExtent.crs()
        self.mSize = QSize(200, 200)
        self.mColor = Qt.black
        self.mMapCanvases = []
        self.ui = timeSeriesViewer.ui
Benjamin Jakimow's avatar
Benjamin Jakimow committed

Benjamin Jakimow's avatar
Benjamin Jakimow committed
        # map-tool handling
        self.mMapTools = []
Benjamin Jakimow's avatar
Benjamin Jakimow committed

Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.scrollArea = self.ui.scrollAreaSubsets
        assert isinstance(self.scrollArea, MapViewScrollArea)
        self.scrollArea.horizontalScrollBar().valueChanged.connect(self.onVisibleMapsChanged)
        self.scrollArea.horizontalScrollBar().rangeChanged.connect(self.onVisibleMapsChanged)
        # self.scrollArea.sigResized.connect(self.onVisibleMapsChanged)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        # self.scrollArea.sigResized.connect(self.refresh())
        # self.scrollArea.horizontalScrollBar().valueChanged.connect(self.mRefreshTimer.start)
Benjamin Jakimow's avatar
Benjamin Jakimow committed

Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.TSV = timeSeriesViewer
        self.TS = timeSeriesViewer.timeSeries()
        self.ui.dockMapViews.setTimeSeries(self.TS)
        self.targetLayout = self.ui.scrollAreaSubsetContent.layout()
Benjamin Jakimow's avatar
Benjamin Jakimow committed

Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.MVC = self.ui.dockMapViews
        assert isinstance(self.MVC, MapViewDock)
        self.MVC.sigShowProfiles.connect(self.sigShowProfiles.emit)
        self.MVC.sigMapViewAdded.connect(self.onMapViewAdded)
        self.MVC.sigMapViewAdded.connect(self.sigMapViewAdded.emit)
        self.MVC.sigMapViewRemoved.connect(self.sigMapViewRemoved.emit)
        self.vectorOverlay = None
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.DVC = DateViewCollection(self)
        self.DVC.sigResizeRequired.connect(self.adjustScrollArea)
Benjamin Jakimow's avatar
Benjamin Jakimow committed

Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.TS.sigTimeSeriesDatesAdded.connect(self.DVC.addDates)
        self.TS.sigTimeSeriesDatesRemoved.connect(self.DVC.removeDates)
Benjamin Jakimow's avatar
Benjamin Jakimow committed

Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.DVC.addDates(self.TS[:])
        if len(self.TS) > 0:
            self.setSpatialExtent(self.TS.maxSpatialExtent())
        #self.setSubsetSize(QSize(100,50))
Benjamin Jakimow's avatar
Benjamin Jakimow committed

Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.mMapRefreshTimer = QTimer(self)
        self.mMapRefreshTimer.timeout.connect(self.timedCanvasRefresh)
        self.mMapRefreshTimer.setInterval(500)
        self.mMapRefreshTimer.start()
        self.mNumberOfHiddenMapsToRefresh = 2
Benjamin Jakimow's avatar
Benjamin Jakimow committed

        self.mSyncLock = False

    def setCurrentLayer(self, layer):

        assert layer is None or isinstance(layer, QgsMapLayer)
        self.mCurrentLayer = layer
        for canvas in self.mapCanvases():
            assert isinstance(canvas, QgsMapCanvas)
            canvas.setCurrentLayer(layer)

    def syncQGISCanvasCenter(self, qgisChanged:bool):

        if self.mSyncLock:
            return

        iface = qgis.utils.iface
        assert isinstance(iface, QgisInterface)

        c = iface.mapCanvas()
        assert isinstance(c, QgsMapCanvas)
        tsvCenter = self.spatialExtent().spatialCenter()
        qgsCenter = SpatialExtent.fromMapCanvas(c).spatialCenter()

        if qgisChanged:
            # change TSV
            if tsvCenter.crs().isValid():
                self.mSyncLock = True
                qgsCenter = qgsCenter.toCrs(tsvCenter.crs())
                self.setSpatialCenter(qgsCenter)
        else:
            # change QGIS
            if qgsCenter.crs().isValid():
                self.mSyncLock = True
                tsvCenter = tsvCenter.toCrs(qgsCenter.crs())
                c.setCenter(tsvCenter)
            else:
                pass
    def visibleMaps(self)->list:
        """
        Returns a list of mapcanvas visible to the user
        :return: [list-of-MapCanvases
        """
        return [m for m in self.mapCanvases() if m.isVisibleToViewport()]

    def onVisibleMapsChanged(self, *args):

        visibleDates = set([m.tsd() for m in self.visibleMaps()])
        if visibleDates != self.mVisibleDates:
            self.mVisibleDates.clear()
            self.mVisibleDates.update(visibleDates)
            self.sigVisibleDatesChanged.emit(list(self.mVisibleDates))
Benjamin Jakimow's avatar
Benjamin Jakimow committed

Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def timedCanvasRefresh(self, *args, force:bool=False):
        self.mSyncLock = False

        # do refresh maps
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        assert isinstance(self.scrollArea, MapViewScrollArea)
Benjamin Jakimow's avatar
Benjamin Jakimow committed

Benjamin Jakimow's avatar
Benjamin Jakimow committed

Benjamin Jakimow's avatar
Benjamin Jakimow committed
        hiddenMaps = sorted([m for m in self.mapCanvases() if not m.isVisibleToViewport()],
                            key = lambda c : self.scrollArea.distanceToCenter(c) )
Benjamin Jakimow's avatar
Benjamin Jakimow committed

Benjamin Jakimow's avatar
Benjamin Jakimow committed
        n = 0
        # redraw all visible maps
        for c in visibleMaps:
            assert isinstance(c, MapCanvas)
            c.timedRefresh()
            n += 1
Benjamin Jakimow's avatar
Benjamin Jakimow committed

Benjamin Jakimow's avatar
Benjamin Jakimow committed
        if n < 10:
            # refresh up to mNumberOfHiddenMapsToRefresh maps which are not visible to the user
            i = 0
            for c in hiddenMaps:
                assert isinstance(c, MapCanvas)
                c.timedRefresh()
                i += 1
                if i >= self.mNumberOfHiddenMapsToRefresh and not force:
                    break
Benjamin Jakimow's avatar
Benjamin Jakimow committed

Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def mapViewFromCanvas(self, mapCanvas:MapCanvas)->MapView:
        """
        Returns the MapView a mapCanvas belongs to
        :param mapCanvas: MapCanvas
        :return: MapView
        """
        for mapView in self.MVC:
            assert isinstance(mapView, MapView)
            if mapCanvas in mapView.mapCanvases():
                return mapView
        return None
Benjamin Jakimow's avatar
Benjamin Jakimow committed

Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def onMapViewAdded(self, *args):
        self.adjustScrollArea()
        s = ""
    def createMapView(self)->MapView:
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        Create a new MapWiew
        :return: MapView
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        return self.MVC.createMapView()
Benjamin Jakimow's avatar
Benjamin Jakimow committed

Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def registerMapCanvas(self, mapCanvas:MapCanvas):
        """
        Connects a MapCanvas and its signals
        :param mapCanvas: MapCanvas
        """
        from eotimeseriesviewer.mapcanvas import MapCanvas
        assert isinstance(mapCanvas, MapCanvas)

        mapCanvas.setMapLayerStore(self.TSV.mMapLayerStore)
        mapCanvas.sigCrosshairPositionChanged.connect(lambda point, canvas=mapCanvas: self.TSV.setCurrentLocation(point, canvas))
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.mMapCanvases.append(mapCanvas)

Benjamin Jakimow's avatar
Benjamin Jakimow committed
        mapCanvas.setFixedSize(self.mSize)
        mapCanvas.setDestinationCrs(self.mCRS)
        mapCanvas.setSpatialExtent(self.mSpatialExtent)


Benjamin Jakimow's avatar
Benjamin Jakimow committed
        def onChanged(e, mapCanvas0=None):
            #self.setSpatialExtent(e, mapCanvas0=mapCanvas0)
            self.setSpatialExtent(e)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        #mapCanvas.sigSpatialExtentChanged.connect(lambda e: self.setSpatialExtent(e, mapCanvas0=mapCanvas))
        #mapCanvas.sigSpatialExtentChanged.connect(lambda e: onChanged(e, mapCanvas0=mapCanvas))
        mapCanvas.sigSpatialExtentChanged.connect(self.setSpatialExtent)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        mapCanvas.sigCrosshairPositionChanged.connect(self.onCrosshairChanged)

    def onCrosshairChanged(self, spatialPoint:SpatialPoint):
        """
        Synchronizes all crosshair positions. Takes care of CRS differences.
        :param spatialPoint: SpatialPoint of new Crosshair position
        """
        from eotimeseriesviewer import 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()]

        if isinstance(spatialPoint, SpatialPoint):
            for mapCanvas in dstCanvases:
                mapCanvas.setCrosshairPosition(spatialPoint, emitSignal=False)


    def setCrosshairStyle(self, crosshairStyle:CrosshairStyle):
        """
        Sets a crosshair style to all map canvas
        :param crosshairStyle: CrosshairStyle

        """
        for mapView in self.mapViews():
            assert isinstance(mapView, MapView)
            mapView.setCrosshairStyle(crosshairStyle)


    def setCrosshairVisibility(self, b:bool):
        """
        Sets the Crosshair visiblity
        :param b: bool
        """
        assert isinstance(b, bool)
        self.onCrosshairChanged(b)


    def setVectorLayer(self, lyr:QgsVectorLayer):
        """
        Sets a QgsVectorLaye to be shown on top of raster images
        :param lyr: QgsVectorLayer
        """
        self.MVC.setVectorLayer(lyr)



    def setMapSize(self, size):
        assert isinstance(size, QSize)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.mSize = size
        from eotimeseriesviewer.mapcanvas import MapCanvas
        for mapCanvas in self.mMapCanvases:
            assert isinstance(mapCanvas, MapCanvas)
            mapCanvas.setFixedSize(size)
        self.sigMapSizeChanged.emit(self.mSize)
        self.adjustScrollArea()
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def mapSize(self)->QSize:
        """
        Returns the MapCanvas size
        :return: QSize
        """
        return QSize(self.mSize)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def refresh(self):
        """
        Refreshes all visible MapCanvases
        """
        for c in self.mapCanvases():
            assert isinstance(c, MapCanvas)
            c.refresh()
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        #self.mMapRefreshTimer.stop()
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def adjustScrollArea(self):
        """
        Adjusts the scroll area widget to fit all visible widgets
        """
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        m = self.targetLayout.contentsMargins()
        nX = len(self.DVC)
        w = h = 0
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        s = QSize()
        r = None
        tsdViews = [v for v in self.DVC if v.ui.isVisible()]
        mapViews = [v for v in self.MVC if v.isVisible()]
        nX = len(tsdViews)
        nY = len(mapViews)
        spacing = self.targetLayout.spacing()
        margins = self.targetLayout.contentsMargins()
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        sizeX = 1
        sizeY = 50
        if nX > 0:
            s = tsdViews[0].ui.sizeHint().width()
            s = nX * (s + spacing) + margins.left() + margins.right()
            sizeX = s
        if nY > 0 and nX > 0:
                s = tsdViews[0].ui.sizeHint().height()
                s = s + margins.top() + margins.bottom()
                sizeY = s
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            #s = tsdViews[0].ui.sizeHint()
            #s = QSize(nX * (s.width() + spacing) + margins.left() + margins.right(),
            #          s.height() + margins.top() + margins.bottom())
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        #print(sizeX, sizeY)
        self.targetLayout.parentWidget().resize(QSize(sizeX, sizeY))

    def setMapTool(self, mapToolKey, *args, **kwds):
        """
        Create a maptool instance to each MapCanvas
        :param mapToolKey: str which MapTool is to create, or QgsMapTool instance
        :param args: optional maptool arguments
        :param kwds: optional maptool keywords
        :return: [list-of-QgsMapTools]
        """
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        del self.mMapTools[:]
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        from eotimeseriesviewer import MapTools, CursorLocationMapTool, SpectralProfileMapTool, TemporalProfileMapTool
        for canvas in self.mMapCanvases:
            mt = None
            if mapToolKey in MapTools.mapToolKeys():
                mt = MapTools.create(mapToolKey, canvas, *args, **kwds)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            if isinstance(mapToolKey, QgsMapTool):
                mt = MapTools.copy(mapToolKey, canvas, *args, **kwds)

            if isinstance(mt, QgsMapTool):
                canvas.setMapTool(mt)
                self.mMapTools.append(mt)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
                # if required, link map-tool with specific slots
                if isinstance(mt, CursorLocationMapTool):
                    mt.sigLocationRequest[SpatialPoint, QgsMapCanvas].connect(self.onLocationRequest)
Benjamin Jakimow's avatar
Benjamin Jakimow committed

Benjamin Jakimow's avatar
Benjamin Jakimow committed
        return self.mMapTools
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def onLocationRequest(self, pt:SpatialPoint, canvas:QgsMapCanvas):
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.sigShowProfiles.emit(pt, canvas, "")
    def setSpatialCenter(self, center:SpatialPoint, mapCanvas0=None):
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        Sets the spatial center of all MapCanvases
        :param center: SpatialPoint
        :param mapCanvas0:
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        assert isinstance(center, SpatialPoint)
        extent = self.spatialExtent()
Benjamin Jakimow's avatar
Benjamin Jakimow committed

        if isinstance(extent, SpatialExtent):
            centerOld = extent.center()
            center = center.toCrs(extent.crs())
            if center != centerOld:
                extent = extent.__copy__()
                extent.setCenter(center)
                self.setSpatialExtent(extent)
    def spatialCenter(self)->SpatialPoint:
        return self.spatialExtent().spatialCenter()
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def setSpatialExtent(self, extent, mapCanvas0=None):
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        Sets the spatial extent of all MapCanvases
        :param extent: SpatialExtent
        :param mapCanvas0:
        :return:
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        assert isinstance(extent, SpatialExtent)
        extent = extent.toCrs(self.crs())
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        if not isinstance(extent, SpatialExtent) \
            or extent.isEmpty() or not extent.isFinite() \
            or extent.width() <= 0 \
            or extent.height() <= 0 \
            or extent == self.mSpatialExtent:
            return
Benjamin Jakimow's avatar
Benjamin Jakimow committed

Benjamin Jakimow's avatar
Benjamin Jakimow committed
        if self.mSpatialExtent == extent:
            return

        self.mSpatialExtent = extent
        for mapCanvas in self.mapCanvases():
            assert isinstance(mapCanvas, MapCanvas)
        if lastExtent != extent:
            self.sigSpatialExtentChanged.emit(extent)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def mapViewDock(self)->MapViewDock:
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        Returns the MapViewDock that controls all MapViews
        :return: MapViewDock
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        return self.MVC
Benjamin Jakimow's avatar
Benjamin Jakimow committed

Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def setMapBackgroundColor(self, color:QColor):
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        Sets the MapCanvas background color
        :param color: QColor
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        assert isinstance(self.MVC, MapViewDock)
        self.MVC.setMapBackgroundColor(color)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def mapCanvases(self, mapView=None)->list:
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        Returns MapCanvases
        :param mapView: a MapView to return MapCanvases from only, defaults to None
        :return: [list-of-MapCanvas]
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        if isinstance(mapView, MapView):
            s = ""
        return self.mMapCanvases[:]
Benjamin Jakimow's avatar
Benjamin Jakimow committed

Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def mapViews(self)->list:
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        Returns a list of all mapviews
        :return [list-of-MapViews]:
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        return self.MVC[:]
Benjamin Jakimow's avatar
Benjamin Jakimow committed

Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def setCrs(self, crs):
        assert isinstance(crs, QgsCoordinateReferenceSystem)
Benjamin Jakimow's avatar
Benjamin Jakimow committed

Benjamin Jakimow's avatar
Benjamin Jakimow committed
        if self.mCRS != crs:
            transform = QgsCoordinateTransform()
            transform.setSourceCrs(self.mCRS)
            transform.setDestinationCrs(crs)
            if transform.isValid() and not transform.isShortCircuited():
                self.mCRS = crs
                for mapCanvas in self.mapCanvases():
                    # print(('STV set CRS {} {}', str(mapCanvas), self.mCRS.description()))
                    mapCanvas.setDestinationCrs(QgsCoordinateReferenceSystem(crs))
                """
                from timeseriesviewer.utils import saveTransform
                if saveTransform(self.mSpatialExtent, self.mCRS, crs):
                    self.mCRS = crs
                    
                else:
                    pass
                """
                self.sigCRSChanged.emit(self.crs())
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def crs(self)->QgsCoordinateReferenceSystem:
        """
        Returns the QgsCoordinateReferenceSystem
        :return: QgsCoordinateReferenceSystem
        """
        return self.mCRS
Benjamin Jakimow's avatar
Benjamin Jakimow committed

Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def spatialExtent(self)->SpatialExtent:
        """
        Returns the SpatialExtent
        :return: SpatialExtent
        """
        return self.mSpatialExtent
Benjamin Jakimow's avatar
Benjamin Jakimow committed

Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def navigateToTSD(self, TSD:TimeSeriesDatum):
        """
        Changes the viewport of the scroll window to show the requested TimeSeriesDatum
        :param TSD: TimeSeriesDatum
        """
        assert isinstance(TSD, TimeSeriesDatum)
        #get widget related to TSD
        tsdv = self.DVC.tsdView(TSD)
        assert isinstance(self.scrollArea, QScrollArea)
        self.scrollArea.ensureWidgetVisible(tsdv.ui)