Skip to content
Snippets Groups Projects
mapcanvas.py 52.5 KiB
Newer Older
  • Learn to ignore specific revisions
  • # -*- coding: utf-8 -*-
    """
    /***************************************************************************
    
                                  -------------------
            begin                : 2015-08-20
            git sha              : $Format:%H$
            copyright            : (C) 2017 by HU-Berlin
            email                : benjamin.jakimow@geo.hu-berlin.de
     ***************************************************************************/
    
    /***************************************************************************
     *                                                                         *
     *   This program is free software; you can redistribute it and/or modify  *
     *   it under the terms of the GNU General Public License as published by  *
     *   the Free Software Foundation; either version 2 of the License, or     *
     *   (at your option) any later version.                                   *
     *                                                                         *
     ***************************************************************************/
    """
    # noinspection PyPep8Naming
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    import os, time, types, enum
    
    from eotimeseriesviewer import CursorLocationMapTool
    
    from qgis.core import *
    from qgis.gui import *
    
    from PyQt5.QtCore import *
    from PyQt5.QtGui import *
    from PyQt5.QtWidgets import *
    
    from PyQt5.QtXml import QDomDocument
    
    from .timeseries import TimeSeriesDate, SensorProxyLayer, SensorInstrument
    
    from .externals.qps.crosshair.crosshair import CrosshairDialog, CrosshairStyle, CrosshairMapCanvasItem
    from .externals.qps.maptools import *
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    from .labeling import quickLabelLayers, labelShortcutLayerClassificationSchemes, setQuickTSDLabelsForRegisteredLayers
    
    from .externals.qps.classification.classificationscheme import ClassificationScheme, ClassInfo
    
    from .externals.qps.utils import *
    
    from .externals.qps.layerproperties import showLayerPropertiesDialog
    
    import eotimeseriesviewer.settings
    
    PROGRESS_TIMER = QTimer()
    PROGRESS_TIMER.start(100)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    def toQgsMimeDataUtilsUri(mapLayer:QgsMapLayer):
    
        uri = QgsMimeDataUtils.Uri()
        uri.name = mapLayer.name()
        uri.providerKey = mapLayer.dataProvider().name()
        uri.uri = mapLayer.source()
        if isinstance(mapLayer, QgsRasterLayer):
            uri.layerType = 'raster'
        elif isinstance(mapLayer, QgsVectorLayer):
            uri.layerType = 'vector'
        else:
            raise NotImplementedError()
        return uri
    
    
    class MapLoadingInfoItem(QgsMapCanvasItem):
    
        def __init__(self, mapCanvas):
            assert isinstance(mapCanvas, QgsMapCanvas)
            super(MapLoadingInfoItem, self).__init__(mapCanvas)
            self.mCanvas = mapCanvas
            self.mProgressConnection = None
    
            self.mCanvas.renderStarting.connect(lambda: self.showLoadingProgress(True))
            #self.mCanvas.renderComplete.connect(lambda: self.showLoadingProgress(False))
    
            PROGRESS_TIMER.timeout.connect(self.onProgressTimeOut)
            self.mShowProgress = False
            self.mIsVisible = True
    
        def showLoadingProgress(self, showProgress: bool):
            self.mShowProgress = showProgress
            self.update()
    
        def onProgressTimeOut(self):
    
            if self.mShowProgress:
                self.mCanvas.update()
    
        def paint(self, painter, QStyleOptionGraphicsItem=None, QWidget_widget=None):
                """
                Paints the crosshair
                :param painter:
                :param QStyleOptionGraphicsItem:
                :param QWidget_widget:
                :return:
                """
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                if False and self.mShowProgress:
    
    
                    if True:
                        options = QStyleOptionProgressBar()
                        options.rect = QRect(0, 0, painter.window().width(), 25)
                        options.textAlignment = Qt.AlignCenter
                        options.progress = 0
                        options.maximum = 0
                        options.minimum = 0
                        QApplication.style().drawControl(QStyle.CE_ProgressBar, options, painter)
    
    
    class MapCanvasInfoItem(QgsMapCanvasItem):
    
        def __init__(self, mapCanvas):
            assert isinstance(mapCanvas, QgsMapCanvas)
            super(MapCanvasInfoItem, self).__init__(mapCanvas)
            self.mCanvas = mapCanvas
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
            self.mText = dict()
            self.mWrapChar = '\n'
            self.mTextFormat = QgsTextFormat()
            self.mTextFormat.setSizeUnit(QgsUnitTypes.RenderPixels)
            self.mTextFormat.setFont(QFont('Helvetica', pointSize=10))
            self.mTextFormat.setColor(QColor('yellow'))
    
        def setWrapChar(self, c:str)->str:
            """
            Sets a Wrap Character
            :param c:
            :return:
            """
            self.mWrapChar = c
            return self.wrapChar()
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
        def setText(self, text:str, alignment:Qt.Alignment=Qt.AlignTop | Qt.AlignHCenter):
    
            self.mText[alignment] = text
    
    
        def setTextFormat(self, format:QgsTextFormat):
            assert isinstance(format, QgsTextFormat)
            self.mTextFormat = format
            self.updateCanvas()
    
        def textFormat(self)->QgsTextFormat:
            """
            Returns the text format.
            :return: QgsTextFormat
            """
            return self.mTextFormat
    
        def font(self)->QFont:
            """
            Returns the font used to write text on the map canvas.
            :return: QFont
            """
            return self.mTextFormat.font()
    
        def setFont(self, font:QFont):
            self.mTextFormat.setFont(font)
    
        def setColor(self, color:QColor):
    
            """
            Sets the map info color
            :param color: QColor
            """
    
    
        def color(self)->QColor:
    
            """
            Returns the info text color
            :return: QColor
            """
    
        def paintText(self, painter, text:str, flags, rotation=0):
            padding = 5
            text = text.replace('\\n', '\n')
            text = text.split(self.wrapChar())
    
            nl = len(text)
            #text = text.split('\\n')
            r = QgsTextRenderer()
    
            painter.setBrush(Qt.NoBrush)
            painter.setPen(Qt.NoPen)
            painter.setRenderHint(QPainter.Antialiasing)
    
            # taken from QGIS Repo src/core/qgspallabeling.cpp
            m2p = QgsMapToPixel(1, 0, 0, 0, 0, 0)
            context.setMapToPixel(m2p)
            context.setScaleFactor(QgsApplication.desktop().logicalDpiX() / 25.4)
            context.setUseAdvancedEffects(True)
            context.setPainter(painter)
            #context.setExtent(self.mCanvas.extent())
            #context.setExpressionContext(self.mCanvas.mapSettings().expressionContext())
    
            vp = QRectF(painter.viewport())
            #rect = self.mCanvas.extent().toRectF()
    
            textFormat = self.mTextFormat
            assert isinstance(textFormat, QgsTextFormat)
            th = r.textHeight(context, textFormat, text,  QgsTextRenderer.Rect)
            tw = r.textWidth(context, textFormat, text)
    
            # area to place the text inside
            rect = QRectF()
            x = 0.5*vp.width()
            y = 0.5*vp.height()
            hAlign = QgsTextRenderer.AlignCenter
    
            # horizontal position
            if bool(flags & Qt.AlignLeft):
                x = padding
                hAlign = QgsTextRenderer.AlignLeft
    
            elif bool(flags & Qt.AlignHCenter):
                x = 0.5 * vp.width()
                hAlign = QgsTextRenderer.AlignCenter
    
            elif bool(flags & Qt.AlignRight):
                x = vp.width() - padding
                hAlign = QgsTextRenderer.AlignRight
    
            # vertical position
            if bool(flags & Qt.AlignTop):
                y = padding + th - 0.5* (th / nl)
    
            elif bool(flags & Qt.AlignVCenter):
                y = 0.5 * (vp.height() + th)
    
            elif bool(flags & Qt.AlignBottom):
                y = vp.height() - padding #- th
    
            poo = QPointF(x, y)
            r.drawText(poo, rotation, hAlign, text, context, textFormat)
    
        def setUpperLeft(self, text:str):
            self.setText(text, Qt.AlignTop | Qt.AlignLeft)
    
        def setMiddleLeft(self, text: str):
            self.setText(text, Qt.AlignVCenter | Qt.AlignLeft)
    
        def setLowerLeft(self, text: str):
            self.setText(text, Qt.AlignBottom | Qt.AlignLeft)
    
    
        def setUpperCenter(self, text:str):
            self.setText(text, Qt.AlignTop | Qt.AlignHCenter)
    
        def setMiddleCenter(self, text: str):
            self.setText(text, Qt.AlignVCenter | Qt.AlignHCenter)
    
        def setLowerCenter(self, text: str):
            self.setText(text, Qt.AlignBottom | Qt.AlignHCenter)
    
        def setUpperRight(self, text:str):
            self.setText(text, Qt.AlignTop | Qt.AlignRight)
    
        def setMiddleRight(self, text: str):
            self.setText(text, Qt.AlignVCenter | Qt.AlignRight)
    
        def setLowerRight(self, text: str):
            self.setText(text, Qt.AlignBottom | Qt.AlignRight)
    
        def clearText(self):
            self.mText.clear()
    
    
        def paint(self, painter, QStyleOptionGraphicsItem=None, QWidget_widget=None):
                """
                Paints the crosshair
                :param painter:
                :param QStyleOptionGraphicsItem:
                :param QWidget_widget:
                :return:
                """
    
                for alignment, text in self.mText.items():
                    if isinstance(text, str) and len(text) > 0:
                        self.paintText(painter, text, alignment)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
    class MapCanvasMapTools(QObject):
    
    
        def __init__(self, canvas:QgsMapCanvas, cadDock:QgsAdvancedDigitizingDockWidget):
    
            super(MapCanvasMapTools, self).__init__(canvas)
            self.mCanvas = canvas
            self.mCadDock = cadDock
    
            self.mtZoomIn = QgsMapToolZoom(canvas, False)
            self.mtZoomOut = QgsMapToolZoom(canvas, True)
            self.mtMoveToCenter = MapToolCenter(canvas)
            self.mtPan = QgsMapToolPan(canvas)
            self.mtPixelScaleExtent = PixelScaleExtentMapTool(canvas)
            self.mtFullExtentMapTool = FullExtentMapTool(canvas)
            self.mtCursorLocation = CursorLocationMapTool(canvas, True)
    
    
            self.mtAddFeature = QgsMapToolAddFeature(canvas, QgsMapToolCapture.CaptureNone, cadDock)
    
            self.mtSelectFeature = QgsMapToolSelect(canvas)
    
    
        def activate(self, mapToolKey, **kwds):
    
            from .externals.qps.maptools import MapTools
    
            if mapToolKey == MapTools.ZoomIn:
                self.mCanvas.setMapTool(self.mtZoomIn)
            elif mapToolKey == MapTools.ZoomOut:
                self.mCanvas.setMapTool(self.mtZoomOut)
            elif mapToolKey == MapTools.Pan:
                self.mCanvas.setMapTool(self.mtPan)
            elif mapToolKey == MapTools.ZoomFull:
                self.mCanvas.setMapTool(self.mtFullExtentMapTool)
            elif mapToolKey == MapTools.ZoomPixelScale:
                self.mCanvas.setMapTool(self.mtPixelScaleExtent)
            elif mapToolKey == MapTools.CursorLocation:
                self.mCanvas.setMapTool(self.mtCursorLocation)
            elif mapToolKey == MapTools.SpectralProfile:
                pass
            elif mapToolKey == MapTools.TemporalProfile:
                pass
            elif mapToolKey == MapTools.MoveToCenter:
                self.mCanvas.setMapTool(self.mtMoveToCenter)
            elif mapToolKey == MapTools.AddFeature:
                self.mCanvas.setMapTool(self.mtAddFeature)
            elif mapToolKey == MapTools.SelectFeature:
                self.mCanvas.setMapTool(self.mtSelectFeature)
    
                self.mtSelectFeature.setSelectionMode(QgsMapToolSelectionHandler.SelectionMode.SelectSimple)
            elif mapToolKey == MapTools.SelectFeatureByPolygon:
                self.mCanvas.setMapTool(self.mtSelectFeature)
                self.mtSelectFeature.setSelectionMode(QgsMapToolSelectionHandler.SelectionMode.SelectPolygon)
            elif mapToolKey == MapTools.SelectFeatureByFreehand:
                self.mCanvas.setMapTool(self.mtSelectFeature)
                self.mtSelectFeature.setSelectionMode(QgsMapToolSelectionHandler.SelectionMode.SelectFreehand)
            elif mapToolKey == MapTools.SelectFeatureByRadius:
                self.mCanvas.setMapTool(self.mtSelectFeature)
                self.mtSelectFeature.setSelectionMode(QgsMapToolSelectionHandler.SelectionMode.SelectRadius)
    
            else:
                print('Unknown MapTool key: {}'.format(mapToolKey))
    
    
            # if undefined, set a current vector layer
            if mapToolKey in [MapTools.SelectFeature, MapTools.SelectFeatureByPolygon, MapTools.SelectFeatureByRadius, MapTools.SelectFeatureByFreehand] \
                    and self.mCanvas.currentLayer() is None:
                for vl in self.mCanvas.layers():
                    if isinstance(vl, QgsVectorLayer):
                        self.mCanvas.setCurrentLayer(vl)
                        break
    
    class MapCanvas(QgsMapCanvas):
    
        """
        A widget based on QgsMapCanvas to draw spatial data
        """
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        class Command(enum.Enum):
    
            """
            Canvas specific commands
            """
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            RefreshRenderer = 1
            Clear = 3
    
        #sigShowProfiles = pyqtSignal(SpatialPoint, str)
    
        sigSpatialExtentChanged = pyqtSignal(SpatialExtent)
    
        #sigChangeDVRequest = pyqtSignal(QgsMapCanvas, str)
        #sigChangeMVRequest = pyqtSignal(QgsMapCanvas, str)
        #sigChangeSVRequest = pyqtSignal(QgsMapCanvas, QgsRasterRenderer)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        sigMapRefreshed = pyqtSignal([float, float], [float])
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
        sigCrosshairPositionChanged = pyqtSignal(SpatialPoint)
        sigCrosshairVisibilityChanged = pyqtSignal(bool)
    
        sigDestinationCrsChanged = pyqtSignal(QgsCoordinateReferenceSystem)
    
        sigCrosshairStyleChanged = pyqtSignal(CrosshairStyle)
    
        def __init__(self, parent=None):
            super(MapCanvas, self).__init__(parent=parent)
    
            self.mMapLayerStore = QgsProject.instance()
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            self.mMapLayers = []
    
            self.mMapTools = None
            self.initMapTools()
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            self.mTimedRefreshPipeLine = dict()
    
            self.mCrosshairItem = CrosshairMapCanvasItem(self)
            self.mInfoItem = MapCanvasInfoItem(self)
    
            self.mProgressItem = MapLoadingInfoItem(self)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            self.mTSD = self.mMapView = None
    
            self.mUserInputWidget = QgsUserInputWidget(self)
            self.mUserInputWidget.setObjectName('UserInputDockWidget')
            self.mUserInputWidget.setAnchorWidget(self)
            self.mUserInputWidget.setAnchorWidgetPoint(QgsFloatingWidget.TopRight)
            self.mUserInputWidget.setAnchorPoint(QgsFloatingWidget.TopRight)
    
            #self.mProgressBar = QProgressBar()
            #self.mUserInputWidget.addUserInputWidget(self.mProgressBar)
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            self.mIsRefreshing = False
    
            self.mRenderingFinished = True
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            self.mRefreshStartTime = time.time()
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            def onMapCanvasRefreshed(*args):
                self.mIsRefreshing = False
    
                self.mRenderingFinished = True
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                self.mIsRefreshing = False
                t2 = time.time()
                dt = t2 - self.mRefreshStartTime
    
                self.sigMapRefreshed[float].emit(dt)
                self.sigMapRefreshed[float, float].emit(self.mRefreshStartTime, t2)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            self.mapCanvasRefreshed.connect(onMapCanvasRefreshed)
    
            self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
    
            bg = eotimeseriesviewer.settings.value(eotimeseriesviewer.settings.Keys.MapBackgroundColor, default=QColor(0, 0, 0))
    
            self.setCanvasColor(bg)
    
            self.setContextMenuPolicy(Qt.DefaultContextMenu)
    
    
            self.extentsChanged.connect(lambda : self.sigSpatialExtentChanged.emit(self.spatialExtent()))
    
            self.destinationCrsChanged.connect(lambda : self.sigDestinationCrsChanged.emit(self.crs()))
    
    
        def userInputWidget(self)->QgsUserInputWidget:
            """
            Returns the mapcanvas QgsUserInputWidget
            :return: QgsUserInputWidget
            """
            return self.mUserInputWidget
    
    
    
        def infoItem(self)->MapCanvasInfoItem:
            """
            Returns the MapCanvasInfoItem, e.g. to plot text on top of the map canvas
            :return: MapCanvasInfoItem
            """
            return self.mInfoItem
    
    
        def mapView(self):
            """
            Returns the MapView this MapCanvas is linked to
            :return:
            """
            return self.mMapView
    
    
        def mapTools(self)->MapCanvasMapTools:
    
            """
            Returns the map tools of this MapCanvas
            :return: MapCanvasMapTools
            """
    
            return self.mMapTools
    
        def initMapTools(self):
    
            self.mCadDock = QgsAdvancedDigitizingDockWidget(self)
            self.mCadDock.setVisible(False)
            self.mMapTools = MapCanvasMapTools(self, self.mCadDock)
    
        def setMapLayerStore(self, store):
            """
            Sets the QgsMapLayerStore or QgsProject instance that is used to register map layers
            :param store: QgsMapLayerStore | QgsProject
            """
            assert isinstance(store, (QgsMapLayerStore, QgsProject))
            self.mMapLayerStore = store
    
        def renderingFinished(self)->bool:
            """
            Returns whether the MapCanvas is processing a rendering task
            :return: bool
            """
            return self.mRenderingFinished
    
    
        def mousePressEvent(self, event:QMouseEvent):
    
            b = event.button() == Qt.LeftButton
            if b and isinstance(self.mapTool(), QgsMapTool):
                b = isinstance(self.mapTool(), (QgsMapToolIdentify,
                                                CursorLocationMapTool,
                                                SpectralProfileMapTool, TemporalProfileMapTool))
    
            super(MapCanvas, self).mousePressEvent(event)
    
            if b:
                ms = self.mapSettings()
                pointXY = ms.mapToPixel().toMapCoordinates(event.x(), event.y())
                spatialPoint = SpatialPoint(ms.destinationCrs(), pointXY)
                self.setCrosshairPosition(spatialPoint)
    
    
        def setMapView(self, mapView):
    
            """
            Sets the map canvas MapView
            :param mapView: MapView
            """
    
            from eotimeseriesviewer.mapvisualization import MapView
    
    
            assert isinstance(mapView, MapView)
    
            self.mMapView = mapView
    
            self.mInfoItem.setTextFormat(mapView.mapTextFormat())
    
            self.addToRefreshPipeLine(mapView.mapBackgroundColor())
            self.addToRefreshPipeLine(MapCanvas.Command.UpdateMapItems)
    
        def setTSD(self, tsd:TimeSeriesDate):
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            """
    
            Sets the TimeSeriesDate this map-canvas is linked to
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            :param tsd:
            :return:
            """
    
            # disconnect old TSD
            if isinstance(self.mTSD, TimeSeriesDate):
                self.mTSD.sensor().sigNameChanged.disconnect(self.updateScope)
    
    
            if isinstance(tsd, TimeSeriesDate):
                self.mTSD.sensor().sigNameChanged.connect(self.updateScope)
    
    
        def updateScope(self):
            """
            Updates map-canvas TSD variables
            """
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            from .mapvisualization import MapView
            from .main import TimeSeriesViewer
    
    
            varMVNumber = None
            varMVName = None
            varDate = None
            varDOY = None
            varSensor = None
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
            tsd = self.tsd()
            if isinstance(tsd, TimeSeriesDate):
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                varDate = str(tsd.date())
                varDOY = tsd.doy()
                varSensor = tsd.sensor().name()
    
            mv = self.mapView()
            if isinstance(mv, MapView):
                varMVName = mv.name()
                if isinstance(TimeSeriesViewer.instance(), TimeSeriesViewer):
                    mapViews = TimeSeriesViewer.instance().mapViews()
                    if mv in mapViews:
                        varMVNumber = mapViews.index(mv) + 1
    
            scope = self.expressionContextScope()
            scope.setVariable('map_view_num', varMVNumber, isStatic=False)
            scope.setVariable('map_view', varMVName, isStatic=False)
            scope.setVariable('map_date', varDate, isStatic=False)
            scope.setVariable('map_doy', varDOY, isStatic=False)
            scope.setVariable('map_sensor', varSensor, isStatic=False)
    
    
        def tsd(self)->TimeSeriesDate:
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            """
    
            Returns the TimeSeriesDate
            :return: TimeSeriesDate
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            """
            return self.mTSD
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def setSpatialExtent(self, extent:SpatialExtent):
            """
            Sets the spatial extent
            :param extent: SpatialExtent
            """
            assert isinstance(extent, SpatialExtent)
            extent = extent.toCrs(self.crs())
            self.setExtent(extent)
    
        def setSpatialCenter(self, center:SpatialPoint):
            """
            Sets the SpatialCenter
            :param center: SpatialPoint
            """
            assert isinstance(center, SpatialPoint)
            center = center.toCrs(self.crs())
            self.setCenter(center)
    
        def setFixedSize(self, size:QSize):
            """
            Changes the map-canvas size
            :param size: QSize
            """
    
            assert isinstance(size, QSize)
            if self.size() != size:
    
                super(MapCanvas, self).setFixedSize(size)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def setCrs(self, crs:QgsCoordinateReferenceSystem):
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            """
            Sets the
            :param crs:
            :return:
            """
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            assert isinstance(crs, QgsCoordinateReferenceSystem)
    
            if self.crs() != crs:
                self.setDestinationCrs(crs)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def crs(self)->QgsCoordinateReferenceSystem:
    
            """
            Shortcut to return self.mapSettings().destinationCrs()
            :return: QgsCoordinateReferenceSystem
            """
    
            return self.mapSettings().destinationCrs()
    
    
            """
            Set the map layers and, if necessary, registers the in a QgsMapLayerStore
            :param mapLayers:
            """
    
            self.mMapLayerStore.addMapLayers(mapLayers)
            super(MapCanvas, self).setLayers(mapLayers)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def isRefreshing(self)->bool:
            return self.mIsRefreshing
    
    
        def isVisibleToViewport(self)->bool:
    
            """
            Returns whether the MapCanvas is visible to a user and not hidden behind the invisible regions of a scroll area.
            :return: bool
            """
    
            return self.visibleRegion().boundingRect().isValid()
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
        def addToRefreshPipeLine(self, arguments: list):
            """
            Adds commands or other arguments to a pipeline which will be handled during the next timed refresh.
            :param arguments: argument | [list-of-arguments]
            """
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            if not isinstance(arguments, list):
                arguments = [arguments]
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            for a in arguments:
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
                if isinstance(a, SpatialExtent):
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                    self.mTimedRefreshPipeLine[SpatialExtent] = a
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                elif isinstance(a, SpatialPoint):
    
                    self.mTimedRefreshPipeLine[SpatialPoint] = a
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
                elif isinstance(a, QColor):
                    self.mTimedRefreshPipeLine[QColor] = a
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                elif isinstance(a, MapCanvas.Command):
                    if not MapCanvas.Command in self.mTimedRefreshPipeLine.keys():
                        self.mTimedRefreshPipeLine[MapCanvas.Command] = []
    
                    # remove previous commands of same type, append command to end
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                    while a in self.mTimedRefreshPipeLine[MapCanvas.Command]:
                        self.mTimedRefreshPipeLine[MapCanvas.Command].remove(a)
                    self.mTimedRefreshPipeLine[MapCanvas.Command].append(a)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                else:
    
                    print('Unsupported argument: {} {}'.format(type(a), str(a)), file=sys.stderr)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            Called to refresh the map canvas with all things needed to be done with lazy evaluation
    
            expected = []
    
            existing = self.layers()
            existingSources = [l.source() for l in existing]
    
    
            if self.mapView() is None or self.tsd() is None:
                self.setLayers([])
                self.mInfoItem.clearText()
                self.update()
    
            for lyr in self.mMapView.layers():
                assert isinstance(lyr, QgsMapLayer)
    
                if isinstance(lyr, SensorProxyLayer):
                    if self.tsd().sensor() == lyr.sensor():
                        for source in self.tsd().sourceUris():
                            sourceLayer = None
    
                            if source in existingSources:
                                sourceLayer = existing[existingSources.index(source)]
                            else:
    
                                loptions = QgsRasterLayer.LayerOptions(loadDefaultStyle=False)
                                sourceLayer = SensorProxyLayer(source, sensor=self.tsd().sensor(), options=loptions)
    
                                sourceLayer.setName(lyr.name())
                                sourceLayer.setCustomProperty('eotsv/sensorid', self.tsd().sensor().id())
    
                                try:
                                    renderer = lyr.renderer()
                                    if isinstance(renderer, QgsRasterRenderer):
                                        sourceLayer.setRenderer(renderer.clone())
                                except Exception as exR:
                                    s = ""
    
                            assert isinstance(sourceLayer, QgsRasterLayer)
                            expected.append(sourceLayer)
                    else:
                        # skip any other SensorProxyLayer that relates to another sensor
                        pass
                else:
                    expected.append(lyr)
    
            if len(self.mTimedRefreshPipeLine) == 0 and self.layers() == expected:
                # there is nothing to do.
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                return
            else:
                self.freeze(True)
    
                # look for new layers
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
                lyrs = self.layers()
                if lyrs != expected:
                    self.setLayers(expected)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
                if True:
                    # set sources first
                    keys = self.mTimedRefreshPipeLine.keys()
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
                    if QgsCoordinateReferenceSystem in keys:
                        self.setDestinationCrs(self.mTimedRefreshPipeLine[QgsCoordinateReferenceSystem])
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
                    if SpatialExtent in keys:
                        self.setSpatialExtent(self.mTimedRefreshPipeLine[SpatialExtent])
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
                    if SpatialPoint in keys:
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                        self.setSpatialCenter(self.mTimedRefreshPipeLine[SpatialPoint])
    
                    if QColor in keys:
                        self.setCanvasColor(self.mTimedRefreshPipeLine[QColor])
    
                    if MapCanvas.Command in keys:
                        commands = self.mTimedRefreshPipeLine[MapCanvas.Command]
                        for command in commands:
                            assert isinstance(command, MapCanvas.Command)
                            if command == MapCanvas.Command.RefreshRenderer:
                                for px in [px for px in self.mMapView.layers() if isinstance(px, SensorProxyLayer)]:
                                    for l in self.layers():
                                        if isinstance(l, SensorProxyLayer) and l.sensor() == px.sensor():
    
                                            try:
                                                renderer = px.renderer().clone()
                                                renderer.setInput(l.dataProvider())
                                                l.setRenderer(renderer)
                                            except Exception as ex:
                                                s = ""
    
                            if command == MapCanvas.Command.UpdateMapItems:
                                #self.update()
    
                    self.mTimedRefreshPipeLine.clear()
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                self.freeze(False)
                self.refresh()
    
                # is this really required?
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def setLayerVisibility(self, cls, isVisible:bool):
            """
            :param cls: type of layer, e.g. QgsRasterLayer to set visibility of all layers of same type
                        QgsMapLayer instance to the visibility of a specific layer
            :param isVisible: bool
            """
            self.mMapLayerModel.setLayerVisibility(cls, isVisible)
            self.addToRefreshPipeLine(MapCanvas.Command.RefreshVisibility)
    
        def setCrosshairStyle(self, crosshairStyle:CrosshairStyle, emitSignal=True):
            """
            Sets the CrosshairStyle
            :param crosshairStyle: CrosshairStyle
            :param emitSignal: Set to Fals to no emit a signal.
            """
    
            from eotimeseriesviewer import CrosshairStyle
    
            if crosshairStyle is None:
    
                self.mCrosshairItem.crosshairStyle.setShow(False)
                self.mCrosshairItem.update()
    
                assert isinstance(crosshairStyle, CrosshairStyle)
    
                self.mCrosshairItem.setCrosshairStyle(crosshairStyle)
    
                self.sigCrosshairStyleChanged.emit(self.mCrosshairItem.crosshairStyle())
    
            """
            Returns the style of the Crosshair.
            :return: CrosshairStyle
            """
    
        def setCrosshairPosition(self, spatialPoint:SpatialPoint):
    
            """
            Sets the position of the Crosshair.
            :param spatialPoint: SpatialPoint
            :param emitSignal: True (default). Set False to avoid emitting sigCrosshairPositionChanged
            :return:
            """
    
            point = spatialPoint.toCrs(self.mapSettings().destinationCrs())
    
            if self.mCrosshairItem.mPosition != point:
                self.mCrosshairItem.setPosition(point)
    
                self.sigCrosshairPositionChanged.emit(point)
    
        def crosshairPosition(self)->SpatialPoint:
            """Returns the last crosshair position"""
            return self.mCrosshairItem.mPosition
    
    
        def setCrosshairVisibility(self, b:bool, emitSignal=True):
            """
            Sets the Crosshair visbility
            :param b: bool
            """
            if b and self.mCrosshairItem.mPosition is None:
                self.mCrosshairItem.setPosition(self.spatialCenter())
                self.sigCrosshairPositionChanged.emit(self.spatialCenter())
    
            if b != self.mCrosshairItem.visibility():
                self.mCrosshairItem.setVisibility(b)
                if emitSignal:
                    self.sigCrosshairVisibilityChanged.emit(b)
    
        def pixmap(self):
            """
            Returns the current map image as pixmap
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            :return: QPixmap
    
        def contextMenu(self, pos:QPoint)->QMenu:
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            Creates the MapCanvas context menu with options relevant for pixel position ``pos``.
    
            :param pos: QPoint
            :return: QMenu
    
            mapSettings = self.mapSettings()
            assert isinstance(mapSettings, QgsMapSettings)
    
            pointGeo = mapSettings.mapToPixel().toMapCoordinates(pos.x(), pos.y())
            assert isinstance(pointGeo, QgsPointXY)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
            tsd = self.tsd()
    
    
            from .main import TimeSeriesViewer
            eotsv = TimeSeriesViewer.instance()
    
            viewPortMapLayers = [l for l in self.layers() if isinstance(l, QgsMapLayer)]
    
            viewPortRasterLayers = [l for l in viewPortMapLayers if isinstance(l, QgsRasterLayer) and SpatialExtent.fromLayer(l).toCrs(self.crs()).contains(pointGeo)]
            viewPortSensorLayers = [l for l in viewPortRasterLayers if isinstance(l, SensorProxyLayer)]
            viewPortVectorLayers = [l for l in viewPortMapLayers if isinstance(l, QgsVectorLayer)]
    
            refSensorLayer = None
            refRasterLayer = None
    
            if len(viewPortRasterLayers) > 0:
                refRasterLayer = viewPortRasterLayers[0]
            if len(viewPortSensorLayers) > 0:
                refSensorLayer = viewPortSensorLayers[0]
    
            if isinstance(self.tsd(), TimeSeriesDate):
    
                tss = None
                sourceUris = self.tsd().sourceUris()
                for sl in viewPortSensorLayers:
                    if sl.source() in sourceUris:
                        tss = self.tsd()[sourceUris.index(sl.source())]
                        break
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                lyrWithSelectedFeatures = [l for l in quickLabelLayers() if l.isEditable() and l.selectedFeatureCount() > 0]
    
                layerNames = ', '.join([l.name() for l in lyrWithSelectedFeatures])
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                m = menu.addMenu('Quick Labels')
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                m.setToolTipsVisible(True)
    
                nQuickLabelLayers = len(lyrWithSelectedFeatures)
                m.setEnabled(nQuickLabelLayers > 0)
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                a = m.addAction('Set Date/Sensor attributes')
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                a.setToolTip('Writes the dates and sensor quick labels of selected features in {}.'.format(layerNames))
    
                a.triggered.connect(lambda *args, tsd = self.tsd(), tss=tss: setQuickTSDLabelsForRegisteredLayers(tsd, tss))
    
    
                from .labeling import CONFKEY_CLASSIFICATIONSCHEME, layerClassSchemes, setQuickClassInfo
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                if len(lyrWithSelectedFeatures) == 0:
                    a = m.addAction('No features selected.')
                    a.setToolTip('Select feature in the labeling panel to apply Quick label value on.')
                    a.setEnabled(False)
                else:
                    for layer in lyrWithSelectedFeatures:
                        assert isinstance(layer, QgsVectorLayer)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                        csf = layerClassSchemes(layer)
                        if len(csf) > 0:
                            m.addSection(layer.name())
                            for (cs, field) in csf:
                                assert isinstance(cs, ClassificationScheme)
                                assert isinstance(field, QgsField)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                                classMenu = m.addMenu('"{}" ({})'.format(field.name(), field.typeName()))
                                for classInfo in cs:
                                    assert isinstance(classInfo, ClassInfo)
                                    a = classMenu.addAction('{} "{}"'.format(classInfo.label(), classInfo.name()))
                                    a.setIcon(classInfo.icon())
                                    a.triggered.connect(lambda _, vl=layer, f=field, c=classInfo: setQuickClassInfo(vl, f, c))
    
            if isinstance(refSensorLayer, SensorProxyLayer):
                m = menu.addMenu('Raster stretch...')
                action = m.addAction('Linear')
                action.triggered.connect(lambda *args, lyr=refSensorLayer: self.stretchToExtent(self.spatialExtent(), 'linear_minmax', layer=lyr, p=0.0))
    
                action = m.addAction('Linear 5%')
                action.triggered.connect(lambda *args, lyr=refSensorLayer: self.stretchToExtent(self.spatialExtent(), 'linear_minmax', layer=lyr, p=0.05))
    
                action = m.addAction('Gaussian')
                action.triggered.connect(lambda *args, lyr=refSensorLayer: self.stretchToExtent(self.spatialExtent(), 'gaussian', layer=lyr, n=3))
    
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
            menu.addSeparator()
    
            from .externals.qps.layerproperties import pasteStyleFromClipboard, pasteStyleToClipboard
    
            b = isinstance(refRasterLayer, QgsRasterLayer)
            a = menu.addAction('Copy Style')
            a.setEnabled(b)
            a.setToolTip('Copy the current layer style to clipboard')
            a.triggered.connect(lambda *args, lyr=refRasterLayer: pasteStyleToClipboard(lyr))
    
            a = menu.addAction('Paste Style')
            a.setEnabled(b)
            a.setEnabled('application/qgis.style' in QApplication.clipboard().mimeData().formats())
            a.triggered.connect(lambda *args, lyr=refRasterLayer: self.onPasteStyleFromClipboard(lyr))
    
            menu.addSeparator()
    
            m = menu.addMenu('Layers...')
    
            visibleLayers = viewPortRasterLayers + viewPortVectorLayers
    
    
            for mapLayer in visibleLayers:
                #sub = m.addMenu(mapLayer.name())
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                if isinstance(mapLayer, SensorProxyLayer):
                    name = os.path.basename(mapLayer.source())
                else:
                    name = mapLayer.name()
                sub = m.addMenu(name)
    
    
                if isinstance(mapLayer, SensorProxyLayer):
                    sub.setIcon(QIcon(':/timeseriesviewer/icons/icon.svg'))
                elif isinstance(mapLayer, QgsRasterLayer):
                    sub.setIcon(QIcon(''))
                elif isinstance(mapLayer, QgsVectorLayer):
                    wkbType = QgsWkbTypes.displayString(int(mapLayer.wkbType()))
                    if re.search('polygon', wkbType, re.I):
                        sub.setIcon(QIcon(r':/images/themes/default/mIconPolygonLayer.svg'))
                    elif re.search('line', wkbType, re.I):
                        sub.setIcon(QIcon(r':/images/themes/default/mIconLineLayer.svg'))
                    elif re.search('point', wkbType, re.I):
                        sub.setIcon(QIcon(r':/images/themes/default/mIconPointLayer.svg'))
    
                a = sub.addAction('Properties...')
    
                a.triggered.connect(lambda *args,
    
                                           lyr = mapLayer,
                                           c = self,
    
                                           #b = isinstance(mapLayer, SensorProxyLayer) == False:
                                           b = True:
    
                                    showLayerPropertiesDialog(lyr, c, useQGISDialog=b))
    
    
                a = sub.addAction('Zoom to Layer')
                a.setIcon(QIcon(':/images/themes/default/mActionZoomToLayer.svg'))
                a.triggered.connect(lambda *args, lyr=mapLayer: self.setSpatialExtent(SpatialExtent.fromLayer(lyr)))
    
    
                a = sub.addAction('Copy Style')
                a.setToolTip('Copy layer style to clipboard')
                a.triggered.connect(lambda *args, lyr=mapLayer: pasteStyleToClipboard(lyr))
    
                a = sub.addAction('Paste Style')
                a.setToolTip('Paster layer style from clipboard')
                a.setEnabled('application/qgis.style' in QApplication.clipboard().mimeData().formats())
                a.triggered.connect(lambda *args, lyr=mapLayer: self.onPasteStyleFromClipboard(lyr))
    
    
            menu.addSeparator()