Skip to content
Snippets Groups Projects
mapcanvas.py 17.7 KiB
Newer Older
import os, logging

logger = logging.getLogger(__name__)

from qgis.core import *
from qgis.gui import *
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from timeseriesviewer import SETTINGS
from timeseriesviewer.utils import *
from timeseriesviewer.ui.widgets import TsvScrollArea

Benjamin Jakimow's avatar
Benjamin Jakimow committed


class MapCanvas(QgsMapCanvas):
    from timeseriesviewer.main import SpatialExtent, SpatialPoint
    sigShowProfiles = pyqtSignal(SpatialPoint)
    sigSpatialExtentChanged = pyqtSignal(SpatialExtent)
    sigChangeDVRequest = pyqtSignal(QgsMapCanvas, str)
    sigChangeMVRequest = pyqtSignal(QgsMapCanvas, str)
    sigChangeSVRequest = pyqtSignal(QgsMapCanvas, QgsRasterRenderer)
    def __init__(self, parent=None):
        super(MapCanvas, self).__init__(parent=parent)
        from timeseriesviewer.mapvisualization import DatumView, MapView, SpatialTemporalVisualization

        #the canvas
        self.setCrsTransformEnabled(True)
        self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
        self.setCanvasColor(SETTINGS.value('CANVAS_BACKGROUND_COLOR', QColor(0, 0, 0)))
        self.setContextMenuPolicy(Qt.DefaultContextMenu)

        self.extentsChanged.connect(lambda : self.sigSpatialExtentChanged.emit(self.spatialExtent()))
        #self.tsdView = tsdView
        #self.referenceLayer = QgsRasterLayer(self.tsdView.timeSeriesDatum.pathImg)
        self.mLayers = []
        #self.mapView = mapView
        #self.spatTempVis = mapView.spatTempVis
        #assert isinstance(self.spatTempVis, SpatialTemporalVisualization)
        #self.sigSpatialExtentChanged.connect(self.spatTempVis.setSpatialExtent)
        from timeseriesviewer.crosshair import CrosshairMapCanvasItem
        self.crosshairItem = CrosshairMapCanvasItem(self)

        self.mMapTools = dict()
        self.mMapTools['zoomOut'] = QgsMapToolZoom(self, True)
        self.mMapTools['zoomIn'] = QgsMapToolZoom(self, False)
        self.mMapTools['pan'] = QgsMapToolPan(self)
        from timeseriesviewer.maptools import CursorLocationMapTool
        mt = CursorLocationMapTool(self)
        mt.sigLocationRequest.connect(self.sigShowProfiles.emit)
        self.mMapTools['identifyProfile'] = mt
        mt = CursorLocationMapTool(self)
        mt.sigLocationRequest.connect(lambda pt: self.setCenter(pt))
        self.mMapTools['moveCenter'] = mt

    def setFixedSize(self, size):
        assert isinstance(size, QSize)
        if self.size() != size:
            super(MapCanvas, self).setFixedSize(size)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def setCrs(self, crs):
        assert isinstance(crs, QgsCoordinateReferenceSystem)
        if self.crs() != crs:
            self.setDestinationCrs(crs)
