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
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'
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
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
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))
152
153
154
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
#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
"""

benjamin.jakimow@geo.hu-berlin.de
committed
sigRemoveMapView = pyqtSignal(object)
sigMapViewVisibilityChanged = pyqtSignal(bool)
sigCrosshairVisibilityChanged = pyqtSignal(bool)

benjamin.jakimow@geo.hu-berlin.de
committed
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
m = QMenu(self.btnToggleCrosshair)
m.addAction(self.actionSetCrosshairStyle)
self.btnToggleCrosshair.setMenu(m)
self.btnToggleCrosshair.setDefaultAction(self.actionToggleCrosshairVisibility)
self.btnToggleMapViewVisibility.setDefaultAction(self.actionToggleMapViewHidden)
self.tbName.textChanged.connect(self.onTitleChanged)
self.actionSetCrosshairStyle.triggered.connect(self.onChangeCrosshairStyle)
self.actionToggleMapViewHidden.toggled.connect(lambda isHidden: self.setVisibility(not isHidden))
self.actionToggleCrosshairVisibility.toggled.connect(self.setCrosshairVisibility)
self.actionAddVectorLayer.triggered.connect(self.onAddVectorLayer)
self.actionAddRasterLayer.triggered.connect(self.onAddRasterLayer)
self.btnAddVectorLayer.setDefaultAction(self.actionAddVectorLayer)
self.btnAddRasterLayer.setDefaultAction(self.actionAddRasterLayer)
self.btnHighlightMapView.setDefaultAction(self.actionHighlightMapView)
self.actionHighlightMapView.triggered.connect(lambda: self.setHighlighted(True, timeout=500))
self.mTimeSeries = None
self.mSensorLayerList = list()
self.mMapCanvases = list()
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)
fixMenuButtons(self)
def setName(self, name:str):
self.setTitle(name)
def name(self)->str:
return self.title()
def visibleMapCanvases(self)->list:
"""
Returns the currently visible mapcanvases
:return: [list-of-MapCanvases]
"""
return [m for m in self.mapCanvases() if m.isVisibleToViewport()]
def onAddVectorLayer(self):
"""
Slot that opens a SelectMapLayersDialog to add a vector layer
"""
from .externals.qps.utils import SelectMapLayersDialog
d = SelectMapLayersDialog()
d.setWindowTitle('Select Vector Layer')
d.addLayerDescription('Vector Layer', QgsMapLayerProxyModel.VectorLayer)
if d.exec() == QDialog.Accepted:
for l in d.mapLayers():
self.addLayer(l)
def onAddRasterLayer(self):
"""
Slot that opens a SelectMapLayersDialog to add a vector layer
"""
from .externals.qps.utils import SelectMapLayersDialog
d = SelectMapLayersDialog()
d.setWindowTitle('Select Raster Layer')
d.addLayerDescription('Raster Layer', QgsMapLayerProxyModel.RasterLayer)
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)
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
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):
style = getCrosshairStyle(parent=self, crosshairStyle=self.crosshairStyle())
if isinstance(style, CrosshairStyle):
self.setCrosshairStyle(style)
def setVisibility(self, b: bool):

Benjamin Jakimow
committed
"""
Sets the map view visibility
:param b: bool
"""
for mapCanvas in self.mapCanvases():
assert isinstance(mapCanvas, MapCanvas)
if self.actionToggleMapViewHidden.isChecked() == b:
self.actionToggleMapViewHidden.setChecked(not b)
self.sigMapViewVisibilityChanged.emit(b)

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
:return: [list-of-MapCanvases]
"""
return self.mMapCanvases[:]
def onTitleChanged(self, *args):
self.setWindowTitle('Map View "{}"'.format(self.title()))
self.sigTitleChanged.emit(self.title())
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)->str:
"""
Returns the MapView title
:return: str
"""
return self.tbName.text()

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

Benjamin Jakimow
committed
def refreshMapView(self, sensor=None):
for mapCanvas in self.mapCanvases():
if isinstance(mapCanvas, MapCanvas):
mapCanvas.refresh()
def setCrosshairStyle(self, crosshairStyle:CrosshairStyle):
"""

Benjamin Jakimow
committed
Seths the CrosshairStyle of this MapView
:param crosshairStyle: CrosshairStyle
"""
from eotimeseriesviewer import CrosshairStyle
assert isinstance(crosshairStyle, CrosshairStyle)
srcCanvas = self.sender()
if isinstance(srcCanvas, MapCanvas):
dstCanvases = [c for c in self.mapCanvases() if c != srcCanvas]
else:
dstCanvases = [c for c in self.mapCanvases()]
for mapCanvas in dstCanvases:
mapCanvas.setCrosshairStyle(crosshairStyle, emitSignal=False)

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:
"""
for c in self.mapCanvases():
assert isinstance(c, MapCanvas)
style = c.crosshairStyle()
if isinstance(style, CrosshairStyle):
return style
return None
def setCrosshairVisibility(self, b:bool):

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

Benjamin Jakimow
committed
"""
# set the action checked state first
if self.actionToggleCrosshairVisibility.isChecked() != b:
self.actionToggleCrosshairVisibility.setChecked(b)

