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. *
* *
***************************************************************************/
"""
import os, sys, re, fnmatch, collections, copy, traceback, bisect

benjamin.jakimow@geo.hu-berlin.de
committed
from qgis.core import *
from qgis.core import QgsContrastEnhancement, QgsRasterShader, QgsColorRampShader, QgsProject, QgsCoordinateReferenceSystem, \
QgsRasterLayer, QgsVectorLayer, QgsMapLayer, QgsMapLayerProxyModel, QgsColorRamp, QgsSingleBandPseudoColorRenderer
from qgis.gui import *
from qgis.gui import QgsDockWidget, QgsMapCanvas, QgsMapTool, QgsCollapsibleGroupBox
from PyQt5.QtXml import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *

benjamin.jakimow@geo.hu-berlin.de
committed
import numpy as np
from .utils import *
from .import Option, OptionListModel
from .timeseries import SensorInstrument, TimeSeriesDate, TimeSeries, SensorProxyLayer
from .utils import loadUI
from .mapviewscrollarea import MapViewScrollArea
from .mapcanvas import MapCanvas, MapTools, MapCanvasInfoItem, MapCanvasMapTools
from .externals.qps.crosshair.crosshair import getCrosshairStyle, CrosshairStyle, CrosshairMapCanvasItem
from .externals.qps.layerproperties import showLayerPropertiesDialog
from .externals.qps.maptools import *
#assert os.path.isfile(dummyPath)
#lyr = QgsRasterLayer(dummyPath)
#assert lyr.isValid()
DUMMY_RASTERINTERFACE = QgsSingleBandGrayRenderer(None, 0)
KEY_LOCKED_LAYER = 'eotsv/locked'
KEY_SENSOR_GROUP = 'eotsv/sensorgroup'
KEY_SENSOR_LAYER = 'eotsv/sensorlayer'

Benjamin Jakimow
committed
def equalTextFormats(tf1:QgsTextFormat, tf2:QgsTextFormat)->True:
return tf1.toMimeData().text() == tf2.toMimeData().text()
class MapViewLayerTreeViewMenuProvider(QgsLayerTreeViewMenuProvider):
def __init__(self, mapView, view: QgsLayerTreeView, canvas: QgsMapCanvas):
super(MapViewLayerTreeViewMenuProvider, self).__init__()
assert isinstance(view, QgsLayerTreeView)
assert isinstance(canvas, QgsMapCanvas)
self.mLayerTreeView = view
self.mDummyCanvas = canvas
self.mDefActions = QgsLayerTreeViewDefaultActions(self.mLayerTreeView)
self.mMapView = mapView
self.actionAddGroup = self.mDefActions.actionAddGroup()
self.actionRename = self.mDefActions.actionRenameGroupOrLayer()
self.actionRemove = self.mDefActions.actionRemoveGroupOrLayer()

Benjamin Jakimow
committed
#self.actionZoomToLayer = self.mDefActions.actionZoomToGroup(self.mDummyCanvas)
self.actionCheckAndAllChildren = self.mDefActions.actionCheckAndAllChildren()
self.actionShowFeatureCount = self.mDefActions.actionShowFeatureCount()

Benjamin Jakimow
committed
#self.actionZoomToLayer = self.mDefActions.actionZoomToLayer(self.mDummyCanvas)
#self.actionZoomToSelected = self.mDefActions.actionZoomToSelection(self.mDummyCanvas)
#self.actionZoomToGroup = self.mDefActions.actionZoomToGroup(self.mDummyCanvas)
self.actionAddEOTSVSpectralProfiles = QAction('Add Spectral Profile Layer')
self.actionAddEOTSVTemporalProfiles = QAction('Add Temporal Profile Layer')
def mapView(self):
return self.mMapView
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
def layerTreeView(self)->QgsLayerTreeView:
return self.mLayerTreeView
def layerTree(self)->QgsLayerTree:
return self.layerTreeModel().rootGroup()
def layerTreeModel(self)->QgsLayerTreeModel:
return self.layerTreeView().model()
def createContextMenu(self)->QMenu:
model = self.layerTreeModel()
ltree = self.layerTree()
view = self.layerTreeView()
g = view.currentGroupNode()
l = view.currentLayer()
i = view.currentIndex()
#fixedNodes = len([l for l in view.selectedLayersRecursive() if l.property(KEY_LOCKED_LAYER) == True]) > 0 or \
# isinstance(g, QgsLayerTreeGroup) and g.property(KEY_LOCKED_LAYER) == True
# disable actions
#self.actionRemove.setEnabled(fixedNodes == False)
menu = QMenu(view)
isSensorGroup = isinstance(g, QgsLayerTreeGroup) and g.customProperty(KEY_SENSOR_GROUP) in [True, 'true']
isSensorLayer = isinstance(l, QgsRasterLayer) and l.customProperty(KEY_SENSOR_LAYER) in [True, 'true']
self.actionRemove.setEnabled(not (isSensorGroup or isSensorLayer))
self.actionAddGroup.setEnabled(not (isSensorGroup or isSensorLayer))
menu.addAction(self.actionAddGroup)
menu.addAction(self.actionRename)
menu.addAction(self.actionRemove)

Benjamin Jakimow
committed
#menu.addAction(self.actionZoomToGroup)
#menu.addAction(self.actionZoomToLayer)
#menu.addAction(self.actionZoomToSelected)
menu.addSeparator()
menu.addAction(self.actionAddEOTSVSpectralProfiles)
menu.addAction(self.actionAddEOTSVTemporalProfiles)

Benjamin Jakimow
committed
menu.addSeparator()
centerCanvas = None
if isinstance(self.mapView(), MapView):
visibleCanvases = self.mapView().visibleMapCanvases()
if len(visibleCanvases) > 0:
i = int(len(visibleCanvases) / 2)
centerCanvas = visibleCanvases[i]
a = menu.addAction('Set Properties')
a.triggered.connect(lambda *args,
canvas = centerCanvas,
lyr = l,
b = not isinstance(l, SensorProxyLayer):
showLayerPropertiesDialog(lyr, canvas, useQGISDialog=b))
a.setEnabled(isinstance(centerCanvas, QgsMapCanvas))

Benjamin Jakimow
committed
from .externals.qps.layerproperties import pasteStyleFromClipboard, pasteStyleToClipboard
a = menu.addAction('Copy Style')
a.setToolTip('Copy the current layer style to clipboard')
a.triggered.connect(lambda *args, lyr=l: pasteStyleToClipboard(lyr))

Benjamin Jakimow
committed
a = menu.addAction('Paste Style')
a.setEnabled('application/qgis.style' in QApplication.clipboard().mimeData().formats())
a.triggered.connect(lambda *args, lyr=l: pasteStyleFromClipboard(lyr))
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
#a = menu.addAction('Settings')
#from qps.layerproperties import showLayerPropertiesDialog
#a.triggered.connect(lambda *args, lyr=l:showLayerPropertiesDialog(lyr, self._canvas))
return menu
class MapViewLayerTreeModel(QgsLayerTreeModel):
"""
Layer Tree as shown in a MapView
"""
def __init__(self, rootNode, parent=None):
super(MapViewLayerTreeModel, self).__init__(rootNode, parent=parent)
def dataXXX(self, index:QModelIndex, role=Qt.DisplayRole):
node = self.index2node(index)
# if node.name() == 'testlayer':
# s = ""
if True:
if isinstance(node, QgsLayerTreeGroup) and node.customProperty(KEY_SENSOR_GROUP) in ['true', True]:
if role == Qt.FontRole:
f = super(MapViewLayerTreeModel, self).data(index, role=role)
f.setBold(True)
return f
if isinstance(node, QgsLayerTreeLayer) and node.customProperty(KEY_SENSOR_LAYER) in ['true', True]:
if role == Qt.FontRole:
f = super(MapViewLayerTreeModel, self).data(index, role=role)
assert isinstance(f, QFont)
f.setItalic(True)
return f

benjamin.jakimow@geo.hu-berlin.de
committed
if role == Qt.DecorationRole:
return QIcon(':/timeseriesviewer/icons/icon.svg')
return super(MapViewLayerTreeModel, self).data(index, role=role)
def flagsXXX(self, index:QModelIndex):
f = super(MapViewLayerTreeModel, self).flags(index)
node = self.index2node(index)
if isinstance(node, QgsLayerTreeNode) and ( \
node.customProperty(KEY_SENSOR_LAYER) in ['true', True] or \
node.customProperty(KEY_SENSOR_GROUP) in ['true', True]):
f = f ^ Qt.ItemIsDragEnabled
f = f ^ Qt.ItemIsDropEnabled
return f
class MapView(QFrame, loadUIFormClass(jp(DIR_UI, 'mapview.ui'))):
"""
A MapView defines how a single map canvas visualizes sensor specific EOTS data plus additional vector overlays
"""
#sigVisibilityChanged = pyqtSignal(bool)
sigCanvasAppearanceChanged = pyqtSignal()
sigCrosshairChanged = pyqtSignal()
sigTitleChanged = pyqtSignal(str)

benjamin.jakimow@geo.hu-berlin.de
committed
sigSensorRendererChanged = pyqtSignal(SensorInstrument, QgsRasterRenderer)
sigShowProfiles = pyqtSignal(SpatialPoint, MapCanvas, str)

benjamin.jakimow@geo.hu-berlin.de
committed
def __init__(self, name='Map View', parent=None):
super(MapView, self).__init__(parent)
self.setupUi(self)

Benjamin Jakimow
committed

Benjamin Jakimow
committed
from eotimeseriesviewer.settings import DEFAULT_VALUES, Keys

Benjamin Jakimow
committed
self.mMapBackgroundColor = DEFAULT_VALUES[Keys.MapBackgroundColor]
self.mMapTextFormat = DEFAULT_VALUES[Keys.MapTextFormat]
self.mCurrentLayer = None
self.mMapWidget = None
self.mTimeSeries = None
self.mSensorLayerList = list()
self.mCrossHairStyle = CrosshairStyle()
m = QMenu(self.btnToggleCrosshair)
m.addAction(self.actionSetCrosshairStyle)
self.btnToggleCrosshair.setMenu(m)
self.btnToggleCrosshair.setDefaultAction(self.actionToggleCrosshairVisibility)
self.btnToggleCrosshair.setChecked(self.crosshairStyle().mShow)
self.btnToggleMapViewVisibility.setDefaultAction(self.actionToggleMapViewHidden)
self.tbName.textChanged.connect(self.onTitleChanged)
self.actionSetCrosshairStyle.triggered.connect(self.onChangeCrosshairStyle)
self.actionToggleMapViewHidden.toggled.connect(self.sigCanvasAppearanceChanged)
self.actionToggleCrosshairVisibility.toggled.connect(self.setCrosshairVisibility)
self.actionAddMapLayer.triggered.connect(lambda *args: self.onAddMapLayer())
self.actionAddVectorLayer.triggered.connect(lambda *args: self.onAddMapLayer(QgsMapLayerProxyModel.VectorLayer))
self.actionAddRasterLayer.triggered.connect(lambda *args: self.onAddMapLayer(QgsMapLayerProxyModel.RasterLayer))
self.btnAddLayer.setDefaultAction(self.actionAddMapLayer)
m = QMenu()
m.addAction(self.actionAddVectorLayer)
m.addAction(self.actionAddRasterLayer)
self.btnAddLayer.setMenu(m)
self.btnHighlightMapView.setDefaultAction(self.actionHighlightMapView)
self.actionHighlightMapView.triggered.connect(lambda: self.setHighlighted(True, timeout=500))
assert isinstance(self.mLayerTreeView, QgsLayerTreeView)
self.mDummyCanvas = QgsMapCanvas() # dummy map canvas for dummy layers
self.mDummyCanvas.setVisible(False)

benjamin.jakimow@geo.hu-berlin.de
committed
self.mLayerTree = QgsLayerTree()
self.mLayerTreeMapCanvasBridget = QgsLayerTreeMapCanvasBridge(self.mLayerTree, self.mDummyCanvas)

benjamin.jakimow@geo.hu-berlin.de
committed
# self.mLayerTreeModel = QgsLayerTreeModel(self.mLayerTree)
self.mLayerTreeModel = MapViewLayerTreeModel(self.mLayerTree)
self.mLayerTreeModel.setFlags(QgsLayerTreeModel.AllowNodeChangeVisibility |
QgsLayerTreeModel.AllowNodeRename |
QgsLayerTreeModel.AllowNodeReorder)
self._createSensorNode()
self.mLayerTreeView.setModel(self.mLayerTreeModel)
self.mMapLayerTreeViewMenuProvider = MapViewLayerTreeViewMenuProvider(self, self.mLayerTreeView, self.mDummyCanvas)
# register some actions that interact with other GUI elements
self.mMapLayerTreeViewMenuProvider.actionAddEOTSVSpectralProfiles.triggered.connect(self.addSpectralProfileLayer)
self.mMapLayerTreeViewMenuProvider.actionAddEOTSVTemporalProfiles.triggered.connect(self.addTemporalProfileLayer)
self.mLayerTreeView.setMenuProvider(self.mMapLayerTreeViewMenuProvider)
self.mLayerTreeView.currentLayerChanged.connect(self.setCurrentLayer)
self.mLayerTree.removedChildren.connect(self.onChildNodesRemoved)
self.mIsVisible = True

Benjamin Jakimow
committed
m = QMenu()
m.addAction(self.optionShowDate)
m.addAction(self.optionShowSensorName)
m.addAction(self.optionShowMapViewName)
self.btnInfoOptions.setMenu(m)
for action in m.actions():
action.toggled.connect(self.sigCanvasAppearanceChanged)
fixMenuButtons(self)
def setName(self, name:str):
self.setTitle(name)
def name(self)->str:
return self.title()

Benjamin Jakimow
committed
def setMapTextFormat(self, textformat:QgsTextFormat)->QgsTextFormat:
if not equalTextFormats(self.mapTextFormat(), textformat):
self.mMapTextFormat = textformat
self.sigCanvasAppearanceChanged.emit()
return self.mapTextFormat()
def mapTextFormat(self)->QgsTextFormat:
return self.mMapTextFormat
def mapBackgroundColor(self)->QColor:
"""
Returns the map background color
:return: QColor
"""
return self.mMapBackgroundColor
def setMapBackgroundColor(self, color:QColor)->QColor:
"""
Sets the map background color
:param color: QColor
:return: QColor
"""
if self.mMapBackgroundColor != color:
self.mMapBackgroundColor = color
self.sigCanvasAppearanceChanged.emit()
return self.mMapBackgroundColor
def visibleMapCanvases(self)->list:
"""
Returns the currently visible mapcanvases
:return: [list-of-MapCanvases]
"""
return [m for m in self.mapCanvases() if m.isVisibleToViewport()]
def onAddMapLayer(self, filter:QgsMapLayerProxyModel.Filter=QgsMapLayerProxyModel.All):
Slot that opens a SelectMapLayersDialog for any kind of layer
"""
from .externals.qps.utils import SelectMapLayersDialog
d = SelectMapLayersDialog()
if filter == QgsMapLayerProxyModel.All:
title = 'Select Layer'
text = 'Layer'
elif filter == QgsMapLayerProxyModel.RasterLayer:
title = 'Select Raster Layer'
text = 'Raster'
elif filter == QgsMapLayerProxyModel.VectorLayer:
title = 'Select Vector Layer'
text = 'Vector'
d.setWindowTitle(title)
d.addLayerDescription(text, filter)
if d.exec() == QDialog.Accepted:
for l in d.mapLayers():
self.addLayer(l)
def setCurrentLayer(self, layer:QgsMapLayer):
"""
Sets the QgsMapCanvas.currentLayer() that is used by some QgsMapTools
:param layer: QgsMapLayer | None
:return:
"""
assert layer is None or isinstance(layer, QgsMapLayer)
self.mCurrentLayer = layer
if layer not in self.mSensorLayerList:
for c in self.mapCanvases():
c.setCurrentLayer(layer)
else:
s = ""
def addSpectralProfileLayer(self):
"""Adds the EOTSV Spectral Profile Layer"""
from eotimeseriesviewer.main import TimeSeriesViewer
tsv = TimeSeriesViewer.instance()
if isinstance(tsv, TimeSeriesViewer):
lyr = tsv.spectralLibrary()
if lyr not in self.layers():
self.addLayer(lyr)
def addTemporalProfileLayer(self):
"""Adds the EOTSV Temporal Profile Layer"""
from eotimeseriesviewer.main import TimeSeriesViewer
tsv = TimeSeriesViewer.instance()
if isinstance(tsv, TimeSeriesViewer):
lyr = tsv.temporalProfileLayer()
if lyr not in self.layers():
self.addLayer(lyr)
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
def addLayer(self, layer:QgsMapLayer):
"""
Add a QgsMapLayer to the MapView layer tree
:param layer: QgsMapLayer
"""
if isinstance(layer, QgsVectorLayer):
self.mLayerTree.insertLayer(0, layer)
else:
self.mLayerTree.addLayer(layer)
def _createSensorNode(self):
self.mLayerTreeSensorNode = QgsLayerTreeGroup(name='Raster Time Series', checked=True)
self.mLayerTreeSensorNode.setCustomProperty(KEY_LOCKED_LAYER, True)
self.mLayerTreeSensorNode.setCustomProperty(KEY_SENSOR_GROUP, True)
self.mLayerTree.addChildNode(self.mLayerTreeSensorNode)
def _containsSensorNode(self, root:QgsLayerTreeGroup)->bool:
assert isinstance(root, QgsLayerTreeGroup)
if root.customProperty(KEY_SENSOR_GROUP) in [True, 'true']:
return True
for grp in root.findGroups():
if self._containsSensorNode(grp):
return True
return False
def onChildNodesRemoved(self, node, idxFrom, idxTo):
if not self._containsSensorNode(self.mLayerTreeModel.rootGroup()):
self._createSensorNode()
def onChangeCrosshairStyle(self):
canvases = self.mapCanvases()
if len(canvases) > 0:
mapCanvas = canvases[0]
else:
mapCanvas = None
style = getCrosshairStyle(parent=self, crosshairStyle=self.crosshairStyle(), mapCanvas=mapCanvas)
if isinstance(style, CrosshairStyle):
self.setCrosshairStyle(style)
def setVisibility(self, b: bool):

