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

refactored time series dock


added TimeSeriesWidget
added regex/wildcard filtering to time series treeview
Signed-off-by: Benjamin Jakimow's avatarBenjamin Jakimow benjamin.jakimow@geo.hu-berlin.de <benjamin.jakimow@geo.hu-berlin.de>
parent eed96be6
Pipeline #13970 failed with stage
in 32 seconds
......@@ -4,7 +4,8 @@ Changelog
2020-11-16 (version 1.15):
* quick labels: CTRL + right mouse button opens map menu even when the feature modify map tool is activates
* source visibility update can be run on entire time series or (new and faster) for the next time steps only
* time series table can keep focus on the date range that is visualized in the map windows
* added "follow current date" option to time series table to keep focus on the map window date range
* added wildcard + regular expression filter to time series table
* smaller bug fixes and improvements of workflow
2020-11-06 (version 1.14):
......
......@@ -43,7 +43,8 @@ import qgis.utils
from eotimeseriesviewer import LOG_MESSAGE_TAG, DIR_UI, settings
from eotimeseriesviewer.utils import SpatialPoint, SpatialExtent, datetime64, fixMenuButtons, file_search, loadUi
from eotimeseriesviewer.timeseries import TimeSeries, TimeSeriesSource, TimeSeriesDate, TimeSeriesTreeView, \
DateTimePrecision, SensorInstrument, SensorProxyLayer, SensorMatching, TimeSeriesFindOverlapTask, TimeSeriesDock
DateTimePrecision, SensorInstrument, SensorProxyLayer, SensorMatching, TimeSeriesFindOverlapTask, \
TimeSeriesDock, TimeSeriesWidget
from eotimeseriesviewer.settings import Keys as SettingKeys, value as SettingValue
from eotimeseriesviewer.settings import defaultValues, setValue
from eotimeseriesviewer.mapcanvas import MapCanvas
......@@ -149,8 +150,16 @@ class EOTimeSeriesViewerUI(QMainWindow):
# from timeseriesviewer.mapvisualization import MapViewDockUI
# self.dockMapViews = addDockWidget(MapViewDockUI(self))
self.dockTimeSeries = self.addDockWidget(area, TimeSeriesDock(self))
self.dockTimeSeries.initActions(self)
self.dockTimeSeries: TimeSeriesDock = self.addDockWidget(area, TimeSeriesDock(self))
tbar: QToolBar = self.dockTimeSeries.timeSeriesWidget().toolBar()
tbar.addSeparator()
tbar.addAction(self.actionAddTSD)
tbar.addAction(self.actionRemoveTSD)
tbar.addAction(self.actionLoadTS)
tbar.addAction(self.actionSaveTS)
tbar.addSeparator()
tbar.addAction(self.actionClearTS)
self.dockTimeSeries.timeSeriesWidget().sigTimeSeriesDatesSelected.connect(self.actionRemoveTSD.setEnabled)
from eotimeseriesviewer.profilevisualization import ProfileViewDock
self.dockProfiles = self.addDockWidget(area, ProfileViewDock(self))
......@@ -458,12 +467,12 @@ class EOTimeSeriesViewer(QgisInterface, QObject):
from eotimeseriesviewer.mapvisualization import MapViewDock, MapWidget
mvd: MapViewDock = self.ui.dockMapViews
dts = self.ui.dockTimeSeries
tswidget: TimeSeriesWidget = self.ui.dockTimeSeries.timeSeriesWidget()
mw: MapWidget = self.ui.mMapWidget
assert isinstance(mvd, MapViewDock)
assert isinstance(mw, MapWidget)
assert isinstance(dts, TimeSeriesDock)
assert isinstance(tswidget, TimeSeriesWidget)
def onClosed():
EOTimeSeriesViewer._instance = None
......@@ -496,7 +505,7 @@ class EOTimeSeriesViewer(QgisInterface, QObject):
# self.mTimeSeries.sigMessage.connect(self.setM)
dts.setTimeSeries(self.mTimeSeries)
tswidget.setTimeSeries(self.mTimeSeries)
self.ui.dockSensors.setTimeSeries(self.mTimeSeries)
self.ui.dockProfiles.setTimeSeries(self.mTimeSeries)
self.ui.dockProfiles.setVectorLayerTools(self.mVectorLayerTools)
......@@ -516,7 +525,7 @@ class EOTimeSeriesViewer(QgisInterface, QObject):
mapCanvas=mw.currentMapCanvas()))
mw.sigCurrentLayerChanged.connect(self.updateCurrentLayerActions)
mw.sigCurrentDateChanged.connect(self.sigCurrentDateChanged)
mw.sigCurrentDateChanged.connect(dts.setCurrentDate)
mw.sigCurrentDateChanged.connect(tswidget.setCurrentDate)
self.ui.optionSyncMapCenter.toggled.connect(self.mapWidget().setSyncWithQGISMapCanvas)
tb = self.ui.toolBarTimeControl
......@@ -528,7 +537,7 @@ class EOTimeSeriesViewer(QgisInterface, QObject):
tb.addAction(mw.actionForwardFast)
tb.addAction(mw.actionLastDate)
tstv = self.ui.dockTimeSeries.timeSeriesTreeView()
tstv: TimeSeriesTreeView = tswidget.timeSeriesTreeView()
assert isinstance(tstv, TimeSeriesTreeView)
tstv.sigMoveToDate.connect(self.setCurrentDate)
tstv.sigMoveToSource.connect(self.setCurrentSource)
......@@ -603,7 +612,7 @@ class EOTimeSeriesViewer(QgisInterface, QObject):
lambda: self.openAddSubdatasetsDialog(
title='Open Sentinel-2 Datasets', filter='MTD_MSIL*.xml'))
self.ui.actionRemoveTSD.triggered.connect(lambda: self.mTimeSeries.removeTSDs(dts.selectedTimeSeriesDates()))
self.ui.actionRemoveTSD.triggered.connect(lambda: self.mTimeSeries.removeTSDs(tswidget.selectedTimeSeriesDates()))
self.ui.actionRefresh.triggered.connect(mw.refresh)
self.ui.actionLoadTS.triggered.connect(self.loadTimeSeriesDefinition)
self.ui.actionClearTS.triggered.connect(self.clearTimeSeries)
......
......@@ -1767,6 +1767,7 @@ class TimeSeries(QAbstractItemModel):
tsd.rowsAboutToBeInserted.connect(
lambda p, first, last, tsd=tsd: self.beginInsertRows(self.tsdToIdx(tsd), first, last))
tsd.rowsInserted.connect(self.endInsertRows)
tsd.sigSourcesAdded.connect(self.sigSourcesAdded)
tsd.sigSourcesRemoved.connect(self.sigSourcesRemoved)
......@@ -1807,19 +1808,27 @@ class TimeSeries(QAbstractItemModel):
toRemove.add(t)
if isinstance(t, TimeSeriesSource):
toRemove.add(t.timeSeriesDate())
toRemove = sorted(list(toRemove))
removed = []
while len(toRemove) > 0:
block: typing.List[TimeSeriesDate] = [toRemove.pop(0)]
r0 = r1 = self.tsdToIdx(block[0]).row()
while len(toRemove) > 0:
if self.index(r1+1, 0).data(Qt.UserRole) != toRemove[0]:
break
else:
block.append(toRemove.pop(0))
r1 += 1
for tsd in list(sorted(list(toRemove), reverse=True)):
assert isinstance(tsd, TimeSeriesDate)
tsd.sigSourcesRemoved.disconnect()
tsd.sigSourcesAdded.disconnect()
tsd.sigRemoveMe.disconnect()
self.beginRemoveRows(self.mRootIndex, r0, r1)
for tsd in block:
self.mTSDs.remove(tsd)
tsd.mTimeSeries = None
tsd.sigSourcesAdded.disconnect(self.sigSourcesAdded)
tsd.sigSourcesRemoved.disconnect(self.sigSourcesRemoved)
row = self.mTSDs.index(tsd)
self.beginRemoveRows(self.mRootIndex, row, row)
self.mTSDs.remove(tsd)
tsd.mTimeSeries = None
removed.append(tsd)
removed.append(tsd)
self.endRemoveRows()
if len(removed) > 0:
......@@ -2859,28 +2868,68 @@ class TimeSeriesFilterModel(QSortFilterProxyModel):
def __init__(self, *args, **kwds):
super().__init__(*args, **kwds)
self.setRecursiveFilteringEnabled(True)
#self.setSortRole(Qt.EditRole)
#self.setDynamicSortFilter(True)
def filterAcceptsRow(self, sourceRow, sourceParent):
reg = self.filterRegExp()
if reg.isEmpty():
return True
class TimeSeriesDock(QgsDockWidget):
"""
QgsDockWidget that shows the TimeSeries
"""
for c in range(self.sourceModel().columnCount()):
idx = self.sourceModel().index(sourceRow, c, parent=sourceParent)
value = idx.data(Qt.DisplayRole)
value = str(value)
if reg.indexIn(value) >= 0:
return True
def __init__(self, parent=None):
super(TimeSeriesDock, self).__init__(parent)
return False
class TimeSeriesWidget(QMainWindow):
sigTimeSeriesDatesSelected = pyqtSignal(bool)
def __init__(self, *args, **kwds):
super().__init__(*args, **kwds)
loadUi(DIR_UI / 'timeserieswidget.ui', self)
loadUi(DIR_UI / 'timeseriesdock.ui', self)
self.frameFilters.setVisible(False)
self.mTimeSeriesTreeView: TimeSeriesTreeView
assert isinstance(self.mTimeSeriesTreeView, TimeSeriesTreeView)
self.mTimeSeries: TimeSeries = None
self.mTSProxyModel: TimeSeriesFilterModel = TimeSeriesFilterModel()
self.mSelectionModel = None
self.mLastDate: TimeSeriesDate = None
self.optionFollowCurrentDate: QAction
self.optionFollowCurrentDate.toggled.connect(lambda : self.setCurrentDate(self.mLastDate))
self.optionFollowCurrentDate.toggled.connect(lambda: self.setCurrentDate(self.mLastDate))
self.optionUseRegex: QAction
self.optionCaseSensitive: QAction
self.btnUseRegex.setDefaultAction(self.optionUseRegex)
self.btnCaseSensitive.setDefaultAction(self.optionCaseSensitive)
self.optionCaseSensitive.toggled.connect(self.onFilterExpressionChanged)
self.optionUseRegex.toggled.connect(self.onFilterExpressionChanged)
self.tbFilterExpression.textChanged.connect(self.onFilterExpressionChanged)
def timeSeriesTreeView(self) -> TimeSeriesTreeView:
return self.mTimeSeriesTreeView
def onFilterExpressionChanged(self, *args):
expression: str = self.tbFilterExpression.text()
useRegex: bool = self.optionUseRegex.isChecked()
if self.optionCaseSensitive.isChecked():
sensitivity = Qt.CaseSensitive
else:
sensitivity = Qt.CaseInsensitive
self.mTSProxyModel.setFilterCaseSensitivity(sensitivity)
if useRegex:
rx = QRegExp(expression, sensitivity)
self.mTSProxyModel.setFilterRegExp(rx)
else:
self.mTSProxyModel.setFilterWildcard(expression)
def toolBar(self) -> QToolBar:
return self.mToolBar
def setCurrentDate(self, tsd: TimeSeriesDate):
"""
......@@ -2896,17 +2945,6 @@ class TimeSeriesDock(QgsDockWidget):
if self.optionFollowCurrentDate.isChecked():
self.moveToDate(tsd)
def initActions(self, parent):
from eotimeseriesviewer.main import EOTimeSeriesViewerUI
assert isinstance(parent, EOTimeSeriesViewerUI)
self.btnFollowCurrentDate.setDefaultAction(self.optionFollowCurrentDate)
self.btnAddTSD.setDefaultAction(parent.actionAddTSD)
self.btnRemoveTSD.setDefaultAction(parent.actionRemoveTSD)
self.btnLoadTS.setDefaultAction(parent.actionLoadTS)
self.btnSaveTS.setDefaultAction(parent.actionSaveTS)
self.btnClearTS.setDefaultAction(parent.actionClearTS)
def moveToDate(self, tsd: TimeSeriesDate):
tstv = self.timeSeriesTreeView()
assert isinstance(tstv, TimeSeriesTreeView)
......@@ -2934,16 +2972,15 @@ class TimeSeriesDock(QgsDockWidget):
info = '{} dates, {} sensors, {} source images'.format(nDates, nSensors, nImages)
else:
info = ''
self.summary.setText(info)
self.mStatusBar.showMessage(info, 0)
def onSelectionChanged(self, *args):
"""
Slot to react on user-driven changes of the selected TimeSeriesDate rows.
"""
b = isinstance(self.mSelectionModel, QItemSelectionModel) and len(self.mSelectionModel.selectedRows()) > 0
self.btnRemoveTSD.setEnabled(
isinstance(self.mSelectionModel, QItemSelectionModel) and
len(self.mSelectionModel.selectedRows()) > 0)
self.sigTimeSeriesDatesSelected.emit(b)
def selectedTimeSeriesDates(self) -> list:
"""
......@@ -2976,7 +3013,6 @@ class TimeSeriesDock(QgsDockWidget):
if isinstance(TS, TimeSeries):
self.mTimeSeries = TS
self.mTSProxyModel = TimeSeriesFilterModel(self)
self.mTSProxyModel.setSourceModel(self.mTimeSeries)
self.mSelectionModel = QItemSelectionModel(self.mTSProxyModel)
self.mSelectionModel.selectionChanged.connect(self.onSelectionChanged)
......@@ -2995,6 +3031,25 @@ class TimeSeriesDock(QgsDockWidget):
self.onSelectionChanged()
def timeSeriesTreeView(self) -> TimeSeriesTreeView:
return self.mTimeSeriesTreeView
class TimeSeriesDock(QgsDockWidget):
"""
QgsDockWidget that wraps the TimeSeriesWidget
"""
def __init__(self, parent=None):
super(TimeSeriesDock, self).__init__(parent)
self.mTimeSeriesWidget = TimeSeriesWidget()
self.setWidget(self.mTimeSeriesWidget)
def timeSeriesWidget(self) -> TimeSeriesWidget:
return self.mTimeSeriesWidget
if __name__ == '__main__':
q = QApplication([])
......
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>timeseriesPanel</class>
<widget class="QDockWidget" name="timeseriesPanel">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>640</width>
<height>266</height>
<width>701</width>
<height>329</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>2</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<property name="minimumSize">
<property name="iconSize">
<size>
<width>152</width>
<height>162</height>
<width>18</width>
<height>18</height>
</size>
</property>
<property name="windowTitle">
<string>Time Series</string>
</property>
<widget class="QFrame" name="dockWidgetContents2">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QFrame" name="TSButtonList">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
<widget class="QgsFilterLineEdit" name="mLineEdit">
<property name="qgisRelation" stdset="0">
<string notr="true"/>
</property>
<property name="minimumSize">
<size>
<width>25</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>23</width>
<height>16777215</height>
</size>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>1</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QToolButton" name="btnFollowCurrentDate">
<property name="text">
<string>...</string>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="btnAddTSD">
<property name="text">
<string>...</string>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="btnRemoveTSD">
<property name="text">
<string>...</string>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="btnLoadTS">
<property name="text">
<string>...</string>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="btnSaveTS">
<property name="text">
<string>...</string>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="btnClearTS">
<property name="text">
<string>...</string>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>10</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Vertical</enum>
<widget class="QCheckBox" name="cbUseRegex">
<property name="text">
<string>.*</string>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QFrame" name="frameFilters">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item>
<widget class="TimeSeriesTreeView" name="mTimeSeriesTreeView">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustIgnored</enum>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="QFrame" name="infoBar">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>524287</height>
</size>
<widget class="TimeSeriesTreeView" name="mTimeSeriesTreeView">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
<enum>QFrame::NoFrame</enum>
</property>
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustIgnored</enum>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>