Benjamin Jakimow's avatar
Benjamin Jakimow committed

    def crs(self):
        return self.mapSettings().destinationCrs()


    def _depr_onExtentsChanged(self, *args):
        if not self.mBlockExtentsChangedSignal:
            self.mBlockExtentsChangedSignal = True
            print('set STV extent')
            self.spatTempVis.setSpatialExtent(self.spatialExtent())
            self.mBlockExtentsChangedSignal = False

    def mapLayersToRender(self, *args):
        """Returns the map layers actually to be rendered"""
        return self.mLayers
        mapLayers = []
        self.mLayers
        if self.mapView.visibleVectorOverlay():
            #if necessary, register new vector layer
            refLyr = self.mapView.vectorLayer
            refUri = refLyr.dataProvider().dataSourceUri()

            if self.vectorLayer is None or self.vectorLayer.dataProvider().dataSourceUri() != refUri:
                providerKey = refLyr.dataProvider().name()
                baseName = os.path.basename(refUri)
                self.vectorLayer = QgsVectorLayer(refUri, baseName, providerKey)

            #update layer style
            self.vectorLayer.setRendererV2(refLyr.rendererV2().clone())
            mapLayers.append(self.vectorLayer)

        if self.referenceLayer:
            mapLayers.append(self.referenceLayer)

        return mapLayers



    def setLayerSet(self, *args):
        raise DeprecationWarning()

    def setLayers(self, mapLayers):
        reg = QgsMapLayerRegistry.instance()
        for l in mapLayers:
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            reg.addMapLayer(l, False)
        self.mLayers = mapLayers[:]
        super(MapCanvas, self).setLayerSet([QgsMapCanvasLayer(l) for l in self.mLayers])
        s = ""
    def refresh(self):
        self.setLayers(self.mapLayersToRender())
        self.setRenderMe()
        super(MapCanvas, self).refresh()
        self.refreshAllLayers()
    def setCrosshairStyle(self,crosshairStyle):
        from timeseriesviewer.crosshair import CrosshairStyle
        if crosshairStyle is None:
            self.crosshairItem.crosshairStyle.setShow(False)
            self.crosshairItem.update()
            assert isinstance(crosshairStyle, CrosshairStyle)
            self.crosshairItem.setCrosshairStyle(crosshairStyle)

    def setShowCrosshair(self,b):
        self.crosshairItem.setShow(b)

    def setRenderMe(self):
        oldFlag = self.renderFlag()

        newFlag = self.visibleRegion().boundingRect().isValid() \
                  and self.isVisible()
                  #and self.tsdView.timeSeriesDatum.isVisible()
        if oldFlag != newFlag:
            self.setRenderFlag(newFlag)

    def pixmap(self):
        """
        Returns the current map image as pixmap
        :return:
        """
        return QPixmap(self.map().contentImage().copy())

    def contextMenuEvent(self, event):
        menu = QMenu()
        # add general options
        menu.addSeparator()
        action = menu.addAction('Stretch using current Extent')
        action.triggered.connect(self.stretchToCurrentExtent)
        action = menu.addAction('Zoom to Layer')
        action.triggered.connect(lambda : self.setSpatialExtent(self.spatialExtentHint()))
        action = menu.addAction('Change crosshair style')
        from timeseriesviewer.crosshair import CrosshairDialog
        action.triggered.connect(lambda : self.setCrosshairStyle(
                CrosshairDialog.getCrosshairStyle(parent=self,
                                                mapCanvas=self,
                                                crosshairStyle=self.crosshairItem.crosshairStyle)
                ))

        if self.crosshairItem.crosshairStyle.mShow:
            action = menu.addAction('Hide crosshair')
            action.triggered.connect(lambda : self.setShowCrosshair(False))
        else:
            action = menu.addAction('Show crosshair')
            action.triggered.connect(lambda: self.setShowCrosshair(True))

        menu.addSeparator()

        m = menu.addMenu('Copy...')
        action = m.addAction('Map to Clipboard')
        action.triggered.connect(lambda: QApplication.clipboard().setPixmap(self.pixmap()))
        action = m.addAction('Image Path')
        action.triggered.connect(lambda: QApplication.clipboard().setText(self.tsdView.TSD.pathImg))
        action = m.addAction('Image Style')
        #action.triggered.connect(lambda: QApplication.clipboard().setPixmap(self.tsdView.TSD.pathImg))

        m = menu.addMenu('Save as...')
        action = m.addAction('PNG')
        action.triggered.connect(lambda : self.saveMapImageDialog('PNG'))
        action = m.addAction('JPEG')
        action.triggered.connect(lambda: self.saveMapImageDialog('JPG'))

        from timeseriesviewer.main import QgisTsvBridge
        bridge = QgisTsvBridge.instance()
        if bridge:
            assert isinstance(bridge, QgisTsvBridge)
            action = m.addAction('Add layer to QGIS')
            action = m.addAction('Import extent from QGIS')
            action = m.addAction('Export extent to QGIS')
            s = ""




        menu.addSeparator()
        action = menu.addAction('Hide date')
        action.triggered.connect(lambda : self.sigChangeDVRequest.emit(self, 'hide_date'))
        action = menu.addAction('Remove date')
        action.triggered.connect(lambda: self.sigChangeDVRequest.emit(self, 'remove_date'))
        action = menu.addAction('Remove map view')
        action.triggered.connect(lambda: self.sigChangeMVRequest.emit(self, 'remove_mapview'))
        action = menu.addAction('Hide map view')
        action.triggered.connect(lambda: self.sigChangeMVRequest.emit(self, 'hide_mapview'))


        menu.exec_(event.globalPos())

    def stretchToCurrentExtent(self):
        ceAlg = QgsContrastEnhancement.StretchToMinimumMaximum
        for l in self.layers():
            if isinstance(l, QgsRasterLayer):
                r = l.renderer()
                dp = l.dataProvider()
                newRenderer = None
                extent = se.toCrs(l.crs())

                assert isinstance(dp, QgsRasterDataProvider)
                bands = None
                if isinstance(r, QgsMultiBandColorRenderer):

                    def getCE(band, ce):
                        stats = dp.bandStatistics(band, QgsRasterBandStats.All, extent, 500)
                        if stats.maximumValue is None:
                            s = ""
                        ce = QgsContrastEnhancement(dp.dataType(band))
                        ce.setContrastEnhancementAlgorithm(ceAlg)
                        ce.setMinimumValue(stats.minimumValue)
                        ce.setMaximumValue(stats.maximumValue)
                        return ce

                    newRenderer = QgsMultiBandColorRenderer(None,r.redBand(), r.greenBand(), r.blueBand())
                    newRenderer.setRedContrastEnhancement(getCE(r.redBand(), r.redContrastEnhancement()))
                    newRenderer.setGreenContrastEnhancement(getCE(r.greenBand(), r.greenContrastEnhancement()))
                    newRenderer.setBlueContrastEnhancement(getCE(r.blueBand(), r.blueContrastEnhancement()))

                elif isinstance(r, QgsSingleBandPseudoColorRenderer):
                    newRenderer = r.clone()
                    stats = dp.bandStatistics(newRenderer.band(), QgsRasterBandStats.All, extent, 500)
                    shader = newRenderer.shader()
                    newRenderer.setClassificationMax(stats.maximumValue)
                    newRenderer.setClassificationMin(stats.minimumValue)
                    shader.setMaximumValue(stats.maximumValue)
                    shader.setMinimumValue(stats.minimumValue)
                    s = ""

                if newRenderer is not None:
                    self.sigChangeSVRequest.emit(self, newRenderer)
        s = ""

    def activateMapTool(self, key):
        if key is None:
            self.setMapTool(None)
        elif key in self.mMapTools.keys():
            super(MapCanvas, self).setMapTool(self.mMapTools[key])
            logger.error('unknown map tool key "{}"'.format(key))

    def saveMapImageDialog(self, fileType):
        lastDir = SETTINGS.value('CANVAS_SAVE_IMG_DIR', os.path.expanduser('~'))
        path = jp(lastDir, '{}.{}.{}'.format(self.tsdView.TSD.date, self.mapView.title(), fileType.lower()))

        path = QFileDialog.getSaveFileName(self, 'Save map as {}'.format(fileType), path)
        if len(path) > 0:
            self.saveAsImage(path, None, fileType)
            SETTINGS.setValue('CANVAS_SAVE_IMG_DIR', os.path.dirname(path))


    def setRenderer(self, renderer):

        #lyrs = [l for l in self.mapLayersToRender() if str(l.source()) == targetLayerUri]
        isRasterRenderer = isinstance(renderer, QgsRasterRenderer)
        if isRasterRenderer:
            lyrs = [l for l in self.mLayers if isinstance(l, QgsRasterLayer)]
            for lyr in lyrs:
                if isinstance(renderer, QgsMultiBandColorRenderer):
                    r = renderer.clone()
                    r.setInput(lyr.dataProvider())
                elif isinstance(renderer, QgsSingleBandPseudoColorRenderer):
                    r = renderer.clone()
                    #r = QgsSingleBandPseudoColorRenderer(None, renderer.band(), None)
                    r.setInput(lyr.dataProvider())
                    cmin = renderer.classificationMin()
                    cmax = renderer.classificationMax()
                    r.setClassificationMin(cmin)
                    r.setClassificationMax(cmax)
                    #r.setShader(renderer.shader())
                    s = ""
                else:
                    raise NotImplementedError()
                lyr.setRenderer(r)
        else:
            lyrs = [l for l in self.mLayers if isinstance(l, QgsVectorLayer)]
            for lyr in lyrs:
                lyr.setRenderer(renderer)

        if not self.signalsBlocked():
            self.refresh()

    def setSpatialExtent(self, spatialExtent):
        assert isinstance(spatialExtent, SpatialExtent)
        if self.spatialExtent() != spatialExtent:
            spatialExtent = spatialExtent.toCrs(self.crs())
            if spatialExtent:
                self.setExtent(spatialExtent)

            if not self.signalsBlocked():
                self.refresh()


    def spatialExtent(self):
        return SpatialExtent.fromMapCanvas(self)

    def spatialExtentHint(self):
        crs = self.crs()
        ext = SpatialExtent.world()
        for lyr in self.mLayers + self.layers():
            ext = SpatialExtent.fromLayer(lyr).toCrs(crs)
            break
        return ext