Benjamin Jakimow
committed
"""
Sets the map view visibility
:param b: bool
"""
if self.actionToggleMapViewHidden.isChecked() == b:
self.actionToggleMapViewHidden.setChecked(not b)
self.sigCanvasAppearanceChanged.emit()

Benjamin Jakimow
committed
def isVisible(self)->bool:
"""
Returns the map view visibility
:return: bool
"""
return not self.actionToggleMapViewHidden.isChecked()

Benjamin Jakimow
committed
def mapCanvases(self)->list:
"""
Returns the MapCanvases related to this map view. Requires that this mapview was added to a MapWidget

Benjamin Jakimow
committed
:return: [list-of-MapCanvases]
"""
if isinstance(self.mMapWidget, MapWidget):
return self.mMapWidget.mapViewCanvases(self)
else:
return []
def onTitleChanged(self, *args):
self.setWindowTitle('Map View "{}"'.format(self.title()))
self.sigTitleChanged.emit(self.title())
if self.optionShowMapViewName.isChecked():
self.sigCanvasAppearanceChanged.emit()
def setMapWidget(self, w):
if isinstance(w, MapWidget):
self.mMapWidget = w
else:
self.mMapWidget = None
def setTimeSeries(self, timeSeries:TimeSeries):
"""
Conntects the MapView with a TimeSeries.
:param timeSeries: TimeSeries
"""
assert isinstance(timeSeries, TimeSeries)
for s in self.sensors():
self.removeSensor(s)
self.mTimeSeries = timeSeries
self.mTimeSeries.sigSensorAdded.connect(self.addSensor)
self.mTimeSeries.sigSensorRemoved.connect(self.removeSensor)
for s in timeSeries.sensors():
self.addSensor(s)
def timeSeries(self)->TimeSeries:
"""
Returns the TimeSeries this mapview is connected with
:return: TimeSeries
"""
return self.mTimeSeries
def setTitle(self, title:str):
"""
Sets the widget title
:param title: str
"""
self.tbName.setText(title)

