Skip to content
Snippets Groups Projects
mapcanvas.py 52.5 KiB
Newer Older
# -*- 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()