Benjamin Jakimow
committed
else:
self.sigCrosshairVisibilityChanged.emit(b)
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
"""
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
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():
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 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:
"""
pair = None
for i, t in enumerate(self.mSensorLayerList):
if t[0] == sensor:
pair = t
break
assert pair is not None, 'Sensor "{}" not found'.format(sensor.name())
self.mLayerTreeSensorNode.removeLayer(pair[1])
self.mSensorLayerList.remove(pair)

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 MapViewDock(QgsDockWidget, loadUI('mapviewdock.ui')):
sigMapViewAdded = pyqtSignal(MapView)
sigMapViewRemoved = pyqtSignal(MapView)
sigShowProfiles = pyqtSignal(SpatialPoint, MapCanvas, str)
sigMapCanvasColorChanged = pyqtSignal(QColor)
sigSpatialExtentChanged = pyqtSignal(SpatialExtent)
sigCrsChanged = pyqtSignal(QgsCoordinateReferenceSystem)
sigMapSizeChanged = pyqtSignal(QSize)
sigMapsPerMapViewChanged = pyqtSignal(int)
def setTimeSeries(self, timeSeries:TimeSeries):
assert isinstance(timeSeries, TimeSeries)
self.mTimeSeries = timeSeries
self.mTimeSeries.sigSensorAdded.connect(self.addSensor)
self.mTimeSeries.sigSensorRemoved.connect(self.removeSensor)
def __init__(self, parent=None):
super(MapViewDock, self).__init__(parent)
self.setupUi(self)
self.btnAddMapView.setDefaultAction(self.actionAddMapView)
self.btnRemoveMapView.setDefaultAction(self.actionRemoveMapView)
self.btnCrs.crsChanged.connect(self.sigCrsChanged)
self.btnMapCanvasColor.colorChanged.connect(self.onMapCanvasBackgroundColorChanged)
self.btnApplySizeChanges.clicked.connect(lambda : self.sigMapSizeChanged.emit(QSize(self.spinBoxMapSizeX.value(),self.spinBoxMapSizeY.value())))
self.actionAddMapView.triggered.connect(self.createMapView)
self.actionRemoveMapView.triggered.connect(lambda : self.removeMapView(self.currentMapView()) if self.currentMapView() else None)
self.actionApplyStyles.triggered.connect(self.refreshCurrentMapView)
self.toolBox.currentChanged.connect(self.onToolboxIndexChanged)
self.spinBoxMapSizeX.valueChanged.connect(lambda: self.onMapSizeChanged('X'))
self.spinBoxMapSizeY.valueChanged.connect(lambda: self.onMapSizeChanged('Y'))
self.mLastMapSize = self.mapSize()

benjamin.jakimow@geo.hu-berlin.de
committed
self.sbMpMV.valueChanged.connect(self.sigMapsPerMapViewChanged)
"""
Returns the defined MapViews
:return: [list-of-MapViews]
"""
assert isinstance(self.toolBox, QToolBox)
mapViews = []
for i in range(self.toolBox.count()):
item = self.toolBox.widget(i)
if isinstance(item, MapView):
mapViews.append(item)
return mapViews

Benjamin Jakimow
committed
"""
Returns all MapCanvases from all MapViews
:return: [list-of-MapCanvases]

Benjamin Jakimow
committed
"""
maps = []
for mapView in self.mapViews():
assert isinstance(mapView, MapView)
maps.extend(mapView.mapCanvases())
return maps

Benjamin Jakimow
committed
if isinstance(crs, QgsCoordinateReferenceSystem):
old = self.btnCrs.crs()
if old != crs:
self.btnCrs.setCrs(crs)
self.btnCrs.setLayerCrs(crs)
def mapsPerMapView(self)->int:
return self.sbMpMV.value()
def setMapsPerMapView(self, n:int):
assert n > 0
self.sbMpMV.setValue(n)
def setMapSize(self, size):
assert isinstance(size, QSize)
ws = [self.spinBoxMapSizeX, self.spinBoxMapSizeY]
oldSize = self.mapSize()
b = oldSize != size
for w in ws:
w.blockSignals(True)
self.spinBoxMapSizeX.setValue(size.width()),
self.spinBoxMapSizeY.setValue(size.height())
self.mLastMapSize = QSize(size)
for w in ws:
w.blockSignals(False)
self.mLastMapSize = QSize(size)
if b:
self.sigMapSizeChanged.emit(size)
def onMapSizeChanged(self, dim):
newSize = self.mapSize()
#1. set size of other dimension accordingly
if dim is not None:
if self.checkBoxKeepSubsetAspectRatio.isChecked():
if dim == 'X':
vOld = self.mLastMapSize.width()
vNew = newSize.width()
targetSpinBox = self.spinBoxMapSizeY
elif dim == 'Y':
vOld = self.mLastMapSize.height()
vNew = newSize.height()
targetSpinBox = self.spinBoxMapSizeX