benjamin.jakimow@geo.hu-berlin.de
committed
def layers(self)->list:
"""
Returns the visible layers, including proxy layer for time-series data
:return: [list-of-QgsMapLayers]
"""
return [l for l in self.mLayerTree.checkedLayers() if isinstance(l, QgsMapLayer)]
def title(self, maskNewLines=True)->str:
"""
Returns the MapView title
:return: str
"""
if maskNewLines:
return self.tbName.text().replace('\\n', ' ').strip()
else:
return self.tbName.text().strip()
def setCrosshairStyle(self, crosshairStyle:CrosshairStyle)->CrosshairStyle:
"""

Benjamin Jakimow
committed
Seths the CrosshairStyle of this MapView
:param crosshairStyle: CrosshairStyle
"""
if self.mCrossHairStyle != crosshairStyle:
self.mCrossHairStyle = crosshairStyle
self.sigCrosshairChanged.emit()
return self.mCrossHairStyle

benjamin.jakimow@geo.hu-berlin.de
committed
def setHighlighted(self, b=True, timeout=1000):
"""
Activates or deactivates a red-line border of the MapCanvases
:param b: True | False to activate / deactivate the highlighted lines-
:param timeout: int, milliseconds how long the highlighted frame should appear
"""

benjamin.jakimow@geo.hu-berlin.de
committed
styleOn = """.MapCanvas {
border: 4px solid red;
border-radius: 4px;
}"""
styleOff = """"""
if b is True:
for mapCanvas in self.mapCanvases():
mapCanvas.setStyleSheet(styleOn)
if timeout > 0:
QTimer.singleShot(timeout, lambda : self.setHighlighted(False))
else:
for mapCanvas in self.mapCanvases():
mapCanvas.setStyleSheet(styleOff)

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

