Skip to content
Snippets Groups Projects
mapvisualization.py 99 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
    import sys
    import re
    import fnmatch
    import collections
    import copy
    
    import traceback
    
    import typing
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    import math
    
    import numpy as np
    
    import time
    
    import qgis.utils
    
    from qgis.PyQt.QtCore import \
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        Qt, QSize, pyqtSignal, QModelIndex, QTimer, QAbstractListModel, QMargins
    
    from qgis.PyQt.QtGui import \
        QColor, QIcon, QGuiApplication, QMouseEvent
    from qgis.PyQt.QtWidgets import \
        QWidget, QLayoutItem, QFrame, QLabel, QGridLayout, QSlider, QMenu, \
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        QToolBox, QDialog, QAction, QSpinBox, QCheckBox, QLineEdit, QWidgetItem, QSpacerItem, QLayout
    
    from qgis.PyQt.QtXml import \
        QDomDocument, QDomNode, QDomElement
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
    from qgis.core import \
    
        QgsCoordinateReferenceSystem, QgsVectorLayer, QgsTextFormat, QgsProject, \
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        QgsRectangle, QgsRasterRenderer, QgsMapLayerStore, QgsMapLayerStyle, \
    
        QgsLayerTreeModel, QgsLayerTreeGroup, QgsPointXY, \
    
        QgsLayerTree, QgsLayerTreeLayer, QgsReadWriteContext, QgsVector, \
    
        QgsRasterLayer, QgsVectorLayer, QgsMapLayer, QgsMapLayerProxyModel, QgsColorRamp, \
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        QgsSingleBandPseudoColorRenderer, QgsExpressionContextGenerator, QgsExpressionContext, \
        QgsExpressionContextUtils, QgsExpression, QgsExpressionContextScope
    
    from qgis.gui import \
        QgsDockWidget, QgsMapCanvas, QgsMapTool, QgsCollapsibleGroupBox, QgsLayerTreeView, \
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        QgisInterface, QgsLayerTreeViewMenuProvider, QgsLayerTreeMapCanvasBridge, \
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        QgsProjectionSelectionWidget, QgsMessageBar, QgsFieldExpressionWidget, QgsFilterLineEdit, \
        QgsExpressionLineEdit, QgsExpressionBuilderDialog, QgsAttributeForm
    
    from .externals.qps.crosshair.crosshair import getCrosshairStyle, CrosshairStyle, CrosshairMapCanvasItem
    
    from .externals.qps.layerproperties import VectorLayerTools
    from .externals.qps.maptools import MapTools
    from .mapcanvas import MapCanvas, MapCanvasInfoItem, KEY_LAST_CLICKED
    from .timeseries import SensorInstrument, TimeSeriesDate, TimeSeries, SensorProxyLayer
    from .utils import loadUi, SpatialPoint, SpatialExtent, datetime64
    from eotimeseriesviewer import DIR_UI
    
    from eotimeseriesviewer.utils import fixMenuButtons
    
    KEY_LOCKED_LAYER = 'eotsv/locked'
    KEY_SENSOR_GROUP = 'eotsv/sensorgroup'
    KEY_SENSOR_LAYER = 'eotsv/sensorlayer'
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    def equalTextFormats(tf1: QgsTextFormat, tf2: QgsTextFormat) -> True:
    
        if not (isinstance(tf1, QgsTextFormat) and isinstance(tf2, QgsTextFormat)):
            return False
    
        return tf1.toMimeData().text() == tf2.toMimeData().text()
    
    class MapViewLayerTreeModel(QgsLayerTreeModel):
        """
        Layer Tree as shown in a MapView
        """
    
        def __init__(self, rootNode, parent=None):
            super(MapViewLayerTreeModel, self).__init__(rootNode, parent=parent)
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
    class MapViewExpressionContextGenerator(QgsExpressionContextGenerator):
    
        def __init__(self, *args, **kwds):
            super().__init__(*args, **kwds)
            self.mMapView: MapView = None
    
        def setMapView(self, mapView):
            self.mMapView = mapView
    
        def createExpressionContext(self) -> QgsExpressionContext:
            context = QgsExpressionContext([QgsExpressionContextUtils.projectScope(QgsProject.instance())])
    
            if False and isinstance(self.mMapView, MapView):
                canvas = self.mMapView.currentMapCanvas()
                context.appendScope(canvas.expressionContextScope())
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            # self._context = context
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
    class MapView(QFrame):
    
        """
        A MapView defines how a single map canvas visualizes sensor specific EOTS data plus additional vector overlays
        """
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        # sigVisibilityChanged = pyqtSignal(bool)
    
        sigCanvasAppearanceChanged = pyqtSignal()
        sigCrosshairChanged = pyqtSignal()
    
        sigTitleChanged = pyqtSignal(str)
    
        sigSensorRendererChanged = pyqtSignal(SensorInstrument, QgsRasterRenderer)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        sigCurrentLayerChanged = pyqtSignal(QgsMapLayer)
    
        sigShowProfiles = pyqtSignal(SpatialPoint, MapCanvas, str)
    
        def __init__(self, name='Map View', parent=None):
            super(MapView, self).__init__(parent)
    
            loadUi(DIR_UI / 'mapview.ui', self)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            # self.setupUi(self)
    
            from eotimeseriesviewer.settings import values, Keys, defaultValues, value
    
            DEFAULT_VALUES = defaultValues()
            self.mMapBackgroundColor: QColor = value(Keys.MapBackgroundColor,
                                                     default=DEFAULT_VALUES.get(Keys.MapBackgroundColor, QColor('black')))
            self.mMapTextFormat: QgsTextFormat = value(Keys.MapTextFormat,
                                                       default=DEFAULT_VALUES.get(Keys.MapTextFormat, QgsTextFormat()))
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            self.mLayerStyleInitialized: typing.Dict[SensorInstrument, bool] = dict()
    
            self.mTimeSeries = None
            self.mSensorLayerList = list()
            self.mCrossHairStyle = CrosshairStyle()
    
    
            m = QMenu(self.btnToggleCrosshair)
            m.addAction(self.actionSetCrosshairStyle)
    
            self.btnToggleCrosshair.setMenu(m)
            self.btnToggleCrosshair.setDefaultAction(self.actionToggleCrosshairVisibility)
    
            self.btnToggleCrosshair.setChecked(self.crosshairStyle().mShow)
    
            self.btnToggleMapViewVisibility.setDefaultAction(self.actionToggleMapViewHidden)
    
            self.tbName.textChanged.connect(self.onTitleChanged)
    
            self.actionSetCrosshairStyle.triggered.connect(self.onChangeCrosshairStyle)
    
            self.actionToggleMapViewHidden.toggled.connect(self.sigCanvasAppearanceChanged)
    
            self.actionToggleCrosshairVisibility.toggled.connect(self.setCrosshairVisibility)
    
    
            self.actionAddMapLayer.triggered.connect(lambda *args: self.onAddMapLayer())
            self.actionAddVectorLayer.triggered.connect(lambda *args: self.onAddMapLayer(QgsMapLayerProxyModel.VectorLayer))
            self.actionAddRasterLayer.triggered.connect(lambda *args: self.onAddMapLayer(QgsMapLayerProxyModel.RasterLayer))
            self.btnAddLayer.setDefaultAction(self.actionAddMapLayer)
            m = QMenu()
            m.addAction(self.actionAddVectorLayer)
            m.addAction(self.actionAddRasterLayer)
            self.btnAddLayer.setMenu(m)
    
            self.btnHighlightMapView.setDefaultAction(self.actionHighlightMapView)
            self.actionHighlightMapView.triggered.connect(lambda: self.setHighlighted(True, timeout=500))
    
            assert isinstance(self.mLayerTreeView, QgsLayerTreeView)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            self.mDummyCanvas = QgsMapCanvas()  # dummy map canvas for dummy layers
    
            self.mDummyCanvas.setVisible(False)
    
            self.mLayerTree = QgsLayerTree()
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            self.mLayerTreeMapCanvasBridge = 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()
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            self.mLayerTreeView: QgsLayerTreeView
    
            self.mLayerTreeView.setModel(self.mLayerTreeModel)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            self.mLayerTreeView.currentLayerChanged.connect(self.sigCurrentLayerChanged.emit)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            self.mMapLayerTreeViewMenuProvider = MapViewLayerTreeViewMenuProvider(self, self.mLayerTreeView,
                                                                                  self.mDummyCanvas)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
            # register some actions that interact with other GUI elements
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            # self.mMapLayerTreeViewMenuProvider.actionAddEOTSVSpectralProfiles.triggered.connect(self.addSpectralProfileLayer)
            # self.mMapLayerTreeViewMenuProvider.actionAddEOTSVTemporalProfiles.triggered.connect(self.addTemporalProfileLayer)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
            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)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            self.tbInfoExpression: QLineEdit
    
            self.mDefaultInfoExpressionToolTip:str = self.tbInfoExpression.toolTip()
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            self.tbInfoExpression.textChanged.connect(self.onMapInfoExpressionChanged)
            self.btnShowInfoExpression.setDefaultAction(self.optionShowInfoExpression)
            self.optionShowInfoExpression.toggled.connect(self.tbInfoExpression.setEnabled)
            self.optionShowInfoExpression.toggled.connect(self.actionSetInfoExpression.setEnabled)
            self.btnSetInfoExpression.setDefaultAction(self.actionSetInfoExpression)
            self.actionSetInfoExpression.triggered.connect(self.onSetInfoExpression)
    
            self._fakeLyr: QgsVectorLayer = QgsVectorLayer("point?crs=epsg:4326", "Scratch point layer", "memory")
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            # self.tbInfoExpression.setLayer(self.mLyr)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            # self.mExpressionContextGenerator = MapViewExpressionContextGenerator()
            # self.mExpressionContextGenerator.setMapView(self)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
            self.tbInfoExpression.setEnabled(self.optionShowInfoExpression.isChecked())
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            # self.tbInfoExpression.registerExpressionContextGenerator(self.mExpressionContextGenerator)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            self.optionShowInfoExpression.toggled.connect(self.sigCanvasAppearanceChanged)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            # self.tbInfoExpression.expressionChanged.connect(self.sigCanvasAppearanceChanged)
    
            for action in m.actions():
                action.toggled.connect(self.sigCanvasAppearanceChanged)
    
    
        def setInfoExpressionError(self, error: str):
    
            if error in ['', None]:
                self.tbInfoExpression.setStyleSheet('')
                self.tbInfoExpression.setToolTip(self.mDefaultInfoExpressionToolTip)
            else:
    
                self.tbInfoExpression.setStyleSheet('QLineEdit#tbInfoExpression{color:red; border: 2px solid red;}')
                self.tbInfoExpression.setToolTip(f'<span style="color:red">{error}</span>')
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def onMapInfoExpressionChanged(self, text: str):
    
            self.sigCanvasAppearanceChanged.emit()
            s = ""
    
        def onSetInfoExpression(self, *args):
    
            context = QgsExpressionContext(QgsExpressionContextUtils.globalProjectLayerScopes(self._fakeLyr))
            c = self.currentMapCanvas()
            if isinstance(c, MapCanvas):
                context.appendScope(QgsExpressionContextScope(c.expressionContextScope()))
            expression = self.tbInfoExpression.text()
            # taken from qgsfeaturefilterwidget.cpp : void QgsFeatureFilterWidget::filterExpressionBuilder()
            dlg = QgsExpressionBuilderDialog(self._fakeLyr, expression,
                                             self,
                                             'generic', context)
            dlg.setWindowTitle('Expression Based Filter')
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            # myDa = QgsDistanceArea()
            # myDa.setSourceCrs(self.mLayer.crs(), QgsProject.instance().transformContext())
            # myDa.setEllipsoid(QgsProject.instance().ellipsoid())
            # dlg.setGeomCalculator(myDa)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
            if dlg.exec() == QDialog.Accepted:
                self.tbInfoExpression.setText(dlg.expressionText())
    
        def __iter__(self) -> typing.Iterator[TimeSeriesDate]:
            return iter(self.mapCanvases())
    
    
        @staticmethod
        def readXml(node: QDomNode):
            if node.nodeName() == 'MapView':
                nodeMapView = node
            else:
                nodeMapView = node.firstChildElement('MapView')
    
            if nodeMapView.nodeName() != 'MapView':
                return None
    
            context = QgsReadWriteContext()
            mapView = MapView()
    
            def to_bool(value) -> bool:
                return str(value).lower() in ['1', 'true']
    
            mapView.setName(nodeMapView.attribute('name'))
            mapView.setMapBackgroundColor(QColor(nodeMapView.attribute('bg')))
            mapView.setVisibility(to_bool(nodeMapView.attribute('visible')))
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            # mapView.optionShowDate.setChecked(to_bool(nodeMapView.attribute('showDate')))
            # mapView.optionShowSensorName.setChecked(to_bool(nodeMapView.attribute('showSensorName')))
            # mapView.optionShowMapViewName.setChecked(to_bool(nodeMapView.attribute('showMapViewName')))
    
            # nodeMapView.setAttribute('showDate', str(self.optionShowDate.checked()))
            # nodeMapView.setAttribute('showSensorName', str(self.optionShowSensorName.checked()))
            # nodeMapView.setAttribute('showMapViewName', str(self.optionShowMapViewName.checked()))
    
    
            textFormat = mapView.mapTextFormat()
            textFormat.readXml(nodeMapView, context)
            lyrNode = node.firstChildElement('MapViewProxyLayer').toElement()
            while lyrNode.nodeName() == 'MapViewProxyLayer':
                sid = lyrNode.attribute('sensor_id')
                styleNode = lyrNode.firstChildElement('LayerStyle')
                style = QgsMapLayerStyle()
                style.readXml(styleNode)
                sensor = SensorInstrument(sid)
                mapView.addSensor(sensor)
                lyr = mapView.sensorProxyLayer(sensor)
                lyr.setMapLayerStyle(style)
    
    
                lyrNode = lyrNode.nextSiblingElement()
    
            return mapView
    
        def writeXml(self, node: QDomNode, doc: QDomDocument):
    
            nodeMapView = doc.createElement('MapView')
            nodeMapView.setAttribute('name', self.name())
            nodeMapView.setAttribute('bg', self.mapBackgroundColor().name())
            nodeMapView.setAttribute('visible', str(self.isVisible()))
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            nodeMapView.setAttribute('infoexpression', self.mapInfoExpression())
    
    
            context = QgsReadWriteContext()
            nodeTextStyle = self.mapTextFormat().writeXml(doc, context)
            nodeMapView.appendChild(nodeTextStyle)
    
            for sensor in self.sensors():
                lyr = self.sensorProxyLayer(sensor)
                if isinstance(lyr, SensorProxyLayer):
                    sensorNode = doc.createElement('MapViewProxyLayer')
                    sensorNode.setAttribute('sensor_id', sensor.id())
                    style: QgsMapLayerStyle = lyr.mapLayerStyle()
                    styleNode = doc.createElement('LayerStyle')
                    style.writeXml(styleNode)
                    sensorNode.appendChild(styleNode)
                    nodeMapView.appendChild(sensorNode)
            node.appendChild(nodeMapView)
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def setName(self, name: str):
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def name(self) -> str:
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def setMapInfoExpression(self, expression: str):
            self.tbInfoExpression.setText(expression)
    
        def mapInfoExpression(self) -> str:
    
            return f'{self.tbInfoExpression.text()}'.strip()
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def setMapTextFormat(self, textformat: QgsTextFormat) -> QgsTextFormat:
    
            if not equalTextFormats(self.mapTextFormat(), textformat):
                self.mMapTextFormat = textformat
                self.sigCanvasAppearanceChanged.emit()
            return self.mapTextFormat()
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def mapTextFormat(self) -> QgsTextFormat:
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def mapBackgroundColor(self) -> QColor:
    
            """
            Returns the map background color
            :return: QColor
            """
            return self.mMapBackgroundColor
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def setMapBackgroundColor(self, color: QColor) -> QColor:
    
            """
            Sets the map background color
            :param color: QColor
            :return: QColor
            """
            if self.mMapBackgroundColor != color:
                self.mMapBackgroundColor = color
                self.sigCanvasAppearanceChanged.emit()
            return self.mMapBackgroundColor
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def visibleMapCanvases(self) -> list:
    
            """
            Returns the currently visible mapcanvases
            :return: [list-of-MapCanvases]
            """
            return [m for m in self.mapCanvases() if m.isVisibleToViewport()]
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def onAddMapLayer(self, filter: QgsMapLayerProxyModel.Filter = QgsMapLayerProxyModel.All):
    
            Slot that opens a SelectMapLayersDialog for any kind of layer
    
            """
            from .externals.qps.utils import SelectMapLayersDialog
            d = SelectMapLayersDialog()
    
    
            if filter == QgsMapLayerProxyModel.All:
                title = 'Select Layer'
                text = 'Layer'
            elif filter == QgsMapLayerProxyModel.RasterLayer:
                title = 'Select Raster Layer'
                text = 'Raster'
            elif filter == QgsMapLayerProxyModel.VectorLayer:
                title = 'Select Vector Layer'
                text = 'Vector'
            d.setWindowTitle(title)
            d.addLayerDescription(text, filter)
    
            if d.exec() == QDialog.Accepted:
                for l in d.mapLayers():
                    self.addLayer(l)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def setCurrentLayer(self, layer: QgsMapLayer):
    
            """
            Sets the QgsMapCanvas.currentLayer() that is used by some QgsMapTools
            :param layer: QgsMapLayer | None
            :return:
            """
    
            assert layer is None or isinstance(layer, QgsMapLayer)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            if layer in self.layers():
                self.mLayerTreeView.setCurrentLayer(layer)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                if layer not in self.mSensorLayerList:
                    for c in self.mapCanvases():
                        c.setCurrentLayer(layer)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                return True
    
        def addSpectralProfileLayers(self):
            """Adds the EOTSV Spectral Profile Layers"""
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            from eotimeseriesviewer.main import EOTimeSeriesViewer
            tsv = EOTimeSeriesViewer.instance()
            if isinstance(tsv, EOTimeSeriesViewer):
    
                for lyr in tsv.spectralLibraries():
                    if lyr not in self.layers():
                        self.addLayer(lyr)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
        def addTemporalProfileLayer(self):
            """Adds the EOTSV Temporal Profile Layer"""
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            from eotimeseriesviewer.main import EOTimeSeriesViewer
            tsv = EOTimeSeriesViewer.instance()
            if isinstance(tsv, EOTimeSeriesViewer):
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                lyr = tsv.temporalProfileLayer()
                if lyr not in self.layers():
                    self.addLayer(lyr)
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        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)
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        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):
    
    
            canvases = self.mapCanvases()
            if len(canvases) > 0:
                mapCanvas = canvases[0]
            else:
                mapCanvas = None
    
            style = getCrosshairStyle(parent=self, crosshairStyle=self.crosshairStyle(), mapCanvas=mapCanvas)
    
            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
    
    
            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.sigCanvasAppearanceChanged.emit()
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def isVisible(self) -> bool:
    
            return not self.actionToggleMapViewHidden.isChecked()
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def mapWidget(self):
            return self.mMapWidget
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def mapCanvases(self) -> typing.List[MapCanvas]:
    
            Returns the MapCanvases related to this map view. Requires that this mapview was added to a MapWidget
    
    
            if isinstance(self.mMapWidget, MapWidget):
                return self.mMapWidget.mapViewCanvases(self)
            else:
                return []
    
        def onTitleChanged(self, *args):
    
            self.setWindowTitle('Map View "{}"'.format(self.title()))
    
            self.sigTitleChanged.emit(self.title())
    
            self.sigCanvasAppearanceChanged.emit()
    
    
        def setMapWidget(self, w):
            if isinstance(w, MapWidget):
                self.mMapWidget = w
            else:
                self.mMapWidget = None
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        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)
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def timeSeries(self) -> TimeSeries:
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            """
            Returns the TimeSeries this mapview is connected with
            :return: TimeSeries
            """
            return self.mTimeSeries
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        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)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def visibleLayers(self) -> typing.List[QgsMapLayer]:
    
            """
            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)]
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def layers(self) -> typing.List[QgsMapLayer]:
            """
            Returns all layers, including invisible or proxy layers for time-series data
            :return: [list-of-QgsMapLayers]
            """
            nodes = self.mLayerTree.findLayers()
            return [n.layer() for n in nodes if isinstance(n.layer(), QgsMapLayer)]
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def title(self, maskNewLines=True) -> str:
    
            """
            Returns the MapView title
            :return: str
            """
    
            if maskNewLines:
                return self.tbName.text().replace('\\n', ' ').strip()
            else:
                return self.tbName.text().strip()
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def setCrosshairStyle(self, crosshairStyle: CrosshairStyle) -> CrosshairStyle:
    
            :param crosshairStyle: CrosshairStyle
            """
    
            if self.mCrossHairStyle != crosshairStyle:
                self.mCrossHairStyle = crosshairStyle
                self.sigCrosshairChanged.emit()
    
            return self.mCrossHairStyle
    
        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:
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                    QTimer.singleShot(timeout, lambda: self.setHighlighted(False))
    
            else:
                for mapCanvas in self.mapCanvases():
                    mapCanvas.setStyleSheet(styleOff)
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def currentMapCanvas(self) -> MapCanvas:
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            """
            Returns the MapCanvas that was clicked / used last
            :return: MapCanvas
            """
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            if not isinstance(self.mMapWidget, MapWidget):
                return None
            canvases = sorted(self.mMapWidget.mapViewCanvases(self),
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                              key=lambda c: c.property(KEY_LAST_CLICKED))
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            if len(canvases) == 0:
                return None
            else:
                return canvases[-1]
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def currentLayer(self) -> QgsMapLayer:
            """
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            Returns the current map layer, i.e. that selected in the map layer tree view.
            If this is a proxy layer, the MapView will try to return a "real" layer from a MapCanvas
    
    
            :return: QgsMapLayer
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            """
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            cl = self.mLayerTreeView.currentLayer()
            if isinstance(cl, SensorProxyLayer):
                sensor = cl.sensor()
    
                canvases = [c for c in self.mapCanvases() if c.tsd().sensor() == sensor]
                canvases = sorted(canvases, key=lambda c: c is not self.currentMapCanvas())
                for c in canvases:
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                    for l in c.layers():
                        if isinstance(l, SensorProxyLayer):
                            return l
            return cl
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def crosshairStyle(self) -> CrosshairStyle:
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def setCrosshairVisibility(self, b: bool):
    
            Enables / diables the map canvas crosshair.
            :param b: bool
    
            if b != self.actionToggleCrosshairVisibility.isChecked():
    
                self.actionToggleCrosshairVisibility.setChecked(b)
    
                self.mCrossHairStyle.setVisibility(b)
                self.sigCrosshairChanged.emit()
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def sensorProxyLayers(self) -> typing.List[SensorProxyLayer]:
    
            layers = [n.layer() for n in self.mLayerTreeSensorNode.findLayers()]
            return [l for l in layers if isinstance(l, SensorProxyLayer)]
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        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
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def sensorLayers(self, sensor: SensorInstrument) -> typing.List[SensorProxyLayer]:
            """
            :param sensor:
            :return:
            """
            layers = []
            for c in self.mapCanvases():
                for l in c.layers():
                    if isinstance(l, SensorProxyLayer) and l.sensor() == sensor:
                        layers.append(l)
            return layers
    
        def sensors(self) -> typing.List[SensorInstrument]:
    
            """
            Returns a list of SensorsInstruments
            :return: [list-of-SensorInstruments]
            """
            return [t[0] for t in self.mSensorLayerList]
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        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():
    
                sensor.sigNameChanged.connect(self.sigCanvasAppearanceChanged)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                masterLayer: SensorProxyLayer = sensor.proxyRasterLayer()
                assert isinstance(masterLayer.renderer(), QgsRasterRenderer)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                self.mSensorLayerList.append((sensor, masterLayer))
                masterLayer.styleChanged.connect(lambda *args, v=self, l=masterLayer: self.onMasterStyleChanged(l))
    
                masterLayer.nameChanged.connect(self.onMasterLyrNameChanged)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                layerTreeLayer: QgsLayerTreeLayer = self.mLayerTreeSensorNode.addLayer(masterLayer)
    
                layerTreeLayer.setCustomProperty(KEY_LOCKED_LAYER, True)
                layerTreeLayer.setCustomProperty(KEY_SENSOR_LAYER, True)
    
    
                dummyLayers = self.mDummyCanvas.layers() + [masterLayer]
                self.mDummyCanvas.setLayers(dummyLayers)
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                self.mLayerStyleInitialized[sensor] = False
    
    
        def onMasterLyrNameChanged(self, *args):
            lyr = self.sender()
            newname = lyr.name()
            ltn = self.mLayerTreeSensorNode.findLayer(lyr)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def onMasterStyleChanged(self, masterLayer: SensorProxyLayer):
    
            sensor: SensorInstrument = masterLayer.sensor()
            style: QgsMapLayerStyle = masterLayer.mapLayerStyle()
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            # print('### MASTER-STYLE-CHANGED')
            # print(style.xmlData())
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            for lyr in self.sensorLayers(sensor):
                lyr.setMapLayerStyle(style)
    
    
            for c in self.sensorCanvases(sensor):
                assert isinstance(c, MapCanvas)
                c.addToRefreshPipeLine(MapCanvas.Command.RefreshRenderer)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
            self.mLayerStyleInitialized[sensor] = True
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        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)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            return [c for c in self.mapCanvases() if isinstance(c, MapCanvas) and \
                    isinstance(c.tsd(), TimeSeriesDate) 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()))
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def removeSensor(self, sensor: SensorInstrument):
    
            """
            Removes a sensor from this map view
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            if sensor in self.mLayerStyleInitialized.keys():
                self.mLayerStyleInitialized.pop(sensor)
    
            toRemove = []
            for t in self.mSensorLayerList:
    
                    toRemove.append(t)
    
            for t in toRemove:
                self.mLayerTreeSensorNode.removeLayer(t[1])
                self.mSensorLayerList.remove(t)
    
        def hasSensor(self, sensor: SensorInstrument) -> bool:
    
            """
            :param sensor:
            :return:
            """
            assert isinstance(sensor, SensorInstrument)
            return sensor in self.sensors()
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    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: QgsLayerTreeView = view
            self.mDummyCanvas: QgsMapCanvas = canvas
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            # self.mDefActions = QgsLayerTreeViewDefaultActions(self.mLayerTreeView)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            self.mMapView: MapView = mapView
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            # 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)
            # self.actionAddEOTSVSpectralProfiles = QAction('Add Spectral Profile Layer')
            # self.actionAddEOTSVTemporalProfiles = QAction('Add Temporal Profile Layer')
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
        def mapView(self) -> MapView:
            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 onRemoveLayers(self):
            selected = self.layerTreeView().selectedLayers()
            for l in selected:
                if not isinstance(l, SensorProxyLayer):
                    self.mapView().mLayerTree.removeLayer(l)
    
        def onSetCanvasCRS(self):
            s = ""
            lyr = self.layerTreeView()
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def onZoomToLayer(self, layer: QgsMapLayer):
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            extent = SpatialExtent.fromLayer(layer)
            if isinstance(extent, SpatialExtent):
                extent = extent.toCrs(self.mapView().mapWidget().crs())
                self.mapView().mapWidget().setSpatialExtent(extent)
    
        def onZoomActualSize(self):
            current = self.mapView().currentLayer()
            if isinstance(current, QgsRasterLayer):
                s = ""
    
        def onStretchToExtent(self):
            current = self.mapView().currentLayer()
            canvas = self.mapView().currentMapCanvas()
            if not isinstance(canvas, MapCanvas):
                return
            if isinstance(current, SensorProxyLayer):
    
                for l in canvas.layers():
                    if isinstance(l, SensorProxyLayer) and l.sensor() == current.sensor():
                        canvas.stretchToExtent(layer=current)
                        break
    
            elif isinstance(current, QgsRasterLayer):
                canvas.stretchToExtent(layer=current)
    
        def createContextMenu(self) -> QMenu:
    
            model = self.layerTreeModel()
            ltree = self.layerTree()
    
            view: QgsLayerTreeView = self.layerTreeView()
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            currentGroup = view.currentGroupNode()
            currentLayer = view.currentLayer()
    
            currentIndex = view.currentIndex()
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            currentCanvas = self.mapView().currentMapCanvas()
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            isSensorGroup = isinstance(currentGroup, QgsLayerTreeGroup) and currentGroup.customProperty(
                KEY_SENSOR_GROUP) in [True, 'true']
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            isSensorLayer = isinstance(currentLayer, SensorProxyLayer)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            mv: MapView = self.mapView()
            mw: MapWidget = mv.mapWidget()
            mw.setCurrentMapView(mv)
            if isSensorLayer:
                # the current layer is an "empty" proxy layer. use one from a visible map canvas instead
                pass
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            from eotimeseriesviewer.main import EOTimeSeriesViewer
            eotsv = EOTimeSeriesViewer.instance()
            if not isinstance(eotsv, EOTimeSeriesViewer):
                return
            menu = QMenu(view)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            assert isinstance(mw, MapWidget)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            if isinstance(currentLayer, QgsMapLayer):
    
                a = menu.addAction('Rename')
                a.triggered.connect(lambda *args, cidx=currentIndex: view.edit(cidx))
                menu.addSeparator()
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                # zoom to layer
                menu.addAction(eotsv.actionZoomToLayer())
    
                # rename layer
                #
    
                # zoom to native resolution
                # in this case not using a map tool but the current layer
                ref = eotsv.actionZoomActualSize()
                a = menu.addAction(ref.text())
                a.setIcon(ref.icon())
                a.triggered.connect(self.onZoomActualSize)
    
                if isinstance(currentLayer, QgsRasterLayer):
                    a = menu.addAction('&Stretch Using Current Extent')
                    a.triggered.connect(self.onStretchToExtent)
    
                # ----
                menu.addSeparator()
                a = menu.addAction('Add Spectral Library Layer')
    
                a.triggered.connect(self.mapView().addSpectralProfileLayers)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
                a = menu.addAction('Add Temporal Profile Layer')
                a.triggered.connect(self.mapView().addTemporalProfileLayer)
    
                # ----
                menu.addSeparator()
                # duplicate layer
                # remove layer
    
                a = menu.addAction('Remove layer')
                a.setToolTip('Remove layer(s)')
                a.triggered.connect(self.onRemoveLayers)
    
                menu.addSeparator()
                if isinstance(currentLayer, QgsVectorLayer):
                    menu.addAction(eotsv.actionOpenTable())
                    menu.addAction(eotsv.actionToggleEditing())
    
                if isinstance(currentLayer, QgsRasterLayer) and not isinstance(currentLayer, SensorProxyLayer):
                    pass
    
                menu.addSeparator()
                # ----------
                # set CRS
                action = menu.addAction('Set layer CRS to map canvas')
                action.triggered.connect(self.onSetCanvasCRS)
    
                # ----
                # Export...
                # ------
                menu.addSeparator()
                # Styles...
                menu.addAction(eotsv.actionPasteLayerStyle())
                menu.addAction(eotsv.actionCopyLayerStyle())
    
                # Properties
                menu.addSeparator()
    
                menu.addAction(eotsv.actionLayerProperties())
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
            menu.addSeparator()
            return menu
    
        def vectorLayerTools(self) -> VectorLayerTools:
    
            from eotimeseriesviewer.main import EOTimeSeriesViewer
            return EOTimeSeriesViewer.instance().mVectorLayerTools
    
        def showAttributeTable(self, lyr: QgsVectorLayer):
    
            from eotimeseriesviewer.main import EOTimeSeriesViewer
            tsv = EOTimeSeriesViewer.instance()
            if isinstance(tsv, EOTimeSeriesViewer):
                tsv.showAttributeTable(lyr)
            s = ""
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def onSetLayerProperties(self, lyr: QgsRasterLayer, canvas: QgsMapCanvas):
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            if isinstance(canvas, MapCanvas):
                canvas.onSetLayerProperties(lyr)
    
    
    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)):