Commit 5b1cc803 authored by Benjamin Jakimow's avatar Benjamin Jakimow
Browse files

modified extent overlap check


Signed-off-by: Benjamin Jakimow's avatarBenjamin Jakimow <benjamin.jakimow@geo.hu-berlin.de>
parent 6df2a10e
Pipeline #12396 failed
......@@ -1144,7 +1144,9 @@ class MapCanvas(QgsMapCanvas):
ts = eotsv.timeSeries()
action = menu.addAction('Focus on Spatial Extent')
action.triggered.connect(ts.focusVisibilityToExtent)
action.triggered.connect(lambda *args,
ext=self.spatialExtent():
ts.focusVisibilityToExtent(ext=ext))
action = menu.addAction('Hide Date')
action.triggered.connect(lambda *args: ts.hideTSDs([tsd]))
......
......@@ -1136,6 +1136,8 @@ class MapWidget(QFrame):
for c in self.mapViewCanvases(mapView):
# find the first map canvas that contains layer data of this sensor
# in its extent
if not isinstance(c.tsd(), TimeSeriesDate):
continue
if c.tsd().sensor() == sensor and c.stretchToCurrentExtent():
mapView.mInitialStretch[sensor] = True
break
......
import os, enum, pathlib, re, json, pickle
from collections import namedtuple
from qgis.core import *
from qgis.core import QgsReadWriteContext, QgsTextFormat, QgsTextBufferSettings, QgsUnitTypes
from qgis.gui import *
from qgis.gui import QgsFileWidget
from qgis.PyQt.QtCore import *
from qgis.PyQt.QtWidgets import *
from qgis.PyQt.QtGui import *
......@@ -34,6 +36,7 @@ class Keys(enum.Enum):
QgsTaskAsync = 'qgs_task_async'
QgsTaskBlockSize = 'qgs_task_block_size'
BandStatsSampleSize = 'band_stats_sample_size'
RasterOverlapSampleSize = 'raster_overlap_sample_size'
def defaultValues() -> dict:
......@@ -62,6 +65,7 @@ def defaultValues() -> dict:
d[Keys.QgsTaskAsync] = True
d[Keys.QgsTaskBlockSize] = 25
d[Keys.BandStatsSampleSize] = 256
d[Keys.RasterOverlapSampleSize] = 25
d[Keys.SettingsVersion] = EOTSV_VERSION
textFormat = QgsTextFormat()
......@@ -137,6 +141,9 @@ def value(key: Keys, default=None):
if key == Keys.BandStatsSampleSize:
value = int(value)
if key == Keys.RasterOverlapSampleSize:
value = int(value)
if key == Keys.MapUpdateInterval:
value = int(value)
......@@ -290,8 +297,8 @@ class SensorSettingsTableModel(QAbstractTableModel):
sensorSpecs = value(Keys.SensorSpecs, default={})
sensors = []
for id, specs in sensorSpecs.items():
sensor = SensorInstrument(id)
for sid, specs in sensorSpecs.items():
sensor = SensorInstrument(sid)
sensor.setName(specs['name'])
sensors.append(sensor)
self.addSensors(sensors)
......@@ -308,7 +315,7 @@ class SensorSettingsTableModel(QAbstractTableModel):
def sensor2idx(self, sensor: SensorInstrument) -> QModelIndex:
if not sensor in self.mSensors:
if sensor not in self.mSensors:
return QModelIndex()
row = self.mSensors.index(sensor)
return self.createIndex(row, 0, sensor)
......@@ -571,6 +578,7 @@ class SettingsDialog(QDialog):
d[Keys.QgsTaskAsync] = self.cbAsyncQgsTasks.isChecked()
d[Keys.QgsTaskBlockSize] = self.sbQgsTaskBlockSize.value()
d[Keys.BandStatsSampleSize] = self.sbBandStatsSampleSize.value()
d[Keys.RasterOverlapSampleSize] = self.sbRasterOverlapSampleSize.value()
for k in self.mLastValues.keys():
if k not in d.keys():
......@@ -647,3 +655,6 @@ class SettingsDialog(QDialog):
if checkKey(key, Keys.BandStatsSampleSize) and isinstance(value, int):
self.sbBandStatsSampleSize.setValue(value)
if checkKey(key, Keys.RasterOverlapSampleSize) and isinstance(value, int):
self.sbRasterOverlapSampleSize.setValue(value)
......@@ -22,6 +22,7 @@
import bisect
import collections
import datetime
import enum
import json
import pathlib
......@@ -1103,13 +1104,16 @@ class SensorMatching(enum.Flag):
parts = []
if bool(flags & SensorMatching.PX_DIMS):
parts.append(
'Source images of same sensor/product must have same ground sampling distance ("pixel size"), number of bands and data type.')
'Source images of same sensor/product must have same ground sampling distance ("pixel size"), '
'number of bands and data type.')
if bool(flags & SensorMatching.WL):
parts.append(
r'Source images of same sensor/product must have same wavelength definition, e.g. nanometer value for each raster band.')
r'Source images of same sensor/product must have same wavelength definition, '
r'e.g. nanometer value for each raster band.')
if bool(flags & SensorMatching.NAME):
parts.append(
r'Source images of same sensor/product must have the same name, e.g. defined by a "sensor type = Landsat 8" metadata entry.')
r'Source images of same sensor/product must have the same name, e.g. defined by '
r'a "sensor type = Landsat 8" metadata entry.')
return '\n'.join(parts)
......@@ -1122,7 +1126,7 @@ class TimeSeriesFindOverlapTask(QgsTask):
timeSeriesSources: typing.List[TimeSeriesSource],
callback=None,
description='Calculate image pixel overlap',
sampleSize: int = 256,
sampleSize: int = 25,
block_size=25):
super().__init__(description=description)
assert block_size >= 1
......@@ -1130,10 +1134,12 @@ class TimeSeriesFindOverlapTask(QgsTask):
assert isinstance(extent, SpatialExtent)
self.mBlockSize: int = block_size
self.mTSS: typing.List[TimeSeriesSource] = timeSeriesSources
self.mTSS: typing.List[str] = [tss.uri() for tss in timeSeriesSources]
self.mCallback = callback
self.mTargetExtent = extent
self.mTargetExtent = extent.__copy__()
self.mSampleSize = sampleSize
self.mIntersections: typing.Dict[str, bool] = dict()
self.mError = None
def run(self):
"""
......@@ -1144,51 +1150,60 @@ class TimeSeriesFindOverlapTask(QgsTask):
extentLookup = dict()
extentLookup[targetCRS.toWkt()] = self.mTargetExtent
done = dict()
n = len(self.mTSS)
next_progress = 10
for i, tss in enumerate(self.mTSS):
if self.isCanceled():
return False
wkt = tss.crsWkt()
if wkt not in extentLookup.keys():
extentLookup[wkt] = self.mTargetExtent.toCrs(tss.crs())
targetExtent2 = extentLookup[wkt]
if not (isinstance(targetExtent2, SpatialExtent) and targetExtent2.intersects(tss.spatialExtent())):
done[tss] = False
self.setProgress(int(100 * (i + 1) / n))
continue
try:
for tssUri in self.mTSS:
self.mIntersections[tssUri] = False
emptyStats: QgsRasterBandStats = QgsRasterBandStats()
emptyMin = emptyStats.minimumValue
emptyMax = emptyStats.maximumValue
del emptyStats
n = len(self.mTSS)
lyr: QgsRasterLayer = tss.asRasterLayer()
if not isinstance(lyr, QgsRasterLayer):
done[tss] = False
self.setProgress(int(100 * (i + 1) / n))
continue
t0 = datetime.datetime.now()
report_delay = datetime.timedelta(seconds=2)
dp: QgsRasterDataProvider = lyr.dataProvider()
stats: QgsRasterBandStats = dp.bandStatistics(1,
stats=QgsRasterBandStats.Range,
extent=targetExtent2,
sampleSize=self.mSampleSize)
if stats.statsGathered > 0:
if stats.minimumValue > stats.maximumValue:
done[tss] = False
else:
done[tss] = True
else:
done[tss] = False
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())
# if len(done) >= self.mBlockSize:
# self.sigTimeSeriesSourceOverlap.emit(done)
# done = dict()
progress = int(100 * (i + 1) / n)
if progress >= next_progress:
self.setProgress(progress)
next_progress += 10
targetExtent2 = extentLookup[wkt]
if not isinstance(targetExtent2, SpatialExtent):
continue
if not targetExtent2.intersects(tss.spatialExtent()):
continue
lyr: QgsRasterLayer = tss.asRasterLayer()
if not isinstance(lyr, QgsRasterLayer):
continue
dp: QgsRasterDataProvider = lyr.dataProvider()
stats: QgsRasterBandStats = dp.bandStatistics(1,
stats=QgsRasterBandStats.Range,
extent=targetExtent2,
sampleSize=self.mSampleSize)
if not isinstance(stats, QgsRasterBandStats):
continue
self.mIntersections[tssUri] = (stats.minimumValue, stats.maximumValue) != (emptyMin, emptyMax)
progress = int(100 * (i + 1) / n)
dt = datetime.datetime.now() - t0
if dt > report_delay:
self.setProgress(progress)
#self.sigTimeSeriesSourceOverlap.emit(self.mIntersections)
#self.mIntersections.clear()
t0 = datetime.datetime.now()
except Exception as ex:
self.mError = ex
return False
if len(done) >= 0:
self.sigTimeSeriesSourceOverlap.emit(done)
#self.sigTimeSeriesSourceOverlap.emit(self.mIntersections)
return True
def canCancel(self) -> bool:
......@@ -1197,6 +1212,7 @@ class TimeSeriesFindOverlapTask(QgsTask):
def finished(self, result):
if self.mCallback is not None:
self.mCallback(result, self)
#print('Finished', flush=True)
class TimeSeriesLoadingTask(QgsTask):
......@@ -1311,13 +1327,17 @@ class TimeSeries(QAbstractItemModel):
if isinstance(spatialExtent, SpatialExtent) and self.mCurrentSpatialExtent != spatialExtent:
self.mCurrentSpatialExtent = spatialExtent
def focusVisibilityToExtent(self, ext: SpatialExtent = None, runAsync: bool = True):
def focusVisibilityToExtent(self, ext: SpatialExtent = None, runAsync: bool = None):
"""
Changes TSDs visibility according to its intersection with a SpatialExtent
:param runAsync: if True (default), the visibility check is run in a parallel task
:param ext: SpatialExtent
"""
if ext is None:
ext = self.currentSpatialExtent()
if runAsync is None:
from eotimeseriesviewer.settings import value, Keys
runAsync = value(Keys.QgsTaskAsync, True)
tssToTest = []
if isinstance(ext, SpatialExtent):
......@@ -1334,33 +1354,41 @@ class TimeSeries(QAbstractItemModel):
qgsTask = TimeSeriesFindOverlapTask(ext,
tssToTest,
sampleSize=value(Keys.BandStatsSampleSize),
block_size=value(Keys.QgsTaskBlockSize))
sampleSize=value(Keys.RasterOverlapSampleSize),
block_size=value(Keys.QgsTaskBlockSize),
callback=self.onTaskFinished)
tid = id(qgsTask)
self.mTasks[tid] = qgsTask
#self.mTasks[tid] = qgsTask
qgsTask.sigTimeSeriesSourceOverlap.connect(self.onFoundOverlapp)
qgsTask.taskCompleted.connect(lambda *args, tid=tid: self.onRemoveTask(tid))
qgsTask.taskTerminated.connect(lambda *args, tid=tid: self.onRemoveTask(tid))
qgsTask.progressChanged.connect(self.sigProgress.emit)
#qgsTask.sigTimeSeriesSourceOverlap.connect(self.onFoundOverlap)
qgsTask.taskCompleted.connect(lambda *args, _tid=tid: self.onRemoveTask(_tid))
qgsTask.taskTerminated.connect(lambda *args, _tid=tid: self.onRemoveTask(_tid))
if runAsync:
tm = QgsApplication.taskManager()
assert isinstance(tm, QgsTaskManager)
tm.addTask(qgsTask)
else:
qgsTask.run()
qgsTask.finished(qgsTask.run())
def onFoundOverlap(self, results: dict):
def onFoundOverlapp(self, results: dict):
URI2TSS = dict()
for tsd in self:
for tss in tsd:
URI2TSS[tss.uri()] = tss
affectedTSDs = set()
for tss, b in results.items():
assert isinstance(tss, TimeSeriesSource)
assert isinstance(b, bool)
tss.setIsVisible(b)
tsd = tss.timeSeriesDate()
if isinstance(tsd, TimeSeriesDate):
affectedTSDs.add(tsd)
for tssUri, b in results.items():
assert isinstance(tssUri, str)
tss = URI2TSS.get(tssUri, None)
if isinstance(tss, TimeSeriesSource):
tss.setIsVisible(b)
tsd = tss.timeSeriesDate()
if isinstance(tsd, TimeSeriesDate):
affectedTSDs.add(tsd)
if len(affectedTSDs) == 0:
return
......@@ -1735,7 +1763,7 @@ class TimeSeries(QAbstractItemModel):
def addTimeSeriesSources(self, sources: typing.List[TimeSeriesSource]):
assert isinstance(sources, list)
print('Add TSS...', flush=True)
addedDates = []
for i, source in enumerate(sources):
assert isinstance(source, TimeSeriesSource)
......@@ -1785,15 +1813,17 @@ class TimeSeries(QAbstractItemModel):
assert isinstance(tm, QgsTaskManager)
tm.addTask(qgsTask)
else:
qgsTask.run()
qgsTask.finished(qgsTask.run())
def onRemoveTask(self, key):
#print(f'remove {key}', flush=True)
if isinstance(key, QgsTask):
key = id(key)
self.mTasks.pop(key)
if key in self.mTasks.keys():
self.mTasks.pop(key)
def onTaskFinished(self, success, task: QgsTask):
# print(':: onAddSourcesAsyncFinished')
#print(':: onAddSourcesAsyncFinished')
if isinstance(task, TimeSeriesLoadingTask):
if len(task.mInvalidSources) > 0:
info = ['Unable to load {} data source(s):'.format(len(task.mInvalidSources))]
......@@ -1801,6 +1831,9 @@ class TimeSeries(QAbstractItemModel):
info.append('Path="{}" Error="{}"'.format(str(s), str(ex).replace('\n', ' ')))
info = '\n'.join(info)
messageLog(info, Qgis.Critical)
elif isinstance(task, TimeSeriesFindOverlapTask):
if success and len(task.mIntersections) > 0:
self.onFoundOverlap(task.mIntersections)
def addTimeSeriesSource(self, source: TimeSeriesSource) -> TimeSeriesDate:
"""
......
......@@ -90,8 +90,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>474</width>
<height>380</height>
<width>399</width>
<height>397</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
......@@ -464,7 +464,7 @@
<string>Reload</string>
</property>
<property name="icon">
<iconset resource="../../../QGIS/images/images.qrc">
<iconset>
<normaloff>:/images/themes/default/mActionReload.svg</normaloff>:/images/themes/default/mActionReload.svg</iconset>
</property>
<property name="autoRaise">
......@@ -478,7 +478,7 @@
<string>Clear</string>
</property>
<property name="icon">
<iconset resource="../../../QGIS/images/images.qrc">
<iconset>
<normaloff>:/images/themes/default/mActionDeleteSelected.svg</normaloff>:/images/themes/default/mActionDeleteSelected.svg</iconset>
</property>
<property name="autoRaise">
......@@ -549,6 +549,9 @@
<property name="text">
<string>Asynchronous QgsTasks</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="4" column="0">
......@@ -583,6 +586,38 @@
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QgsSpinBox" name="sbRasterOverlapSampleSize">
<property name="minimumSize">
<size>
<width>100</width>
<height>22</height>
</size>
</property>
<property name="suffix">
<string> px</string>
</property>
<property name="prefix">
<string/>
</property>
<property name="minimum">
<number>5</number>
</property>
<property name="maximum">
<number>64000</number>
</property>
<property name="value">
<number>25</number>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QLabel" name="label_14">
<property name="text">
<string>Raster overlap sample size</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
......@@ -602,7 +637,7 @@
</layout>
<action name="actionRefreshSensorList">
<property name="icon">
<iconset resource="../../../QGIS/images/images.qrc">
<iconset>
<normaloff>:/images/themes/default/mActionReload.svg</normaloff>:/images/themes/default/mActionReload.svg</iconset>
</property>
<property name="text">
......@@ -611,7 +646,7 @@
</action>
<action name="actionDeleteSelectedSensors">
<property name="icon">
<iconset resource="../../../QGIS/images/images.qrc">
<iconset>
<normaloff>:/images/themes/default/mActionDeleteSelected.svg</normaloff>:/images/themes/default/mActionDeleteSelected.svg</iconset>
</property>
<property name="text">
......
......@@ -75,7 +75,7 @@
<x>0</x>
<y>0</y>
<width>1035</width>
<height>26</height>
<height>17</height>
</rect>
</property>
<property name="contextMenuPolicy">
......@@ -192,7 +192,6 @@
<addaction name="actionZoomPixelScale"/>
<addaction name="mActionZoomToLayer"/>
<addaction name="actionZoomFullExtent"/>
<addaction name="optionMoveCenter"/>
</widget>
<widget class="QToolBar" name="toolBarIdentify">
<property name="windowTitle">
......@@ -206,6 +205,7 @@
</attribute>
<addaction name="actionIdentify"/>
<addaction name="separator"/>
<addaction name="optionMoveCenter"/>
<addaction name="optionIdentifyCursorLocation"/>
<addaction name="optionIdentifySpectralProfile"/>
<addaction name="optionIdentifyTemporalProfile"/>
......@@ -384,7 +384,7 @@
<bool>true</bool>
</property>
<property name="icon">
<iconset resource="../../../QGIS/images/images.qrc">
<iconset>
<normaloff>:/images/themes/default/mActionZoomIn.svg</normaloff>:/images/themes/default/mActionZoomIn.svg</iconset>
</property>
<property name="text">
......@@ -399,7 +399,7 @@
<bool>true</bool>
</property>
<property name="icon">
<iconset resource="../../../QGIS/images/images.qrc">
<iconset>
<normaloff>:/images/themes/default/mActionZoomOut.svg</normaloff>:/images/themes/default/mActionZoomOut.svg</iconset>
</property>
<property name="text">
......@@ -414,7 +414,7 @@
<bool>true</bool>
</property>
<property name="icon">
<iconset resource="../../../QGIS/images/images.qrc">
<iconset>
<normaloff>:/images/themes/default/mActionZoomFullExtent.svg</normaloff>:/images/themes/default/mActionZoomFullExtent.svg</iconset>
</property>
<property name="text">
......@@ -429,7 +429,7 @@
<bool>true</bool>
</property>
<property name="icon">
<iconset resource="../../../QGIS/images/images.qrc">
<iconset>
<normaloff>:/images/themes/default/mActionZoomActual.svg</normaloff>:/images/themes/default/mActionZoomActual.svg</iconset>
</property>
<property name="text">
......@@ -444,7 +444,7 @@
<bool>true</bool>
</property>
<property name="icon">
<iconset resource="../../../QGIS/images/images.qrc">
<iconset>
<normaloff>:/images/themes/default/mActionPan.svg</normaloff>:/images/themes/default/mActionPan.svg</iconset>
</property>
<property name="text">
......@@ -518,7 +518,7 @@
</action>
<action name="actionRefresh">
<property name="icon">
<iconset resource="../../../QGIS/images/images.qrc">
<iconset>
<normaloff>:/images/themes/default/mActionReload.svg</normaloff>:/images/themes/default/mActionReload.svg</iconset>
</property>
<property name="text">
......@@ -596,7 +596,7 @@
</action>
<action name="actionAddVectorData">
<property name="icon">
<iconset resource="../../../QGIS/images/images.qrc">
<iconset>
<normaloff>:/images/themes/default/mActionAddOgrLayer.svg</normaloff>:/images/themes/default/mActionAddOgrLayer.svg</iconset>
</property>
<property name="text">
......@@ -649,7 +649,7 @@
<bool>false</bool>
</property>
<property name="icon">
<iconset resource="../../../QGIS/images/images.qrc">
<iconset>
<normaloff>:/images/themes/default/mActionPropertiesWidget.svg</normaloff>:/images/themes/default/mActionPropertiesWidget.svg</iconset>
</property>
<property name="text">
......@@ -724,7 +724,7 @@
<bool>true</bool>
</property>
<property name="icon">
<iconset resource="../../../QGIS/images/images.qrc">
<iconset>
<normaloff>:/images/themes/default/mActionSelectRectangle.svg</normaloff>:/images/themes/default/mActionSelectRectangle.svg</iconset>
</property>
<property name="text">
......@@ -739,7 +739,7 @@
<bool>true</bool>
</property>
<property name="icon">
<iconset resource="../../../QGIS/images/images.qrc">
<iconset>
<normaloff>:/images/themes/default/mActionSelectPolygon.svg</normaloff>:/images/themes/default/mActionSelectPolygon.svg</iconset>
</property>
<property name="text">
......@@ -754,7 +754,7 @@
<bool>true</bool>
</property>
<property name="icon">
<iconset resource="../../../QGIS/images/images.qrc">
<iconset>
<normaloff>:/images/themes/default/mActionSelectFreehand.svg</normaloff>:/images/themes/default/mActionSelectFreehand.svg</iconset>
</property>
<property name="text">
......@@ -766,7 +766,7 @@
<bool>true</bool>
</property>
<property name="icon">
<iconset resource="../../../QGIS/images/images.qrc">
<iconset>
<normaloff>:/images/themes/default/mActionSelectRadius.svg</normaloff>:/images/themes/default/mActionSelectRadius.svg</iconset>
</property>
<property name="text">
......@@ -775,7 +775,7 @@
</action>
<action name="actionExportMapsToImages">
<property name="icon">
<iconset resource="../../../QGIS/images/images.qrc">
<iconset>
<normaloff>:/images/themes/default/mActionSaveMapAsImage.svg</normaloff>:/images/themes/default/mActionSaveMapAsImage.svg</iconset>
</property>
<property name="text">
......@@ -795,7 +795,7 @@
<bool>true</bool>
</property>
<property name="icon">
<iconset resource="../../../QGIS/images/images.qrc">
<iconset>
<normaloff>:/images/themes/default/lockedGray.svg</normaloff>:/images/themes/default/lockedGray.svg</iconset>
</property>
<property name="text">
......@@ -823,7 +823,7 @@
</action>
<action name="mActionSaveEdits">
<property name="icon">
<iconset resource="../../../QGIS/images/images.qrc">
<iconset>
<normaloff>:/images/themes/default/mActionSaveEdits.svg</normaloff>:/images/themes/default/mActionSaveEdits.svg</iconset>
</property>
<property name="text">
......@@ -838,7 +838,7 @@
<bool>true</bool>
</property>
<property name="icon">