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
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)
sigMapViewVisibility = pyqtSignal(bool)
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
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)
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
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)
if changed:
self.sigMapViewVisibility.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)
def registerMapCanvas(self, mapCanvas:MapCanvas):
"""
Registers a new MapCanvas to this MapView
:param sensor:
:param mapCanvas:
:return:
"""
from eotimeseriesviewer.mapcanvas import MapCanvas
assert isinstance(mapCanvas, MapCanvas)
mapCanvas.sigCrosshairVisibilityChanged.connect(self.setCrosshairVisibility)
mapCanvas.sigCrosshairStyleChanged.connect(self.setCrosshairStyle)
self.mMapCanvases.append(mapCanvas)
self.sigMapViewVisibility.connect(mapCanvas.setEnabled)

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:
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.setCrosshairVisibility(b, emitSignal=False)

Benjamin Jakimow
committed
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
"""
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
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 DatumViewUI(QFrame, loadUI('timeseriesdateview.ui')):
"""
Widget to host the MapCanvases of all map views that relate to a single Datum-Sensor combinbation.
"""
def __init__(self, title='<#>', parent=None):
super(DatumViewUI, self).__init__(parent)
self.setupUi(self)
# do not show progress bar
self.progressBar.setVisible(False)
def sizeHint(self):
m = self.layout().contentsMargins()
s = QSize(0, 0)
map = None
widgets = [self.layout().itemAt(i).widget() for i in range(self.layout().count())]
widgets = [w for w in widgets if isinstance(w, QWidget)]
maps = [w for w in widgets if isinstance(w, MapCanvas)]
others = [w for w in widgets if not isinstance(w, MapCanvas)]
s = self.layout().spacing()
m = self.layout().contentsMargins()
def totalHeight(widgetList):
total = QSize(0,0)
for w in widgetList:
ws = w.size()
if ws.width() == 0:
ws = w.sizeHint()
if w.isVisible():
total.setWidth(max([total.width(), ws.width()]))
total.setHeight(total.height() + ws.height())
return total
baseSize = totalHeight(widgets)
if baseSize.width() == 0:
for o in others:
baseSize.setWidth(9999)
s = QSize(baseSize.width() + m.left() + m.right(),
baseSize.height() + m.top() + m.bottom())
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
return s
"""
def sizeHint(self):
if not self.ui.isVisible():
return QSize(0,0)
else:
#return self.ui.sizeHint()
size = self.ui.sizeHint()
s = self.ui.layout().spacing()
m = self.ui.layout().contentsMargins()
dx = m.left() + m.right() + s
dy = self.ui.layout().spacing()
n = len([m for m in self.mapCanvases.keys() if m.isVisible()])
if n > 0:
baseSize = self.mapCanvases.values()[0].size()
size = QSize(baseSize.width()+ dx, \
size.height()+ (n+1)*(dy+2*s))
else:
s = ""
return size
"""

benjamin.jakimow@geo.hu-berlin.de
committed
sigRenderProgress = pyqtSignal(int,int)
sigLoadingStarted = pyqtSignal(MapView, TimeSeriesDate)
sigLoadingFinished = pyqtSignal(MapView, TimeSeriesDate)

benjamin.jakimow@geo.hu-berlin.de
committed
sigVisibilityChanged = pyqtSignal(bool)
def __init__(self, timeSeriesDate:TimeSeriesDate, stv, parent=None):
assert isinstance(timeSeriesDate, TimeSeriesDate)

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

benjamin.jakimow@geo.hu-berlin.de
committed
self.ui.create()
self.showLoading(False)
self.wOffset = self.ui.layout().count()-1

benjamin.jakimow@geo.hu-berlin.de
committed
self.minHeight = self.ui.height()

benjamin.jakimow@geo.hu-berlin.de
committed
self.renderProgress = dict()
assert isinstance(stv, SpatialTemporalVisualization)
self.STV = stv
self.mSensor.sigNameChanged.connect(lambda :self.setColumnInfo())
self.TSD.sigVisibilityChanged.connect(self.setVisibility)

benjamin.jakimow@geo.hu-berlin.de
committed
self.setColumnInfo()
self.mRenderState = dict()

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

benjamin.jakimow@geo.hu-berlin.de
committed
def setColumnInfo(self):
labelTxt = '{}\n{}'.format(str(self.TSD.mDate), self.TSD.mSensor.name())
tooltip = '\n'.join([tss.uri()for tss in self.TSD.sources()])

benjamin.jakimow@geo.hu-berlin.de
committed
self.ui.labelTitle.setText(labelTxt)
self.ui.labelTitle.setToolTip(tooltip)

benjamin.jakimow@geo.hu-berlin.de
committed
def setVisibility(self, b):
self.ui.setVisible(b)
self.sigVisibilityChanged.emit(b)

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

benjamin.jakimow@geo.hu-berlin.de
committed
def removeMapView(self, mapView):
self.ui.layout().removeWidget(canvas)

benjamin.jakimow@geo.hu-berlin.de
committed
canvas.close()
def mapCanvases(self)->list:
"""
Retuns the MapCanvases of this DataView
:return: [list-of-MapCanvases]
"""
return self.mMapCanvases.values()
def refresh(self):
"""
Refreshes the MapCanvases in this DatumView, if they are not hidden behind a scroll area.
"""

benjamin.jakimow@geo.hu-berlin.de
committed
if self.ui.isVisible():
for c in self.mapCanvases():

benjamin.jakimow@geo.hu-berlin.de
committed
if c.isVisible():

benjamin.jakimow@geo.hu-berlin.de
committed
def insertMapView(self, mapView):
assert isinstance(mapView, MapView)
from eotimeseriesviewer.mapcanvas import MapCanvas

benjamin.jakimow@geo.hu-berlin.de
committed
mapCanvas.setObjectName('MapCanvas {} {}'.format(mapView.title(), self.TSD.mDate))

Benjamin Jakimow
committed
self.registerMapCanvas(mapView, mapCanvas)
mapCanvas.setMapView(mapView)
mapCanvas.setTSD(self.TSD)
mapView.registerMapCanvas(mapCanvas)
mapCanvas.renderComplete.connect(lambda: self.onRenderingChange(False))
mapCanvas.renderStarting.connect(lambda: self.onRenderingChange(True))
#mapCanvas.sigMapRefreshed[float, float].connect(
# lambda dt: self.STV.TSV.ui.dockSystemInfo.addTimeDelta('Map {}'.format(self.mSensor.name()), dt))
#mapCanvas.sigMapRefreshed.connect(
# lambda dt: self.STV.TSV.ui.dockSystemInfo.addTimeDelta('All Sensors', dt))

benjamin.jakimow@geo.hu-berlin.de
committed
def showLoading(self, b):
if b:
self.ui.progressBar.setRange(0, 0)
self.ui.progressBar.setValue(-1)
else:
self.ui.progressBar.setRange(0,1)
self.ui.progressBar.setValue(0)
def onRenderingChange(self, b):
mc = self.sender()
#assert isinstance(mc, QgsMapCanvas)
self.mRenderState[mc] = b
self.showLoading(any(self.mRenderState.values()))
def onRendering(self, *args):
renderFlags = [m.renderFlag() for m in self.mMapCanvases.values()]
drawFlags = [m.isDrawing() for m in self.mMapCanvases.values()]

benjamin.jakimow@geo.hu-berlin.de
committed
# print((renderFlags, drawFlags))
isLoading = any(renderFlags)
self.showLoading(isLoading)
s = ""

benjamin.jakimow@geo.hu-berlin.de
committed
def registerMapCanvas(self, mapView, mapCanvas):
from eotimeseriesviewer.mapcanvas import MapCanvas

Benjamin Jakimow
committed
mapCanvas.setVisible(mapView.isVisible())
#mapView.sigTitleChanged.connect(lambda title : mapCanvas.setSaveFileName('{}_{}'.format(self.TSD.date, title)))
#mapCanvas.mapLayerModel().addMapLayerSources(self.TSD.qgsMimeDataUtilsUris())
#self.ui.layout().insertWidget(self.wOffset + len(self.mapCanvases), mapCanvas)
self.ui.layout().insertWidget(self.ui.layout().count() - 1, mapCanvas)
self.ui.update()
#register signals handled on (this) DV level
mapCanvas.renderStarting.connect(lambda: self.sigLoadingStarted.emit(mapView, self.TSD))
mapCanvas.mapCanvasRefreshed.connect(lambda: self.sigLoadingFinished.emit(mapView, self.TSD))
#mapCanvas.sigShowProfiles.connect(lambda c, t : mapView.sigShowProfiles.emit(c,mapCanvas, t))
#mapCanvas.sigChangeDVRequest.connect(self.onMapCanvasRequest)

benjamin.jakimow@geo.hu-berlin.de
committed
def __lt__(self, other):
assert isinstance(other, DatumView)
return self.TSD < other.TSD

benjamin.jakimow@geo.hu-berlin.de
committed
def __eq__(self, other):
assert isinstance(other, DatumView)
return self.TSD == other.TSD

benjamin.jakimow@geo.hu-berlin.de
committed
sigLoadingStarted = pyqtSignal(MapView, TimeSeriesDate)
sigLoadingFinished = pyqtSignal(MapView, TimeSeriesDate)
sigShowProfiles = pyqtSignal(SpatialPoint)
sigSpatialExtentChanged = pyqtSignal(SpatialExtent)
def __init__(self, STViz):
assert isinstance(STViz, SpatialTemporalVisualization)
super(DateViewCollection, self).__init__()
#self.tsv = tsv
#self.timeSeries = tsv.TS
self.mViews = list()
self.STV = STViz
self.ui = self.STV.targetLayout.parentWidget()
self.scrollArea = self.ui.parentWidget().parentWidget()
#potentially there are many more dates than views.
#therefore we implement the addinng/removing of mapviews here
#we reduce the number of layout refresh calls by
#suspending signals, adding the new map view canvases, and sending sigResizeRequired
self.STV.mMapViewDock.sigMapViewAdded.connect(self.addMapView)
self.STV.mMapViewDock.sigMapViewRemoved.connect(self.removeMapView)

benjamin.jakimow@geo.hu-berlin.de
committed
def tsdFromMapCanvas(self, mapCanvas):
assert isinstance(mapCanvas, MapCanvas)
for view in self.mViews:
assert isinstance(view, DatumView)
if mapCanvas in view.mMapCanvases.values():
return view.TSD
return None
def tsdView(self, tsd):
r = [v for v in self.mViews if v.TSD == tsd]
if len(r) == 1:
return r[0]
else:
raise Exception('TSD not in list')

benjamin.jakimow@geo.hu-berlin.de
committed
def addMapView(self, mapView):
assert isinstance(mapView, MapView)
w = self.ui
#w.setUpdatesEnabled(False)
#for tsdv in self.mViews:
# tsdv.ui.setUpdatesEnabled(False)
#for tsdv in self.mViews:
# tsdv.ui.setUpdatesEnabled(True)
#mapView.sigSensorRendererChanged.connect(lambda *args : self.setRasterRenderer(mapView, *args))
mapView.sigMapViewVisibility.connect(lambda: self.sigResizeRequired.emit())
mapView.sigShowProfiles.connect(self.sigShowProfiles.emit)
#w.setUpdatesEnabled(True)
def removeMapView(self, mapView):
assert isinstance(mapView, MapView)
for tsdv in self.mViews:
tsdv.removeMapView(mapView)
self.sigResizeRequired.emit()
def highlightDate(self, tsd):
"""
Highlights a time series data for a specific time our
:param tsd:
:return:
"""
tsdView = self.tsdView(tsd)
if isinstance(tsdView, DatumView):
tsdView.setHighlight(True)
def orderedViews(self):
#returns the
if self.focusView is not None:
assert isinstance(self.focusView, DatumView)
return sorted(self.mViews, key=lambda v: np.abs(v.TSD.date - self.focusView.TSD.date))
else:
return self.mViews
"""
def setSubsetSize(self, size):
assert isinstance(size, QSize)
self.subsetSize = size
for tsdView in self.orderedViews():
tsdView.blockSignals(True)
for tsdView in self.orderedViews():
tsdView.setSubsetSize(size)
for tsdView in self.orderedViews():
tsdView.blockSignals(False)
"""

Benjamin Jakimow
committed