class CanvasBoundingBoxItem(QgsGeometryRubberBand):

    def __init__(self, mapCanvas):
        assert isinstance(mapCanvas, QgsMapCanvas)
        super(CanvasBoundingBoxItem, self).__init__(mapCanvas)

        self.canvas = mapCanvas
        self.mCanvasExtents = dict()
        self.mShow = True
        self.mShowTitles = True
        self.setIconType(QgsGeometryRubberBand.ICON_NONE)

    def connectCanvas(self, canvas):
        assert isinstance(canvas, QgsMapCanvas)
        assert canvas != self.canvas
        if canvas not in self.mCanvasExtents.keys():
            self.mCanvasExtents[canvas] = None
            canvas.extentsChanged.connect(lambda : self.onExtentsChanged(canvas))
            canvas.destroyed.connect(lambda : self.disconnectCanvas(canvas))
            self.onExtentsChanged(canvas)

    def disconnectCanvas(self, canvas):
            self.mCanvasExtents.pop(canvas)

    def onExtentsChanged(self, canvas):
        assert isinstance(canvas, QgsMapCanvas)

        ext = SpatialExtent.fromMapCanvas(canvas)
        ext = ext.toCrs(self.canvas.mapSettings().destinationCrs())

        geom = QgsPolygonV2()
        assert geom.fromWkt(ext.asWktPolygon())

        self.mCanvasExtents[canvas] = (ext, geom)
        self.refreshExtents()

    def refreshExtents(self):
        multi = QgsMultiPolygonV2()
        if self.mShow:
            for canvas, t in self.mCanvasExtents.items():
                ext, geom = t
                multi.addGeometry(geom.clone())
        self.setGeometry(multi)

    def paint(self, painter, QStyleOptionGraphicsItem=None, QWidget_widget=None):
        super(CanvasBoundingBoxItem, self).paint(painter)

        if self.mShowTitles and self.mShow:
            painter.setPen(Qt.blue);
            painter.setFont(QFont("Arial", 30))

            for canvas, t in self.mCanvasExtents.items():
                ext, geom = t
                ULpx = self.toCanvasCoordinates(ext.center())
                txt = canvas.windowTitle()
                painter.drawLine(0, 0, 200, 200);
                painter.drawText(ULpx,  txt)


    def setShow(self, b):
        assert isinstance(b, bool)
        self.mShow = b

    def setShowTitles(self, b):
        assert isinstance(b, bool)
        self.mShowTitles = b