benjamin.jakimow@geo.hu-berlin.de
committed
oldState = targetSpinBox.blockSignals(True)
targetSpinBox.setValue(int(round(float(vNew) / vOld * targetSpinBox.value())))
targetSpinBox.blockSignals(oldState)
newSize = self.mapSize()
if newSize != self.mLastMapSize:
self.btnApplySizeChanges.setEnabled(True)
else:
self.sigMapSizeChanged.emit(self.mapSize())
self.btnApplySizeChanges.setEnabled(False)
self.setMapSize(newSize)

benjamin.jakimow@geo.hu-berlin.de
committed
def mapSize(self)->QSize:
return QSize(self.spinBoxMapSizeX.value(),
self.spinBoxMapSizeY.value())

benjamin.jakimow@geo.hu-berlin.de
committed
def refreshCurrentMapView(self, *args):
mv = self.currentMapView()
if isinstance(mv, MapView):
mv.refreshMapView()
else:
s =""

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

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

benjamin.jakimow@geo.hu-berlin.de
committed
for mapView in mapViews:
idx = self.stackedWidget.indexOf(mapView.ui)
if idx >= 0:
self.stackedWidget.removeWidget(mapView.ui)
mapView.ui.close()
else:
s = ""

benjamin.jakimow@geo.hu-berlin.de
committed
self.actionRemoveMapView.setEnabled(len(self.mMapViews) > 0)

benjamin.jakimow@geo.hu-berlin.de
committed
def mapBackgroundColor(self)->QColor:
"""
Returns the map canvas background color
:return: QColor
"""
return self.btnMapCanvasColor.color()

benjamin.jakimow@geo.hu-berlin.de
committed
def setMapBackgroundColor(self, color:QColor):
"""
Sets the MapCanvas background color
:param color: QColor
"""
if color != self.mapBackgroundColor():
self.btnMapCanvasColor.setColor(color)

benjamin.jakimow@geo.hu-berlin.de
committed
def onMapCanvasBackgroundColorChanged(self, color:QColor):
"""
Reacts on a changes map color
:param color: QColor
"""
assert isinstance(color, QColor)
self.mColor = color
for mapCanvas in self.mapCanvases():
assert isinstance(mapCanvas, MapCanvas)
mapCanvas.addToRefreshPipeLine(color)

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

benjamin.jakimow@geo.hu-berlin.de
committed
def onMapViewsAdded(self, mapViews):
nextShown = None
for mapView in mapViews:
mapView.sigTitleChanged.connect(self.updateTitle)
self.stackedWidget.addWidget(mapView.ui)
if nextShown is None:
nextShown = mapView

benjamin.jakimow@geo.hu-berlin.de
committed
contents = mapView.ui.scrollAreaWidgetContents
size = contents.size()
hint = contents.sizeHint()
#mapView.ui.scrollArea.update()
s = ""
#setMinimumSize(mapView.ui.scrollAreaWidgetContents.sizeHint())
#hint = contents.sizeHint()
#contents.setMinimumSize(hint)
if isinstance(nextShown, MapView):
self.setCurrentMapView(nextShown)

benjamin.jakimow@geo.hu-berlin.de
committed
for mapView in mapViews:
self.sigMapViewAdded.emit(mapView)

benjamin.jakimow@geo.hu-berlin.de
committed
def updateButtons(self, *args):
b = len(self.mMapViews) > 0
self.actionRemoveMapView.setEnabled(b)
self.actionApplyStyles.setEnabled(b)
self.actionHighlightMapView.setEnabled(b)

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

Benjamin Jakimow
committed
def createMapView(self, name:str=None)->MapView:
"""
Create a new MapView
:return: MapView
"""

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

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

Benjamin Jakimow
committed
if isinstance(name, str) and len(name) > 0:
title = name
else:

Benjamin Jakimow
committed
while title in [m.title() for m in self.mapViews()]:
n += 1
title = 'Map View {}'.format(n)

benjamin.jakimow@geo.hu-berlin.de
committed
if n == 1:
mapView.optionShowDate.setChecked(True)
mapView.optionShowSensorName.setChecked(True)
mapView.setTitle(title)
mapView.sigShowProfiles.connect(self.sigShowProfiles)
mapView.setTimeSeries(self.mTimeSeries)
self.addMapView(mapView)
return mapView

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

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

benjamin.jakimow@geo.hu-berlin.de
committed
"""
assert isinstance(mapView, MapView)
mapView.sigTitleChanged.connect(lambda *args, mv=mapView : self.onMapViewUpdated(mv))