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
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 TimeSeriesDatum, SensorProxyLayer, SensorInstrument
from .externals.qps.crosshair.crosshair import CrosshairDialog, CrosshairStyle, CrosshairMapCanvasItem
from .externals.qps.maptools import *

Benjamin Jakimow
committed
from .labeling import LabelAttributeTableModel, labelShortcutLayers, layerClassSchemes, applyShortcutsToRegisteredLayers
from .externals.qps.classification.classificationscheme import ClassificationScheme, ClassInfo
from .externals.qps.utils import *
from .externals.qps.layerproperties import showLayerPropertiesDialog
import eotimeseriesviewer.settings
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 MapCanvasInfoItem(QgsMapCanvasItem):
def __init__(self, mapCanvas):
assert isinstance(mapCanvas, QgsMapCanvas)
super(MapCanvasInfoItem, self).__init__(mapCanvas)
self.mCanvas = mapCanvas
self.mULText = None
self.mLRText = None
self.mURText = None
self.mVisibility = True
def setVisibility(self, b:bool):
Sets the visibility of a Crosshair
:param b:
:return:
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
assert isinstance(b, bool)
old = self.mShow
self.mVisibility = b
if old != b:
self.mCanvas.update()
def visibility(self)->bool:
"""Returns the Crosshair visibility"""
return self.mVisibility
def paintText(self, painter, text:str, position):
pen = QPen(Qt.SolidLine)
pen.setWidth(self.mCrosshairStyle.mThickness)
pen.setColor(self.mCrosshairStyle.mColor)
nLines = len(text.splitlines())
font = QFont('Courier', pointSize=10)
brush = self.mCanvas.backgroundBrush()
c = brush.color()
c.setAlpha(170)
brush.setColor(c)
painter.setBrush(brush)
painter.setPen(Qt.NoPen)
fm = QFontMetrics(font)
#background = QPolygonF(QRectF(backGroundPos, backGroundSize))
#painter.drawPolygon(background)
painter.setPen(pen)
painter.drawText(position, text)
painter.setFont(QFont('Courier', pointSize=10))
def paint(self, painter, QStyleOptionGraphicsItem=None, QWidget_widget=None):
"""
Paints the crosshair
:param painter:
:param QStyleOptionGraphicsItem:
:param QWidget_widget:
:return:
"""
if self.mLRText:
self.paintText(painter, self.mLRText, QPoint(0, 0))
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.CapturePoint, cadDock)
self.mtSelectFeature = QgsMapToolSelect(canvas)
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)
else:
print('Unknown MapTool key: {}'.format(mapToolKey))
class MapCanvas(QgsMapCanvas):
"""
A widget based on QgsMapCanvas to draw spatial data
"""
"""
Canvas specific commands
"""
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)

Benjamin Jakimow
committed
sigCrosshairStyleChanged = pyqtSignal(CrosshairStyle)
def __init__(self, parent=None):
super(MapCanvas, self).__init__(parent=parent)
self.mMapLayerStore = QgsProject.instance()
self.mMapTools = None
self.initMapTools()

benjamin.jakimow@geo.hu-berlin.de
committed
self.mCrosshairItem = CrosshairMapCanvasItem(self)
self.mInfoItem = MapCanvasInfoItem(self)
self.mNeedsRefresh = False
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)
bg = eotimeseriesviewer.settings.value(eotimeseriesviewer.settings.Keys.MapBackgroundColor, default=QColor(0, 0, 0))
self.setContextMenuPolicy(Qt.DefaultContextMenu)
self.extentsChanged.connect(lambda : self.sigSpatialExtentChanged.emit(self.spatialExtent()))
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

Benjamin Jakimow
committed
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)
from eotimeseriesviewer.mapvisualization import MapView
assert isinstance(mapView, MapView)
self.mMapView = mapView
def setTSD(self, tsd:TimeSeriesDatum):
"""
Sets the TimeSeriesDatum this map-canvas is linked to
:param tsd:
:return:
"""
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))
def tsd(self)->TimeSeriesDatum:
"""
Returns the TimeSeriesDatum
:return: TimeSeriesDatum
"""
return self.mTSD

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)
if self.crs() != crs:
self.setDestinationCrs(crs)
"""
Shortcut to return self.mapSettings().destinationCrs()
:return: QgsCoordinateReferenceSystem
"""
return self.mapSettings().destinationCrs()
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 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()
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]
"""
if not isinstance(arguments, list):
arguments = [arguments]
if isinstance(a, SpatialExtent):
elif isinstance(a, SpatialPoint):
self.mTimedRefreshPipeLine[SpatialExtent] = a
elif isinstance(a, QColor):
self.mTimedRefreshPipeLine[QColor] = a
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
while a in self.mTimedRefreshPipeLine[MapCanvas.Command]:
self.mTimedRefreshPipeLine[MapCanvas.Command].remove(a)
self.mTimedRefreshPipeLine[MapCanvas.Command].append(a)
else:
raise NotImplementedError('Unsupported argument: {}'.format(str(a)))
def timedRefresh(self):
"""
Called to refresh the map canvas with all things needed to be done with lazy evaluation
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
expected = []
existing = self.layers()
existingSources = [l.source() for l in existing]
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:
sourceLayer = SensorProxyLayer(source, sensor=self.tsd().sensor())
sourceLayer.setName(lyr.name())
sourceLayer.setCustomProperty('eotsv/sensorid', self.tsd().sensor().id())
renderer = lyr.renderer().clone()
sourceLayer.setRenderer(renderer)
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.
# look for new layers
lyrs = self.layers()
if lyrs != expected:
self.setLayers(expected)
if True:
# set sources first
keys = self.mTimedRefreshPipeLine.keys()
if QgsCoordinateReferenceSystem in keys:
self.setDestinationCrs(self.mTimedRefreshPipeLine[QgsCoordinateReferenceSystem])
if SpatialExtent in keys:
self.setSpatialExtent(self.mTimedRefreshPipeLine[SpatialExtent])
if SpatialPoint in keys:
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():
renderer = px.renderer().clone()
renderer.setInput(l.dataProvider())
l.setRenderer(renderer)
self.mTimedRefreshPipeLine.clear()
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:

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())

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

Benjamin Jakimow
committed
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
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, pos:QPoint)->QMenu:
"""
Create 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)
from .main import TimeSeriesViewer
eotsv = TimeSeriesViewer.instance()
menu = QMenu()
if isinstance(self.tsd(), TimeSeriesDatum):
action = menu.addAction('Set Quick Labels'.format(self.tsd().date()))
lyrWithSelectedFeatures = [l for l in labelShortcutLayers() if l.isEditable() and l.selectedFeatureCount() > 0]
layerNames = ', '.join([l.name() for l in lyrWithSelectedFeatures])
action.setEnabled(len(lyrWithSelectedFeatures) > 0)
action.setToolTip('Write date/sensor values to {} features in {}.'.format(self.tsd().date(), layerNames))
action.triggered.connect(lambda *args, tsd=self.tsd(): applyShortcutsToRegisteredLayers(tsd, None))
menu.addSeparator()
m = menu.addMenu('Optimize raster stretches...')
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))
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
menu.addSeparator()
m = menu.addMenu('Layers...')
for mapLayer in self.layers():
sub = m.addMenu(mapLayer.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: showLayerPropertiesDialog(lyr, self))
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)))
menu.addSeparator()
action = menu.addAction('Zoom to full extent')
action.setIcon(QIcon(':/images/themes/default/mActionZoomFullExtent.svg'))
action.triggered.connect(lambda: self.setExtent(self.fullExtent()))
menu.addSeparator()
m = menu.addMenu('Crosshair...')
action = m.addAction('Show')
action.setCheckable(True)
action.setChecked(self.mCrosshairItem.visibility())
action.toggled.connect(self.setCrosshairVisibility)

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

Benjamin Jakimow
committed
if isinstance(style, CrosshairStyle):
self.setCrosshairStyle(style)
action.triggered.connect(onCrosshairChange)
if isinstance(tsd, TimeSeriesDatum):
menu.addSeparator()
m = menu.addMenu('Copy...')
action = m.addAction('Date')
action.triggered.connect(lambda: QApplication.clipboard().setText(str(tsd.date())))
action.setToolTip('Sends "" to the clipboard.'.format(str(tsd.date())))
action = m.addAction('Sensor')
action.triggered.connect(lambda: QApplication.clipboard().setText(tsd.sensor().name()))
action.setToolTip('Sends "" to the clipboard.'.format(tsd.sensor().name()))
action = m.addAction('Path')
action.triggered.connect(lambda: QApplication.clipboard().setText('\n'.join(tsd.sourceUris())))
action.setToolTip('Sends the {} source URI(s) to the clipboard.'.format(len(tsd)))
action = m.addAction('Map')
action.triggered.connect(lambda: QApplication.clipboard().setPixmap(self.pixmap()))
action.setToolTip('Copies this map into the clipboard.')

benjamin.jakimow@geo.hu-berlin.de
committed
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)')
action.triggered.connect(lambda: QApplication.clipboard().setText(center.asWkt()))

benjamin.jakimow@geo.hu-berlin.de
committed

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.asWktPolygon()))
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

Benjamin Jakimow
committed
classSchemes = []
for layer in lyrWithSelectedFeaturs:
for classScheme in layerClassSchemes(layer):
assert isinstance(classScheme, ClassificationScheme)
if classScheme in classSchemes:
continue
classMenu = m.addMenu('Classification "{}"'.format(classScheme.name()))
assert isinstance(classMenu, QMenu)
for classInfo in classScheme:
assert isinstance(classInfo, ClassInfo)
a = classMenu.addAction(classInfo.name())
a.setIcon(classInfo.icon())
a.setToolTip('Write "{}" or "{}" to connected vector field attributes'.format(classInfo.name(), classInfo.label()))

Benjamin Jakimow
committed
a.triggered.connect(

Benjamin Jakimow
committed
applyShortcutsToRegisteredLayers(tsd, [ci]))
classSchemes.append(classScheme)
if isinstance(self.mTSD, TimeSeriesDatum):
menu.addSeparator()
action = menu.addAction('Hide date')
action.triggered.connect(lambda: self.mTSD.setVisibility(False))
if isinstance(eotsv, TimeSeriesViewer):
ts = eotsv.timeSeries()
action = menu.addAction('Remove date')
action.triggered.connect(lambda *args, : ts.removeTSDs([tsd]))
#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:QContextMenuEvent):
"""
Create and shows the MapCanvas context menu.
:param event: QEvent
"""
assert isinstance(event, QContextMenuEvent)
menu = self.contextMenu(event.pos())
menu.exec_(event.globalPos())
from eotimeseriesviewer.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:
"""
layers = [l for l in self.layers() if isinstance(l, SensorProxyLayer)]
for l in layers:
if isinstance(l, SensorProxyLayer):
r = l.renderer()
dp = l.dataProvider()
newRenderer = None
assert isinstance(dp, QgsRasterDataProvider)
def getCE(band):
stats = dp.bandStatistics(band, QgsRasterBandStats.All, extent, 500)
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):
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):
# 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 = r.clone()
ce = getCE(newRenderer.grayBand())
newRenderer.setContrastEnhancement(ce)
elif isinstance(r, QgsPalettedRasterRenderer):
if newRenderer is not None:
self.mMapView.sensorProxyLayer(l.sensor()).setRenderer(newRenderer)
s = ""
def saveMapImageDialog(self, fileType):
"""
Opens a dialog to save the map as local file
:param fileType:
:return:
"""
import eotimeseriesviewer.settings
lastDir = eotimeseriesviewer.settings.value(eotimeseriesviewer.settings.Keys.ScreenShotDirectory, os.path.expanduser('~'))
from eotimeseriesviewer.utils import filenameFromString
from eotimeseriesviewer.mapvisualization import MapView
if isinstance(self.mTSD, TimeSeriesDatum) and isinstance(self.mMapView, MapView):
path = filenameFromString('{}.{}'.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)
eotimeseriesviewer.settings.setValue(eotimeseriesviewer.settings.Keys.ScreenShotDirectory, os.path.dirname(path))
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())
if isinstance(spatialExtent, SpatialExtent):
self.setExtent(spatialExtent)
def setSpatialCenter(self, spatialPoint: SpatialPoint):
"""
Sets the map center
:param spatialPoint: SpatialPoint
"""
center = spatialPoint.toCrs(self.crs())
if isinstance(center, SpatialPoint):
self.setCenter(center)

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
:return: SpatialExtent
"""
layers = self.layers()
if len(layers) > 0:
e = self.fullExtent()
ext = SpatialExtent(crs, e)
else:
ext = SpatialExtent.world()
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
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):
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)