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

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

parents 3287eeae ae31f671
Pipeline #13948 failed with stage
in 52 seconds
......@@ -729,6 +729,7 @@ class LabelWidget(AttributeTableWidget):
self.mOptionSelectBehaviour.setChecked(True)
m = QMenu()
m.setToolTipsVisible(True)
self.mOptionSelectionSetSelection = m.addAction('Set Selection')
self.mOptionSelectionSetSelection.setIcon(QIcon(':/images/themes/default/mIconSelected.svg'))
self.mOptionSelectionSetSelection.setToolTip('Selects a feature.')
......
......@@ -884,6 +884,11 @@ class MapCanvas(QgsMapCanvas):
tsd = self.tsd()
date = None
if isinstance(tsd, TimeSeriesDate):
date = tsd.date()
from .main import EOTimeSeriesViewer
eotsv = EOTimeSeriesViewer.instance()
......@@ -1175,13 +1180,33 @@ class MapCanvas(QgsMapCanvas):
ts = eotsv.timeSeries()
action = menu.addAction('Update date visibility')
action.setToolTip('Updates the visibility of observation dates and source images according its '
'presence of unmasked pixels for this spatial extent')
mv = self.mapView()
if mv:
n_max = len(mv.mapCanvases())
else:
n_max = 5
action = menu.addAction('Update source visibility')
action.setToolTip('Updates observation source visibility according their spatial intersection '
'with this map extent.')
action.triggered.connect(lambda *args,
ext=self.spatialExtent():
ts.focusVisibilityToExtent(ext=ext,
date_of_interest=date,
max_after=n_max,
max_before=n_max
))
action = menu.addAction('Update source visibility (all)')
action.setToolTip('Updates observation source visibility according their spatial intersection '
'with this map extent.<br/>'
'<span style="color:red">This can take some time for longe time series</span>')
action.triggered.connect(lambda *args,
ext=self.spatialExtent():
ts.focusVisibilityToExtent(ext=ext))
ts.focusVisibilityToExtent(ext=ext, date_of_interest=date))
menu.addSeparator()
action = menu.addAction('Hide Date')
action.triggered.connect(lambda *args: ts.hideTSDs([tsd]))
......@@ -1250,6 +1275,7 @@ class MapCanvas(QgsMapCanvas):
and not bool(mt.flags() & QgsMapTool.ShowContextMenu) \
and bool(modifiers & Qt.ControlModifier):
menu = QMenu()
menu.setToolTipsVisible(True)
# mt.populateContextMenu(menu)
self.populateContextMenu(menu, event.pos())
menu.exec_(event.globalPos())
......@@ -1258,6 +1284,7 @@ class MapCanvas(QgsMapCanvas):
if isinstance(mt, QgsMapTool):
if bool(mt.flags() & QgsMapTool.ShowContextMenu) or bool(modifiers & Qt.ControlModifier):
menu = QMenu()
menu.setToolTipsVisible(True)
mt.populateContextMenu(menu)
self.populateContextMenu(menu, event.pos())
menu.exec_(event.globalPos())
......
......@@ -1527,11 +1527,20 @@ class MapWidget(QFrame):
def mapViewCanvases(self, mapView: MapView) -> typing.List[MapCanvas]:
"""
Returns the MapCanvases related to a MapView, sorted
Returns the MapCanvases related to a MapView, sorted in temporal order
:param mapView: MapView
:return: [list-of-MapCanvases]
"""
return sorted(self.mCanvases[mapView], key=lambda c: c.tsd())
A = []
B = []
for c in self.mCanvases.get(mapView, []):
if isinstance(c.tsd(), TimeSeriesDate):
A.append(c)
else:
B.append(c)
return sorted(A, key=lambda c: c.tsd()) + B
def moveToNextTSD(self):
......
......@@ -44,6 +44,7 @@ from qgis.core import QgsRasterLayer, QgsCoordinateReferenceSystem, \
QgsProject, QgsGeometry, QgsApplication, QgsTask, QgsRasterBandStats, QgsRectangle, QgsRasterDataProvider, \
QgsTaskManager, QgsPoint, QgsPointXY, \
QgsMimeDataUtils, QgsCoordinateTransform
try:
from qgis.core import QgsRasterLayerTemporalProperties
except:
......@@ -228,9 +229,9 @@ class SensorInstrument(QObject):
else:
self.mName = self.mNameOriginal
#import eotimeseriesviewer.settings
#storedName = eotimeseriesviewer.settings.sensorName(self.mId)
#if isinstance(storedName, str):
# import eotimeseriesviewer.settings
# storedName = eotimeseriesviewer.settings.sensorName(self.mId)
# if isinstance(storedName, str):
# self.mName = storedName
if not isinstance(band_names, list):
......@@ -1170,85 +1171,148 @@ class TimeSeriesFindOverlapTask(QgsTask):
def __init__(self,
extent: SpatialExtent,
timeSeriesSources: typing.List[TimeSeriesSource],
time_series_sources: typing.List[TimeSeriesSource],
date_of_interest: np.datetime64 = None,
max_forward: int = -1,
max_backward: int = -1,
callback=None,
description='Calculate image pixel overlap',
sampleSize: int = 16,
sample_size: int = 16,
progress_interval: int = 2):
"""
:param extent:
:param time_series_sources:
:param date_of_interest: date of interest from which to start searching. "pivot" date
:param max_forward: max. number of intersecting dates to search into the past of date_of_interest
defaults to -1 = search for all dates < date_of_interest
:param max_backward: max. number of intersecting dates to search into the future of date_of_interest
defaults to -1 = search for all dates > date_of_interest
:param callback:
:param description:
:param sample_size:
:param progress_interval:
"""
super().__init__(description=description)
assert sampleSize >= 1
assert sample_size >= 1
assert progress_interval >= 1
assert isinstance(extent, SpatialExtent)
self.mTSS: typing.List[str] = [tss.uri() for tss in timeSeriesSources]
self.mTSS: typing.List[typing.Tuple[str, np.datetime64]] = \
[(tss.uri(), tss.date()) for tss in time_series_sources]
self.mDates = set([t[1] for t in self.mTSS])
if not isinstance(date_of_interest, np.datetime64):
self.mDOI = date_of_interest
else:
self.mDOI = date_of_interest
if max_forward == -1:
self.m_max_forward = len(self.mTSS)
else:
self.m_max_forward = max_forward
if max_backward == -1:
self.m_max_backward = len(self.mTSS)
else:
self.m_max_backward = max_backward
self.mCallback = callback
self.mTargetExtent = extent.__copy__()
self.mSampleSize = sampleSize
self.mSampleSize = sample_size
self.mProgressInterval = datetime.timedelta(seconds=progress_interval)
self.mIntersections: typing.Dict[str, bool] = dict()
self.mError = None
emptyStats: QgsRasterBandStats = QgsRasterBandStats()
emptyMin = emptyStats.minimumValue
emptyMax = emptyStats.maximumValue
self.mEmptyMinMax = (emptyMin, emptyMax)
targetCRS: QgsCoordinateReferenceSystem = self.mTargetExtent.crs()
self.mExtentLookup: typing.Dict[str, SpatialExtent] = dict()
self.mExtentLookup[targetCRS.toWkt()] = self.mTargetExtent
def testTSS(self, tssUri: str) -> bool:
tss = TimeSeriesSource.create(tssUri)
wkt = tss.crsWkt()
if wkt not in self.mExtentLookup.keys():
self.mExtentLookup[wkt] = self.mTargetExtent.toCrs(tss.crs())
targetExtent2 = self.mExtentLookup.get(wkt)
if not isinstance(targetExtent2, SpatialExtent):
return False
if not targetExtent2.intersects(tss.spatialExtent()):
return False
lyr: QgsRasterLayer = tss.asRasterLayer()
if not isinstance(lyr, QgsRasterLayer):
return False
stats: QgsRasterBandStats = lyr.dataProvider().bandStatistics(1,
stats=QgsRasterBandStats.Range,
extent=targetExtent2,
sampleSize=self.mSampleSize)
if not isinstance(stats, QgsRasterBandStats):
return False
return (stats.minimumValue, stats.maximumValue) != self.mEmptyMinMax
def run(self):
"""
Start the Task and returns the results.
:return:
"""
targetCRS: QgsCoordinateReferenceSystem = self.mTargetExtent.crs()
extentLookup = dict()
extentLookup[targetCRS.toWkt()] = self.mTargetExtent
try:
for tssUri in self.mTSS:
self.mIntersections[tssUri] = False
emptyStats: QgsRasterBandStats = QgsRasterBandStats()
emptyMin = emptyStats.minimumValue
emptyMax = emptyStats.maximumValue
del emptyStats
# for tssUri in self.mTSS:
# self.mIntersections[tssUri] = False
n = len(self.mTSS)
t0 = datetime.datetime.now()
for i, tssUri in enumerate(self.mTSS):
if self.isCanceled():
return False
tss = TimeSeriesSource.create(tssUri)
wkt = tss.crsWkt()
if wkt not in extentLookup.keys():
extentLookup[wkt] = self.mTargetExtent.toCrs(tss.crs())
sources = sorted(self.mTSS, key=lambda t: abs(self.mDOI - t[1]))
targetExtent2 = extentLookup[wkt]
if not isinstance(targetExtent2, SpatialExtent):
continue
dates_before = set()
dates_after = set()
if not targetExtent2.intersects(tss.spatialExtent()):
del targetExtent2
continue
smallest = min(self.mDates)
highest = max(self.mDates)
lyr: QgsRasterLayer = tss.asRasterLayer()
del tss
for i, t in enumerate(sources):
if self.isCanceled():
return False
if not isinstance(lyr, QgsRasterLayer):
continue
tssUri: str = t[0]
obs_date: np.datetime64 = t[1]
stats: QgsRasterBandStats = lyr.dataProvider().bandStatistics(1,
stats=QgsRasterBandStats.Range,
extent=targetExtent2,
sampleSize=self.mSampleSize)
if obs_date < smallest or obs_date > highest:
continue
del lyr
is_intersection: bool = self.testTSS(tssUri)
self.mIntersections[tssUri] = is_intersection
if not isinstance(stats, QgsRasterBandStats):
continue
self.mIntersections[tssUri] = (stats.minimumValue, stats.maximumValue) != (emptyMin, emptyMax)
del stats
del targetExtent2
progress = int(100 * (i + 1) / n)
if is_intersection:
if obs_date < self.mDOI:
dates_before.add(obs_date)
if len(dates_before) >= self.m_max_backward:
smallest = min(dates_before)
elif obs_date > self.mDOI:
dates_after.add(obs_date)
if len(dates_after) >= self.m_max_forward:
highest = max(dates_after)
dt = datetime.datetime.now() - t0
if dt > self.mProgressInterval:
self.sigTimeSeriesSourceOverlap.emit(self.mIntersections.copy())
self.mIntersections.clear()
progress = int(100 * (i + 1) / n)
self.setProgress(progress)
t0 = datetime.datetime.now()
except Exception as ex:
self.mError = ex
return False
......@@ -1256,8 +1320,7 @@ class TimeSeriesFindOverlapTask(QgsTask):
if len(self.mIntersections) > 0:
self.sigTimeSeriesSourceOverlap.emit(self.mIntersections.copy())
self.mIntersections.clear()
del targetCRS
del extentLookup
self.setProgress(100)
return True
......@@ -1404,7 +1467,10 @@ class TimeSeries(QAbstractItemModel):
if isinstance(spatialExtent, SpatialExtent) and self.mCurrentSpatialExtent != spatialExtent:
self.mCurrentSpatialExtent = spatialExtent
def focusVisibilityToExtent(self, ext: SpatialExtent = None, runAsync: bool = None):
def focusVisibilityToExtent(self, ext: SpatialExtent = None, runAsync: bool = None,
date_of_interest: np.datetime64 = None,
max_before: int = -1,
max_after: int = -1):
"""
Changes TSDs visibility according to its intersection with a SpatialExtent
:param runAsync: if True (default), the visibility check is run in a parallel task
......@@ -1416,7 +1482,7 @@ class TimeSeries(QAbstractItemModel):
from eotimeseriesviewer.settings import value, Keys
runAsync = value(Keys.QgsTaskAsync, True)
tssToTest = []
tssToTest: typing.List[TimeSeriesSource] = []
if isinstance(ext, SpatialExtent):
changed = False
for tsd in self:
......@@ -1431,13 +1497,16 @@ class TimeSeries(QAbstractItemModel):
qgsTask = TimeSeriesFindOverlapTask(ext,
tssToTest,
sampleSize=value(Keys.RasterOverlapSampleSize),
date_of_interest=date_of_interest,
max_backward=max_before,
max_forward=max_after,
sample_size=value(Keys.RasterOverlapSampleSize),
callback=self.onTaskFinished)
qgsTask.sigTimeSeriesSourceOverlap.connect(self.onFoundOverlap)
qgsTask.progressChanged.connect(self.sigProgress.emit)
if runAsync:
if False and runAsync:
tm = QgsApplication.taskManager()
assert isinstance(tm, QgsTaskManager)
tm.addTask(qgsTask)
......@@ -1814,7 +1883,7 @@ class TimeSeries(QAbstractItemModel):
c = self.columnNames().index(self.cnSensor)
idx0 = self.index(0, c)
idx1 = self.index(self.rowCount()-1, c)
idx1 = self.index(self.rowCount() - 1, c)
self.dataChanged.emit(idx0, idx1)
s = ""
......
......@@ -128,7 +128,7 @@ class TestTimeSeries(EOTSVTestCase):
ext_full.yMaximum())
for ext in [ext_full, ext_nodata, ext_outofbounds]:
task = TimeSeriesFindOverlapTask(ext, [tss], sampleSize=1024, callback=onFinished)
task = TimeSeriesFindOverlapTask(ext, [tss], sample_size=1024, callback=onFinished)
task.sigTimeSeriesSourceOverlap.connect(onOverlapp)
task.finished(task.run())
......
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