Commit ed6bf9ea authored by Benjamin Jakimow's avatar Benjamin Jakimow
Browse files

Merge branch 'develop' of bitbucket.org:jakimowb/eo-time-series-viewer into develop

parents 9dedcd47 03151699
......@@ -337,5 +337,25 @@ The spectral library view allows you to visualize, label and export spectral pro
Quick Labeling
--------------
The EO Time Series Viewer assists you in describing, i.e. *label* reference data based on the time series visualize in the EO Time Series Viewer,
Basically, there are possible labeling scenarios:
a) You need to identify spatial objects of interest and annotate them with one or multiple attributes. For example, you use the EO Time series viewer
to identify exemplary land cover types, digitize them as polygons and annotate these polygons with a land cover type ("forest") and the year of observation ("2014")
b) The spatial objects of interest already exists, but you need to annotate them, for example to annotate a set of random sampled points whether they got deforested or not,
e.g. for an map accuracy assessment.
In both cases, you like to fill the attribute table of a vector layer based on temporal and contextual information.
The EO Time Series Viewer supports this work by "Quick Label" short-cuts which automatically fill the attributes values
for features selected in the vector layer.
......@@ -23,11 +23,10 @@
import os
import sys
import site
from qgis.gui import *
from qgis.core import *
from qgis.PyQt.QtCore import *
from qgis.PyQt.QtGui import *
from qgis.PyQt.QtWidgets import *
from qgis.gui import QgisInterface
class EOTimeSeriesViewerPlugin:
......
......@@ -30,7 +30,6 @@ import typing
import warnings
from qgis.core import QgsApplication, Qgis
from qgis.gui import QgsMapLayerConfigWidgetFactory
import qgis.utils
MIN_QGIS_VERSION = '3.14'
__version__ = '1.0'
......@@ -40,7 +39,7 @@ DIR_UI_FILES = DIR_QPS / 'ui'
DIR_ICONS = DIR_UI_FILES / 'icons'
QPS_RESOURCE_FILE = DIR_QPS / 'qpsresources_rc.py'
MAPLAYER_CONFIGWIDGET_FACTORIES: typing.Dict[str, QgsMapLayerConfigWidgetFactory] = dict()
MAPLAYER_CONFIGWIDGET_FACTORIES = list()
if Qgis.QGIS_VERSION < MIN_QGIS_VERSION:
warnings.warn(f'Your QGIS ({Qgis.QGIS_VERSION}) is outdated. '
......@@ -55,11 +54,9 @@ def registerMapLayerConfigWidgetFactory(factory: QgsMapLayerConfigWidgetFactory)
:return:
:rtype:
"""
global MAPLAYER_CONFIGWIDGET_FACTORIES
assert isinstance(factory, QgsMapLayerConfigWidgetFactory)
if factory.title() not in MAPLAYER_CONFIGWIDGET_FACTORIES.keys():
MAPLAYER_CONFIGWIDGET_FACTORIES[factory.title()] = factory
qgis.utils.iface.registerMapLayerConfigWidgetFactory(factory)
if factory not in MAPLAYER_CONFIGWIDGET_FACTORIES:
MAPLAYER_CONFIGWIDGET_FACTORIES.append(factory)
def unregisterMapLayerConfigWidgetFactory(factory: QgsMapLayerConfigWidgetFactory):
......@@ -70,11 +67,9 @@ def unregisterMapLayerConfigWidgetFactory(factory: QgsMapLayerConfigWidgetFactor
:return:
:rtype:
"""
global MAPLAYER_CONFIGWIDGET_FACTORIES
assert isinstance(factory, QgsMapLayerConfigWidgetFactory)
if factory.title() in MAPLAYER_CONFIGWIDGET_FACTORIES.keys():
MAPLAYER_CONFIGWIDGET_FACTORIES.pop(factory.title())
qgis.utils.iface.unregisterMapLayerConfigWidgetFactory(factory)
while factory in MAPLAYER_CONFIGWIDGET_FACTORIES:
MAPLAYER_CONFIGWIDGET_FACTORIES.remove(factory)
def mapLayerConfigWidgetFactories() -> typing.List[QgsMapLayerConfigWidgetFactory]:
......@@ -83,8 +78,7 @@ def mapLayerConfigWidgetFactories() -> typing.List[QgsMapLayerConfigWidgetFactor
:return: list of QgsMapLayerConfigWidgetFactories
:rtype:
"""
global MAPLAYER_CONFIGWIDGET_FACTORIES
return list(MAPLAYER_CONFIGWIDGET_FACTORIES.values())
return MAPLAYER_CONFIGWIDGET_FACTORIES[:]
def registerEditorWidgets():
......
......@@ -94,6 +94,52 @@ class VectorValueSet(SourceValueSet):
self.features.append(featureInfo)
class PixelPositionTreeNode(TreeNode):
def __init__(self, px: QPoint, coord: SpatialPoint, *args,
name: str = 'Pixel',
zeroBased: bool = True, **kwds):
super().__init__(*args, **kwds)
self.setName(name)
self.mPx: QPoint = px
self.mGeo: SpatialPoint = coord
self.mZeroBased = zeroBased
if self.mZeroBased:
self.setValues([(px.x(), px.y())])
self.setToolTip(f'Pixel Coordinate (upper-left = (0,0) )')
else:
self.setValues([(px.x() + 1, px.y() + 1)])
self.setToolTip(f'Pixel Coordinate (upper-left = (1,1) )')
def populateContextMenu(self, menu: QMenu):
a = menu.addAction('Copy pixel coordinate (0-based)')
a.setToolTip('Copies the zero-based pixel coordinate (first/upper-left pixel = 0,0)')
a.triggered.connect(lambda *args, txt=f'{self.mPx.x()}, {self.mPx.y()}':
self.onCopyToClipBoad(txt))
a = menu.addAction('Copy pixel coordinate (1-based)')
a.setToolTip('Copies the one-based pixel coordinate (first/upper-left pixel = 1,1)')
a.triggered.connect(lambda *args, txt=f'{self.mPx.x() + 1}, {self.mPx.y() + 1}':
self.onCopyToClipBoad(txt))
a = menu.addAction('Copy spatial coordinate')
a.setToolTip('Copies the spatial coordinate as <x>,<y> pair')
a.triggered.connect(lambda *args, txt=self.mGeo.toString():
self.onCopyToClipBoad(txt))
a = menu.addAction('Copy spatial coordinate (WKT)')
a.setToolTip('Copies the spatial coordinate as Well-Known-Type')
a.triggered.connect(lambda *args, txt=self.mGeo.asWkt():
self.onCopyToClipBoad(txt))
def onCopyToClipBoad(self, text: str):
cb: QClipboard = QApplication.clipboard()
cb.setText(text)
class CursorLocationInfoModel(TreeModel):
ALWAYS_EXPAND = 'always'
NEVER_EXPAND = 'never'
......@@ -103,9 +149,22 @@ class CursorLocationInfoModel(TreeModel):
super().__init__(parent=parent)
self.setColumnNames(['Band/Field', 'Value'])
self.mCountFromZero: bool = True
self.mCursorLocation: SpatialPoint = None
def flags(self, index):
def setCursorLocation(self, location: SpatialPoint):
assert isinstance(location, SpatialPoint)
self.mCursorLocation = location
def setCountFromZero(self, b: bool):
"""
Specifies if the 1st pixel (upper left corner) is countes as 0,0 (True, default) or 1,1 (False)
:param b: bool
"""
assert isinstance(b, bool)
self.mCountFromZero = b
def flags(self, index: QModelIndex):
return Qt.ItemIsEnabled | Qt.ItemIsSelectable
def addSourceValues(self, sourceValueSet: SourceValueSet):
......@@ -121,8 +180,10 @@ class CursorLocationInfoModel(TreeModel):
root.setIcon(QIcon(':/images/themes/default/mIconRasterLayer.svg'))
# add subnodes
pxNode = TreeNode(name='Pixel')
pxNode.setValues(['{},{}'.format(sourceValueSet.pxPosition.x(), sourceValueSet.pxPosition.y())])
pxNode = PixelPositionTreeNode(sourceValueSet.pxPosition,
self.mCursorLocation,
name='Pixel',
zeroBased=self.mCountFromZero)
subNodes = [pxNode]
......@@ -183,9 +244,10 @@ class CursorLocationInfoModel(TreeModel):
self.rootNode().appendChildNodes(newSourceNodes)
def clear(self):
#self.beginResetModel()
# self.beginResetModel()
self.mRootNode.removeAllChildNodes()
#self.endResetModel()
# self.endResetModel()
class CursorLocationInfoTreeView(TreeView):
......@@ -364,6 +426,7 @@ class CursorLocationInfoDock(QDockWidget):
self.treeView().updateNodeExpansion(False)
self.mLocationInfoModel.clear()
self.mLocationInfoModel.setCursorLocation(self.cursorLocation())
for l in lyrs:
assert isinstance(l, QgsMapLayer)
......
......@@ -152,9 +152,10 @@ class RasterBandConfigWidget(QpsMapLayerConfigWidget):
elif isinstance(oldRenderer, QgsMultiBandColorRenderer):
newRenderer = oldRenderer.clone()
newRenderer.setInput(oldRenderer.input())
newRenderer.setRedBand(self.cbMultiBandRed.currentBand())
newRenderer.setGreenBand(self.cbMultiBandGreen.currentBand())
newRenderer.setBlueBand(self.cbMultiBandBlue.currentBand())
if isinstance(newRenderer, QgsMultiBandColorRenderer):
newRenderer.setRedBand(self.cbMultiBandRed.currentBand())
newRenderer.setGreenBand(self.cbMultiBandGreen.currentBand())
newRenderer.setBlueBand(self.cbMultiBandBlue.currentBand())
return newRenderer
def setRenderer(self, renderer: QgsRasterRenderer):
......
......@@ -37,7 +37,7 @@ import inspect
from qgis.PyQt.QtCore import QModelIndex, QAbstractItemModel, QAbstractListModel, \
pyqtSignal, Qt, QObject, QAbstractListModel, QSize, pyqtBoundSignal, QMetaEnum, QMetaType
from qgis.PyQt.QtWidgets import QComboBox, QTreeView, QMenu
from qgis.PyQt.QtGui import QIcon
from qgis.PyQt.QtGui import QIcon, QContextMenuEvent
def currentComboBoxValue(comboBox):
......@@ -574,8 +574,13 @@ class TreeNode(QObject):
"""
return self.mName
def contextMenu(self):
return None
def populateContextMenu(self, menu: QMenu):
"""
Implement this to add a TreeNode specific context menu
:param menu:
:return:
"""
pass
def setValue(self, value):
"""
......@@ -730,7 +735,8 @@ class PyObjectTreeNode(TreeNode):
if isinstance(v, QMetaEnum):
s = ""
# create a new node
newNodes.append(PyObjectTreeNode(name=a, obj=v))
# this allows to create a new node even of inherited classes
newNodes.append(self.__class__(name=a, obj=v))
if len(newNodes) > 0:
self.appendChildNodes(newNodes)
......@@ -1107,6 +1113,7 @@ class TreeView(QTreeView):
"""
A basic QAbstractItemView implementation to realize TreeModels.
"""
populateContextMenu = pyqtSignal(QMenu)
def __init__(self, *args, **kwds):
super(TreeView, self).__init__(*args, **kwds)
......@@ -1115,6 +1122,25 @@ class TreeView(QTreeView):
self.mModel = None
self.mNodeExpansion: typing.Dict[str, bool] = dict()
def contextMenuEvent(self, event: QContextMenuEvent) -> None:
"""
Default implementation. Emits populateContextMenu to create context menu
:param event:
:return:
"""
menu: QMenu = QMenu()
menu.setToolTipsVisible(True)
nodes = self.selectedNodes()
if len(nodes) == 1:
node: TreeNode = nodes[0]
node.populateContextMenu(menu)
self.populateContextMenu.emit(menu)
if not menu.isEmpty():
menu.exec_(self.viewport().mapToGlobal(event.pos()))
def setAutoExpansionDepth(self, depth: int):
"""
Sets the depth until which new TreeNodes will be opened
......
......@@ -1316,8 +1316,9 @@ class MapWidget(QFrame):
mwNode.setAttribute('mapsPerMapView', f'{self.mapsPerMapView()}')
mwNode.setAttribute('mapWidth', f'{mapSize.width()}')
mwNode.setAttribute('mapHeight', f'{mapSize.height()}')
mwNode.setAttribute('mapDate', f'{self.currentDate().date()}')
currentDate = self.currentDate()
if isinstance(currentDate, TimeSeriesDate):
mwNode.setAttribute('mapDate', f'{currentDate.date()}')
crsNode = doc.createElement('MapExtent')
self.spatialExtent().writeXml(crsNode, doc)
mwNode.appendChild(crsNode)
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment