Newer
Older
# -*- coding: utf-8 -*-
"""
/***************************************************************************

Benjamin Jakimow
committed
EO Time Series Viewer
-------------------
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
import os, time, types
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

Benjamin Jakimow
committed
from . import SETTINGS
from .utils import *
from .timeseries import TimeSeriesDatum
from .crosshair import CrosshairDialog, CrosshairStyle
from .maptools import *
class MapCanvas(QgsMapCanvas):
saveFileDirectories = dict()
sigShowProfiles = pyqtSignal(SpatialPoint, str)
sigSpatialExtentChanged = pyqtSignal(SpatialExtent)
sigChangeDVRequest = pyqtSignal(QgsMapCanvas, str)
sigChangeMVRequest = pyqtSignal(QgsMapCanvas, str)
sigChangeSVRequest = pyqtSignal(QgsMapCanvas, QgsRasterRenderer)

Benjamin Jakimow
committed
sigCrosshairPositionChanged = pyqtSignal(SpatialPoint)
sigCrosshairVisibilityChanged = pyqtSignal(bool)
from .crosshair import CrosshairStyle
sigCrosshairStyleChanged = pyqtSignal(CrosshairStyle)
def __init__(self, parent=None):
super(MapCanvas, self).__init__(parent=parent)
self.mMapLayerStore = QgsProject.instance()
self.mLayerSources = []
#the canvas

benjamin.jakimow@geo.hu-berlin.de
committed
self.mRasterLayersVisible = True
self.mVectorLayersVisible = True
self.mPluginLayerVisible = True
self.mNeedsRefresh = False
self.mRendererRaster = None
self.mRendererVector = None
self.mRenderingFinished = True
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@geo.hu-berlin.de
committed
self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
self.setCanvasColor(SETTINGS.value('CANVAS_BACKGROUND_COLOR', QColor(0, 0, 0)))
self.setContextMenuPolicy(Qt.DefaultContextMenu)
#refreshTimer.timeout.connect(self.onTimerRefresh)
#self.extentsChanged.connect(lambda : self._setDataRefreshed())
self.extentsChanged.connect(lambda : self.sigSpatialExtentChanged.emit(self.spatialExtent()))
from timeseriesviewer.crosshair import CrosshairMapCanvasItem

Benjamin Jakimow
committed
self.mCrosshairItem = CrosshairMapCanvasItem(self)
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 setLayerVisibility(self, cls, isVisible:bool):
"""
Sets if specific QgsMapLayer types are to be shown or not
:param cls: class-type or instance of QgsMapLayer
:param isVisible: bool
"""
assert isinstance(isVisible, bool)
if cls == QgsRasterLayer or isinstance(cls, QgsRasterLayer):
self.mRasterLayersVisible = isVisible
elif cls == QgsVectorLayer or isinstance(cls, QgsVectorLayer):
self.mVectorLayersVisible = isVisible
elif cls == QgsPluginLayer or isinstance(cls, QgsPluginLayer):
self.mPluginLayerVisible = isVisible
def renderingFinished(self)->bool:
"""
Returns whether the MapCanvas is processing a rendering task
:return: bool
"""
return self.mRenderingFinished

Benjamin Jakimow
committed
def mousePressEvent(self, event:QMouseEvent):
b = event.button() == Qt.LeftButton
if b and isinstance(self.mapTool(), QgsMapTool):
from timeseriesviewer.maptools import CursorLocationMapTool
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):
from timeseriesviewer.mapvisualization import MapView
assert isinstance(mapView, MapView)
self.mMapView = mapView

Benjamin Jakimow
committed
scope = self.expressionContextScope()
def setTSD(self, tsd):
from timeseriesviewer.timeseries import TimeSeriesDatum
assert isinstance(tsd, TimeSeriesDatum)
self.mTSD = tsd

Benjamin Jakimow
committed
scope = self.expressionContextScope()
scope.setVariable('map_date', str(tsd.date()), isStatic=True)
scope.setVariable('map_doy', tsd.doy(), isStatic=True)
scope.setVariable('map_sensor', tsd.sensor().name(), isStatic=False)
tsd.sensor().sigNameChanged.connect(lambda name : scope.setVariable('map_sensor', name))

Benjamin Jakimow
committed
def setFixedSize(self, size):
assert isinstance(size, QSize)
if self.size() != size:
super(MapCanvas, self).setFixedSize(size)
if self.crs() != crs:
self.setDestinationCrs(crs)
"""
Shortcut to return self.mapSettings().destinationCrs()
:return: QgsCoordinateReferenceSystem
"""
return self.mapSettings().destinationCrs()
return self.mLayerSources

benjamin.jakimow@geo.hu-berlin.de
committed
def setLayers(self, mapLayers):
"""
Set the map layers and, if necessary, registers the in a QgsMapLayerStore
:param mapLayers:
"""
self.mMapLayerStore.addMapLayers(mapLayers)
super(MapCanvas, self).setLayers(mapLayers)
def setLazyLayers(self, layerSources):
"""
Sets the list of map layers of map layer sources to be shown.
:param layerSources: [list-of-sources]
"""
assert isinstance(layerSources, (list, types.GeneratorType))
self.mLayerSources = layerSources
def isVisibleToViewport(self)->bool:
return self.visibleRegion().boundingRect().isValid()
def visibleLayers(self)->list:
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
Returns the QgsMpaLayers to be shown in the map
:return: [list-of-QgsMapLayers]
"""
for i in range(len(self.mLayerSources)):
if not isinstance(self.mLayerSources[i], QgsMapLayer):
lyr = toMapLayer(self.mLayerSources[i])
assert isinstance(lyr, QgsMapLayer), 'unable to convert {} into QgsMapLayer'.format(
self.mLayerSources[i])
self.mLayerSources[i] = lyr
if isinstance(lyr, QgsRasterLayer) and isinstance(self.mRendererRaster, QgsRasterRenderer):
lyr.setRenderer(self.mRendererRaster)
elif isinstance(lyr, QgsVectorLayer) and isinstance(self.mRendererVector, QgsFeatureRenderer):
lyr.setRenderer(self.mRendererVector)
visibleLayers = []
for l in self.mLayerSources:
if isinstance(l, QgsRasterLayer) and self.mRasterLayersVisible:
visibleLayers.append(l)
elif isinstance(l, QgsVectorLayer) and self.mVectorLayersVisible:
visibleLayers.append(l)
elif isinstance(l, QgsPluginLayer) and self.mPluginLayerVisible:
visibleLayers.append(l)
return visibleLayers
def timedRefresh(self):
"""
Call this to refresh all layers, visibility etc.
visibleLayers = self.visibleLayers()
lastLayers = self.layers()
if self.mNeedsRefresh or visibleLayers != lastLayers:
self.mIsRefreshing = True
self.mRefreshStartTime = time.time()
self.setLayers(visibleLayers)
self.refresh()
self.mNeedsRefresh = False
def setCrosshairStyle(self, crosshairStyle:CrosshairStyle, emitSignal=True):
"""
Sets the CrosshairStyle
:param crosshairStyle: CrosshairStyle
:param emitSignal: Set to Fals to no emit a signal.
"""
from timeseriesviewer.crosshair import CrosshairStyle
if crosshairStyle is None:

Benjamin Jakimow
committed
self.mCrosshairItem.crosshairStyle.setShow(False)
self.mCrosshairItem.update()
else:
assert isinstance(crosshairStyle, CrosshairStyle)

Benjamin Jakimow
committed
self.mCrosshairItem.setCrosshairStyle(crosshairStyle)

Benjamin Jakimow
committed
if emitSignal:
self.sigCrosshairStyleChanged.emit(self.mCrosshairItem.crosshairStyle)
def crosshairStyle(self)->CrosshairStyle:
"""
Returns the style of the Crosshair.
:return: CrosshairStyle
"""

Benjamin Jakimow
committed
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
return self.mCrosshairItem.crosshairStyle
def setCrosshairPosition(self, spatialPoint:SpatialPoint, emitSignal=True):
"""
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())
self.mCrosshairItem.setPosition(point)
if emitSignal:
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 layerPaths(self):
:return: [list-of-str]