Benjamin Jakimow
committed
"""
return self.mCrossHairStyle
def setCrosshairVisibility(self, b:bool):

Benjamin Jakimow
committed
"""
Enables / diables the map canvas crosshair.
:param b: bool

Benjamin Jakimow
committed
"""
if b != self.actionToggleCrosshairVisibility.isChecked():
self.actionToggleCrosshairVisibility.setChecked(b)

Benjamin Jakimow
committed
else:
self.mCrossHairStyle.setVisibility(b)
self.sigCrosshairChanged.emit()
def sensorProxyLayers(self)->list:
layers = [n.layer() for n in self.mLayerTreeSensorNode.findLayers()]
return [l for l in layers if isinstance(l, SensorProxyLayer)]
def sensorProxyLayer(self, sensor:SensorInstrument)->SensorProxyLayer:

benjamin.jakimow@geo.hu-berlin.de
committed
"""
Returns the proxy layer related to a SensorInstrument
:param sensor: SensorInstrument
:return: SensorLayer
"""
for l in self.sensorProxyLayers():
if l.sensor() == sensor:
return l
return None
def sensors(self)->list:
"""
Returns a list of SensorsInstruments
:return: [list-of-SensorInstruments]
"""
return [t[0] for t in self.mSensorLayerList]
def addSensor(self, sensor:SensorInstrument):
"""
Adds a SensorInstrument to be shown in this MapView. Each sensor will be represented as a Raster Layer in the
Tree Model.
:param sensor: SensorInstrument
"""
assert isinstance(sensor, SensorInstrument)
if sensor not in self.sensors():
sensor.sigNameChanged.connect(self.sigCanvasAppearanceChanged)
dummyLayer = sensor.proxyLayer()
assert isinstance(dummyLayer.renderer(), QgsRasterRenderer)
dummyLayer.rendererChanged.connect(lambda sensor=sensor: self.onSensorRendererChanged(sensor))

Benjamin Jakimow
committed
#QgsProject.instance().addMapLayer(dummyLayer)
layerTreeLayer = self.mLayerTreeSensorNode.addLayer(dummyLayer)
assert isinstance(layerTreeLayer, QgsLayerTreeLayer)
layerTreeLayer.setCustomProperty(KEY_LOCKED_LAYER, True)
layerTreeLayer.setCustomProperty(KEY_SENSOR_LAYER, True)
self.mSensorLayerList.append((sensor, dummyLayer))
def onSensorRendererChanged(self, sensor:SensorInstrument):
for c in self.sensorCanvases(sensor):
assert isinstance(c, MapCanvas)
c.addToRefreshPipeLine(MapCanvas.Command.RefreshRenderer)
def sensorCanvases(self, sensor:SensorInstrument)->list:
"""
Returns the MapCanvases that show a layer with data for the given ``sensor``
:param sensor: SensorInstrument
:return:
"""
assert isinstance(sensor, SensorInstrument)
return [c for c in self.mapCanvases() if isinstance(c, MapCanvas) and \
isinstance(c.tsd(), TimeSeriesDate) and c.tsd().sensor() == sensor]
def sensorLayer(self, sensor: SensorInstrument):
"""
Returns the QgsRasterLayer that is used a proxy to specify the QgsRasterRenderer for a sensor
:param sensor: SensorInstrument
:return: QgsRasterLayer
"""
assert isinstance(sensor, SensorInstrument)
for t in self.mSensorLayerList:
s, l = t
assert isinstance(s, SensorInstrument)
assert isinstance(l, QgsRasterLayer)
if s == sensor:
return l
raise Exception('Sensor "{}" not registered to MapView "{}"'.format(sensor.name(), self.title()))
def removeSensor(self, sensor:SensorInstrument):
"""
Removes a sensor from this map view