def exampleSyncedCanvases():
    global btnCrs, mapCanvases, lyrs, syncExtents
    import site, sys
    # add site-packages to sys.path as done by enmapboxplugin.py
    from timeseriesviewer import sandbox
    from timeseriesviewer.utils import SpatialExtent
    import example.Images
    qgsApp = sandbox.initQgisEnvironment()
    w = QWidget()
    hl1 = QHBoxLayout()
    hl2 = QHBoxLayout()
    btnCrs = QgsProjectionSelectionWidget(w)
    btnRefresh = QPushButton('Refresh', w)
    hl1.addWidget(btnCrs)
    hl1.addWidget(btnRefresh)
    vl = QVBoxLayout()
    vl.addLayout(hl1)
    vl.addLayout(hl2)
    w.setLayout(vl)
    files = [example.Images.Img_2014_01_15_LC82270652014015LGN00_BOA,
             example.Images.Img_2013_05_20_LC82270652013140LGN01_BOA,
             example.Images.Img_2013_08_16_LE72270652013228CUB00_BOA]
    mapCanvases = []
    lyrs = []

    def onRefresh(*args):

        crs = btnCrs.crs()
        ext = SpatialExtent.fromLayer(lyrs[0]).toCrs(crs)
        for mapCanvas in mapCanvases:
            mapCanvas.setCrs(crs)
            mapCanvas.setSpatialExtent(ext)
            mapCanvas.refresh()
            mapCanvas.refreshAllLayers()

    def syncExtents(ext):

        for mapCanvas in mapCanvases:

            oldext = SpatialExtent.fromMapCanvas(mapCanvas)
            if oldext != ext:
                mapCanvas.blockSignals(True)
                #mapCanvas.setExtent(ext)
                mapCanvas.setSpatialExtent(ext)
                mapCanvas.blockSignals(False)
                mapCanvas.refreshAllLayers()

    def registerMapCanvas(mapCanvas):
        mapCanvas.extentsChanged.connect(lambda: syncExtents(SpatialExtent.fromMapCanvas(mapCanvas)))

    for i, f in enumerate(files):
        ml = QgsRasterLayer(f)
        #QgsMapLayerRegistry.instance().addMapLayer(ml)
        lyrs.append(ml)

        #mapCanvas = QgsMapCanvas(w)
        mapCanvas = MapCanvas(w)
        mapCanvas.setCrsTransformEnabled(True)
        registerMapCanvas(mapCanvas)
        hl2.addWidget(mapCanvas)
        #mapCanvas.setLayers([QgsMapCanvasLayer(ml)])
        mapCanvas.setLayers([ml])

        if i == 0:
            btnCrs.setCrs(ml.crs())
        mapCanvases.append(mapCanvas)

        btnCrs.crsChanged.connect(onRefresh)
        btnRefresh.clicked.connect(onRefresh)
    w.show()
    onRefresh()


if __name__ == '__main__':
    exampleSyncedCanvases()