benjamin.jakimow@geo.hu-berlin.de
committed
return [str(l.source()) for l in self.layers()]
def pixmap(self):
"""
Returns the current map image as pixmap
return self.grab()
def contextMenu(self)->QMenu:
"""
Create the MapCanvas context menu
:return:
"""
menu = QMenu()
# add general options
menu.addSeparator()
m = menu.addMenu('Stretch to current extent...')
action = m.addAction('Linear')
action.triggered.connect(lambda : self.stretchToExtent(self.spatialExtent(), 'linear_minmax', p=0.0))
action = m.addAction('Linear 5%')
action.triggered.connect(lambda: self.stretchToExtent(self.spatialExtent(), 'linear_minmax', p=0.05))
action = m.addAction('Gaussian')
action.triggered.connect(lambda: self.stretchToExtent(self.spatialExtent(), 'gaussian', n=3))
action = menu.addAction('Zoom to Layer')
action.triggered.connect(lambda : self.setSpatialExtent(self.spatialExtentHint()))
action = menu.addAction('Refresh')
action.triggered.connect(lambda: self.refresh(True))
menu.addSeparator()
action = menu.addAction('Change crosshair style')

Benjamin Jakimow
committed
def onCrosshairChange(*args):
style = CrosshairDialog.getCrosshairStyle(parent=self,

benjamin.jakimow@geo.hu-berlin.de
committed
mapCanvas=self,

Benjamin Jakimow
committed
crosshairStyle=self.mCrosshairItem.crosshairStyle)
if isinstance(style, CrosshairStyle):
self.setCrosshairStyle(style)
action.triggered.connect(onCrosshairChange)

Benjamin Jakimow
committed
if self.mCrosshairItem.visibility():
action = menu.addAction('Hide crosshair')

Benjamin Jakimow
committed
action.triggered.connect(lambda : self.setCrosshairVisibility(False))
else:
action = menu.addAction('Show crosshair')

Benjamin Jakimow
committed
action.triggered.connect(lambda: self.setCrosshairVisibility(True))
menu.addSeparator()
m = menu.addMenu('Copy...')
action = m.addAction('Date')
action.triggered.connect(lambda: self.sigChangeDVRequest.emit(self, 'copy_date'))
action = m.addAction('Sensor')
action.triggered.connect(lambda: self.sigChangeDVRequest.emit(self, 'copy_sensor'))
action = m.addAction('Path')
action.triggered.connect(lambda: self.sigChangeDVRequest.emit(self, 'copy_path'))

benjamin.jakimow@geo.hu-berlin.de
committed
action = m.addAction('Map')
action.triggered.connect(lambda: QApplication.clipboard().setPixmap(self.pixmap()))
m = menu.addMenu('Map Coordinates...')
ext = self.spatialExtent()
center = self.spatialExtent().spatialCenter()
action = m.addAction('Extent (WKT Coordinates)')
action.triggered.connect(lambda: QApplication.clipboard().setText(ext.asWktCoordinates()))
action = m.addAction('Extent (WKT Polygon)')
action.triggered.connect(lambda: QApplication.clipboard().setText(ext.asWktPolygon()))
m.addSeparator()
action = m.addAction('Map Center (WKT)')

benjamin.jakimow@geo.hu-berlin.de
committed
action.triggered.connect(lambda: QApplication.clipboard().setText(center.wellKnownText()))

benjamin.jakimow@geo.hu-berlin.de
committed
action.triggered.connect(lambda: QApplication.clipboard().setText(center.toString()))
action = m.addAction('Map Extent (WKT)')
action.triggered.connect(lambda: QApplication.clipboard().setText(ext.wellKnownText()))
action = m.addAction('Map Extent')
action.triggered.connect(lambda: QApplication.clipboard().setText(ext.toString()))

benjamin.jakimow@geo.hu-berlin.de
committed
m.addSeparator()
action = m.addAction('CRS (EPSG)')
action.triggered.connect(lambda: QApplication.clipboard().setText(self.crs().authid()))
action = m.addAction('CRS (WKT)')
action.triggered.connect(lambda: QApplication.clipboard().setText(self.crs().toWkt()))
action = m.addAction('CRS (Proj4)')
action.triggered.connect(lambda: QApplication.clipboard().setText(self.crs().toProj4()))
action = m.addAction('PNG')
action.triggered.connect(lambda : self.saveMapImageDialog('PNG'))
action = m.addAction('JPEG')
action.triggered.connect(lambda: self.saveMapImageDialog('JPG'))

benjamin.jakimow@geo.hu-berlin.de
committed
from timeseriesviewer.utils import qgisInstance
actionAddRaster2QGIS = menu.addAction('Add raster layers(s) to QGIS')
actionAddRaster2QGIS.triggered.connect(lambda : self.addLayers2QGIS(
[l for l in self.layers() if isinstance(l, QgsRasterLayer)]
)
)
# QGIS 3: action.triggered.connect(lambda: QgsProject.instance().addMapLayers([l for l in self.layers() if isinstance(l, QgsRasterLayer)]))
actionAddVector2QGIS = menu.addAction('Add vector layer(s) to QGIS')
actionAddRaster2QGIS.triggered.connect(lambda : self.addLayers2QGIS(
#QgsProject.instance().addMapLayers(
[l for l in self.layers() if isinstance(l, QgsVectorLayer)]
)
)
# QGIS 3: action.triggered.connect(lambda: QgsProject.instance().addMapLayers([l for l in self.layers() if isinstance(l, QgsVectorLayer)]))
b = isinstance(qgisInstance(), QgisInterface)
for a in [actionAddRaster2QGIS, actionAddVector2QGIS]:
a.setEnabled(b)
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('Hide map view')
action.triggered.connect(lambda: self.sigChangeMVRequest.emit(self, 'hide_mapview'))
action = menu.addAction('Remove map view')
action.triggered.connect(lambda: self.sigChangeMVRequest.emit(self, 'remove_mapview'))
return menu
def contextMenuEvent(self, event):
"""
Create and shows the MapCanvas context menu.
:param event: QEvent
"""
menu = self.contextMenu()
menu.exec_(event.globalPos())
def addLayers2QGIS(self, mapLayers):
from timeseriesviewer.utils import qgisInstance
iface = qgisInstance()
if isinstance(iface, QgisInterface):
grpNode= iface.layerTreeView().currentGroupNode()
assert isinstance(grpNode, QgsLayerTreeGroup)
for l in mapLayers:
if isinstance(l, QgsRasterLayer):
lqgis = iface.addRasterLayer(l.source(), l.name())
lqgis.setRenderer(l.renderer().clone())
lqgis = iface.addVectorLayer(l.source(), l.name(), 'ogr')
def stretchToCurrentExtent(self):
se = self.spatialExtent()
def stretchToExtent(self, spatialExtent:SpatialExtent, stretchType='linear_minmax', **stretchArgs):
"""
:param spatialExtent: rectangle to get the image statistics for
:param stretchType: ['linear_minmax' (default), 'gaussian']
:param stretchArgs:
linear_minmax: 'p' percentage from min/max, e.g. +- 5 %
gaussian: 'n' mean +- n* standard deviations
:return:
"""
for l in self.layers():
if isinstance(l, QgsRasterLayer):
r = l.renderer()
dp = l.dataProvider()
newRenderer = None
assert isinstance(dp, QgsRasterDataProvider)
def getCE(band):
stats = dp.bandStatistics(band, QgsRasterBandStats.All, extent, 500)
# hist = dp.histogram(band,100, stats.minimumValue, stats.maximumValue, extent, 500, False)
ce = QgsContrastEnhancement(dp.dataType(band))
d = (stats.maximumValue - stats.minimumValue)
if stretchType == 'linear_minmax':
ce.setContrastEnhancementAlgorithm(QgsContrastEnhancement.StretchToMinimumMaximum)
ce.setMinimumValue(stats.minimumValue + d * stretchArgs.get('p', 0))
ce.setMaximumValue(stats.maximumValue - d * stretchArgs.get('p', 0))
elif stretchType == 'gaussian':
ce.setContrastEnhancementAlgorithm(QgsContrastEnhancement.StretchToMinimumMaximum)
ce.setMinimumValue(stats.mean - stats.stdDev * stretchArgs.get('n', 3))
ce.setMaximumValue(stats.mean + stats.stdDev * stretchArgs.get('n', 3))
else:
# stretchType == 'linear_minmax':
ce.setContrastEnhancementAlgorithm(QgsContrastEnhancement.StretchToMinimumMaximum)
ce.setMinimumValue(stats.minimumValue)
ce.setMaximumValue(stats.maximumValue)
return ce
if isinstance(r, QgsMultiBandColorRenderer):
#newRenderer = QgsMultiBandColorRenderer(None, r.redBand(), r.greenBand(), r.blueBand())
newRenderer = cloneRenderer(r)
ceR = getCE(r.redBand())
ceG = getCE(r.greenBand())
ceB = getCE(r.blueBand())
newRenderer.setRedContrastEnhancement(ceR)
newRenderer.setGreenContrastEnhancement(ceG)
newRenderer.setBlueContrastEnhancement(ceB)
elif isinstance(r, QgsSingleBandPseudoColorRenderer):
ce = getCE(newRenderer.band())
#stats = dp.bandStatistics(newRenderer.band(), QgsRasterBandStats.All, extent, 500)
shader = newRenderer.shader()
newRenderer.setClassificationMax(ce.maximumValue())
newRenderer.setClassificationMin(ce.minimumValue())
shader.setMaximumValue(ce.maximumValue())
shader.setMinimumValue(ce.minimumValue())
elif isinstance(r, QgsSingleBandGrayRenderer):
newRenderer = cloneRenderer(r)
s = ""
elif isinstance(r, QgsPalettedRasterRenderer):
s = ""
if newRenderer is not None:
self.sigChangeSVRequest.emit(self, newRenderer)
s = ""
def saveMapImageDialog(self, fileType):
lastDir = SETTINGS.value('CANVAS_SAVE_IMG_DIR', os.path.expanduser('~'))
from timeseriesviewer.utils import saveFilePath
from timeseriesviewer.mapvisualization import MapView
if isinstance(self.mTSD, TimeSeriesDatum) and isinstance(self.mMapView, MapView):
path = saveFilePath('{}.{}'.format(self.mTSD.date, self.mMapView.title()))
else:
path = 'mapcanvas'
path = jp(lastDir, '{}.{}'.format(path, 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))

benjamin.jakimow@geo.hu-berlin.de
committed
def setRenderer(self, renderer, refresh=True):
"""
Sets a QgsRasterRenderer or QgsFeatureRenderer which is applied or all QgsRasterLayers or QgsVectorLayer, respectively.
:param renderer: QgsRasterRenderer | QgsFeatureRenderer
:param refresh:
:return:
"""
if isinstance(renderer, QgsRasterRenderer):
self.mRendererRaster = renderer
for l in self.layers():
if isinstance(l, QgsRasterLayer):
l.setRenderer(renderer.clone())
if isinstance(renderer, QgsFeatureRenderer):
self.mRendererVector = renderer
for l in self.layers():
if isinstance(l, QgsVectorLayer):
l.setRenderer(renderer.clone())
def setSpatialExtent(self, spatialExtent:SpatialExtent):
"""
Sets the SpatialExtent to be shown.
:param spatialExtent: SpatialExtent
"""
assert isinstance(spatialExtent, SpatialExtent)
if self.spatialExtent() != spatialExtent:
spatialExtent = spatialExtent.toCrs(self.crs())
ext = QgsRectangle(spatialExtent)
self.setCenter(ext.center())
self.setExtent(ext)

Benjamin Jakimow
committed
def spatialExtent(self)->SpatialExtent:
"""
Returns the map extent as SpatialExtent (extent + CRS)
:return: SpatialExtent
"""
return SpatialExtent.fromMapCanvas(self)

Benjamin Jakimow
committed
def spatialCenter(self)->SpatialPoint:
"""
Returns the map center as SpatialPoint (QgsPointXY + CRS)
:return: SpatialPoint
"""
return SpatialPoint.fromMapCanvasCenter(self)
def spatialExtentHint(self)->SpatialExtent:
"""
Returns a hint for a SpatialExtent, derived from the first raster layer in this
:return:
"""
crs = self.crs()
ext = SpatialExtent.world()
layers = self.layers() + self.visibleLayers()
for lyr in layers:
ext = SpatialExtent.fromLayer(lyr).toCrs(crs)
break
return ext
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
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())
assert geom.fromWkt(ext.asWktPolygon())
self.mCanvasExtents[canvas] = (ext, geom)
self.refreshExtents()
def refreshExtents(self):
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
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