benjamin.jakimow@geo.hu-berlin.de
committed
:param sensor:
:return:
"""
toRemove = []
for t in self.mSensorLayerList:
toRemove.append(t)
for t in toRemove:
self.mLayerTreeSensorNode.removeLayer(t[1])
self.mSensorLayerList.remove(t)

benjamin.jakimow@geo.hu-berlin.de
committed
def hasSensor(self, sensor)->bool:
"""
:param sensor:
:return:
"""
assert isinstance(sensor, SensorInstrument)
return sensor in self.sensors()

benjamin.jakimow@geo.hu-berlin.de
committed
class MapViewListModel(QAbstractListModel):
"""
A model to store a list of map views.

benjamin.jakimow@geo.hu-berlin.de
committed
"""
sigMapViewsAdded = pyqtSignal(list)
sigMapViewsRemoved = pyqtSignal(list)

benjamin.jakimow@geo.hu-berlin.de
committed
def __init__(self, parent=None):
super(MapViewListModel, self).__init__(parent)
self.mMapViewList = []

benjamin.jakimow@geo.hu-berlin.de
committed
def addMapView(self, mapView):
i = len(self.mMapViewList)
self.insertMapView(i, mapView)
def insertMapView(self, i, mapView):
self.insertMapViews(i, [mapView])
def insertMapViews(self, i, mapViews):
assert isinstance(mapViews, list)
assert i >= 0 and i <= len(self.mMapViewList)
self.beginInsertRows(QModelIndex(), i, i + len(mapViews) - 1)

benjamin.jakimow@geo.hu-berlin.de
committed
for j in range(len(mapViews)):
mapView = mapViews[j]
assert isinstance(mapView, MapView)
mapView.sigTitleChanged.connect(
lambda : self.doRefresh([mapView])
)
self.mMapViewList.insert(i + j, mapView)
self.endInsertRows()
self.sigMapViewsAdded.emit(mapViews)

benjamin.jakimow@geo.hu-berlin.de
committed
def doRefresh(self, mapViews):
for mapView in mapViews:
idx = self.mapView2idx(mapView)
self.dataChanged.emit(idx, idx)

benjamin.jakimow@geo.hu-berlin.de
committed
def removeMapView(self, mapView):
self.removeMapViews([mapView])

benjamin.jakimow@geo.hu-berlin.de
committed
def removeMapViews(self, mapViews):
assert isinstance(mapViews, list)
for mv in mapViews:
assert mv in self.mMapViewList
idx = self.mapView2idx(mv)
self.beginRemoveRows(idx.parent(), idx.row(), idx.row())
self.mMapViewList.remove(mv)
self.endRemoveRows()
self.sigMapViewsRemoved.emit(mapViews)

benjamin.jakimow@geo.hu-berlin.de
committed
def rowCount(self, parent=None, *args, **kwargs):
return len(self.mMapViewList)

benjamin.jakimow@geo.hu-berlin.de
committed
def columnCount(self, QModelIndex_parent=None, *args, **kwargs):
return 1
def idx2MapView(self, index):
if isinstance(index, QModelIndex):
if index.isValid():
index = index.row()
else:
return None
assert index >= 0 and index < len(self.mMapViewList)
return self.mMapViewList[index]
def mapView2idx(self, mapView):
assert isinstance(mapView, MapView)
row = self.mMapViewList.index(mapView)
return self.createIndex(row, 0, mapView)

benjamin.jakimow@geo.hu-berlin.de
committed
def __getitem__(self, slice):
return self.mMapViewList[slice]
def data(self, index, role=Qt.DisplayRole):
if not index.isValid():
return None
if (index.row() >= len(self.mMapViewList)) or (index.row() < 0):
return None
mapView = self.idx2MapView(index)
assert isinstance(mapView, MapView)
if role == Qt.DisplayRole:
value = '{} {}'.format(index.row() +1 , mapView.title())
#if role == Qt.DecorationRole:
#value = classInfo.icon(QSize(20,20))
if role == Qt.UserRole:
value = mapView
return value
class MapWidget(QFrame, loadUIFormClass(jp(DIR_UI, 'mapwidget.ui'))):
"""
This widget contains all maps
"""
class ViewMode(enum.Enum):
MapViewByRows = 1,
MapViewByCols = 2
sigCrosshairPositionChanged = pyqtSignal([SpatialPoint], [SpatialPoint, MapCanvas])
sigCRSChanged = pyqtSignal(QgsCoordinateReferenceSystem)

Benjamin Jakimow
committed
sigMapBackgroundColorChanged = pyqtSignal(QColor)
sigMapTextColorChanged = pyqtSignal(QColor)

Benjamin Jakimow
committed
sigMapTextFormatChanged = pyqtSignal(QgsTextFormat)
sigMapsPerMapViewChanged = pyqtSignal(int)
sigMapViewsChanged = pyqtSignal()
sigMapViewAdded = pyqtSignal(MapView)
sigMapViewRemoved = pyqtSignal(MapView)
sigCurrentDateChanged = pyqtSignal(TimeSeriesDate)
sigCurrentLocationChanged = pyqtSignal(SpatialPoint, MapCanvas)
sigVisibleDatesChanged = pyqtSignal(list)
sigViewModeChanged = pyqtSignal(ViewMode)
def __init__(self, *args, **kwds):
super(MapWidget, self).__init__(*args, **kwds)
self.setContentsMargins(1, 1, 1, 1)

Benjamin Jakimow
committed
self.mGrid = self.gridFrame.layout()
assert isinstance(self.mGrid, QGridLayout)
self.mGrid.setSpacing(0)
self.mGrid.setContentsMargins(0, 0, 0, 0)
self.mSyncLock = False

Benjamin Jakimow
committed
self.mMaxNumberOfCachedLayers = 0
self.mMapLayerStore = QgsMapLayerStore()

Benjamin Jakimow
committed
self.mMapLayerCache = dict()
self.mCanvasCache = dict()
self.mMapViews = []
self.mCanvases = dict()
self.mTimeSeries = None
self.mMapToolKey = MapTools.Pan
self.mViewMode = MapWidget.ViewMode.MapViewByRows
self.mMpMV = 3
self.mSpatialExtent = SpatialExtent.world()
self.mCrs = self.mSpatialExtent.crs()
self.mCurrentDate = None
self.mCrosshairPosition = None

benjamin.jakimow@geo.hu-berlin.de
committed
self.mMapSize = QSize(200, 200)

Benjamin Jakimow
committed
from eotimeseriesviewer.settings import DEFAULT_VALUES, Keys
self.mMapTextFormat = DEFAULT_VALUES[Keys.MapTextFormat]
self.mMapRefreshTimer = QTimer(self)
self.mMapRefreshTimer.timeout.connect(self.timedRefresh)
self.mMapRefreshTimer.setInterval(500)
self.mMapRefreshTimer.start()

Benjamin Jakimow
committed
self.btnFirst.setDefaultAction(self.actionFirstDate)
self.btnLast.setDefaultAction(self.actionLastDate)
self.btnBackward.setDefaultAction(self.actionBackward)
self.btnForward.setDefaultAction(self.actionForward)
self.btnBackwardFast.setDefaultAction(self.actionBackwardFast)
self.btnForwardFast.setDefaultAction(self.actionForwardFast)
self.actionFirstDate.triggered.connect(self.moveToFirstTSD)
self.actionLastDate.triggered.connect(self.moveToLastTSD)
self.actionBackward.triggered.connect(self.moveToPreviousTSD)
self.actionForward.triggered.connect(self.moveToNextTSD)
self.actionBackwardFast.triggered.connect(self.moveToPreviousTSDFast)
self.actionForwardFast.triggered.connect(self.moveToNextTSDFast)

Benjamin Jakimow
committed
self.mTimeSlider.valueChanged.connect(self.onSliderReleased)
def messageBar(self)->QgsMessageBar:
"""
Returns the QgsMessageBar
:return: QgsMessageBar
"""
return self.mMessageBar
def refresh(self):
for c in self.mapCanvases():
assert isinstance(c, MapCanvas)
c.timedRefresh()

Benjamin Jakimow
committed
def setMapTextFormat(self, textFormat:QgsTextFormat)->QgsTextFormat:
if not equalTextFormats(textFormat, self.mMapTextFormat):
self.mMapTextFormat = textFormat
for mapView in self.mapViews():
assert isinstance(mapView, MapView)
mapView.setMapTextFormat(textFormat)
self.sigMapTextFormatChanged.emit(self.mapTextFormat())
return self.mapTextFormat()
def mapTextFormat(self)->QgsTextFormat:
return self.mMapTextFormat
def setMapTool(self, mapToolKey:MapTools):
if self.mMapToolKey != mapToolKey:
self.mMapToolKey = mapToolKey

Benjamin Jakimow
committed
for c in self.mapCanvases():
assert isinstance(c, MapCanvas)
mts = c.mapTools()
mts.activate(self.mMapToolKey)
def visibleTSDs(self)->list:
"""
Returns the list of currently shown TimeSeriesDates.
:return: [list-of-TimeSeriesDates]
"""
for mv in self.mMapViews:
tsds = []
for c in self.mCanvases[mv]:
if isinstance(c.tsd(), TimeSeriesDate):
tsds.append(c.tsd())
return sorted(tsds)
return []
def spatialExtent(self)->SpatialExtent:
"""
Returns the current SpatialExtent
:return: SpatialExtent
"""
return self.mSpatialExtent
def setSpatialExtent(self, extent:SpatialExtent)->SpatialExtent:
"""
Sets a SpatialExtent to all MapCanvases.
:param extent: SpatialExtent
:return: SpatialExtent the current SpatialExtent
"""
if self.mSpatialExtent != extent:
self.mSpatialExtent = extent

benjamin.jakimow@geo.hu-berlin.de
committed
for c in self.mapCanvases():
assert isinstance(c, MapCanvas)
c.addToRefreshPipeLine(extent)

benjamin.jakimow@geo.hu-berlin.de
committed
self.sigSpatialExtentChanged.emit(self.mSpatialExtent.__copy__())
return self.spatialExtent()

benjamin.jakimow@geo.hu-berlin.de
committed
def setSpatialCenter(self, centerNew:SpatialPoint):
"""
Sets the spatial center of all MapCanvases
:param centerNew: SpatialPoint
"""
assert isinstance(centerNew, SpatialPoint)
extent = self.spatialExtent()
if isinstance(extent, SpatialExtent):
centerOld = extent.center()
centerNew = centerNew.toCrs(extent.crs())
if centerNew != centerOld and isinstance(centerNew, SpatialPoint):
extent = extent.__copy__()
extent.setCenter(centerNew)
self.setSpatialExtent(extent)

benjamin.jakimow@geo.hu-berlin.de
committed
def spatialCenter(self)->SpatialPoint:
"""
Return the center of all map canvas
:return: SpatialPoint
"""
return self.spatialExtent().spatialCenter()

benjamin.jakimow@geo.hu-berlin.de
committed
def setCrs(self, crs:QgsCoordinateReferenceSystem)->QgsCoordinateReferenceSystem:
Sets the MapCanvas CRS.
:param crs: QgsCoordinateReferenceSystem
:return: QgsCoordinateReferenceSystem

benjamin.jakimow@geo.hu-berlin.de
committed
self.mCrs = crs
if isinstance(crs, QgsCoordinateReferenceSystem):
for c in self.mapCanvases():
c.setCrs(crs)