QGIS Temporal Controller con dati raster
A partire da QGIS 3.14 TimeManager è morto, lunga vita al controller temporale! Ci sono molti tutorial là fuori su come "Temporal Controller" si occupa dei livelli vettoriali, il che sembra carino. Tuttavia, sto lavorando molto con i dati raster e non riesco a trovare alcuna informazione su come ottenere un comportamento simile a quello di TimeManager.
Ho un NetCDF come questo:
ncdump -h SLA_ts.nc
netcdf SLA_ts {
dimensions:
time = 124 ;
lat = 5 ;
lon = 5 ;
variables:
char crs ;
crs:grid_mapping_name = "latitude_longitude" ;
crs:long_name = "CRS definition" ;
crs:semi_major_axis = 6378136.3 ;
crs:inverse_flattening = 298.257 ;
float time(time) ;
time:long_name = "time" ;
time:calendar = "standard" ;
time:units = "days since 1858-11-17 00:00:00.0" ;
time:actual_range = 52244., 55987.735625 ;
float lat(lat) ;
lat:standard_name = "latitude" ;
lat:long_name = "latitude" ;
lat:units = "degrees_north" ;
lat:actual_range = 53.8, 54.2 ;
float lon(lon) ;
lon:standard_name = "longitude" ;
lon:long_name = "longitude" ;
lon:units = "degrees_east" ;
lon:actual_range = 7.5, 7.9 ;
float sla(time, lat, lon) ;
sla:_FillValue = NaNf ;
sla:grid_mapping = "crs" ;
sla:long_name = "sea level anomaly" ;
sla:unit = "m" ;
// global attributes:
:Conventions = "CF-1.5" ;
}
Quando carico questo file in QGIS, i passaggi temporali vengono caricati come fasce (nella scheda Simbologia, vengono visualizzati come Band 1: time=52244 (days since 1858-11-17 00:00:00)e così via ...). Tuttavia, la scheda temporale offre solo un intervallo di tempo fisso come mostrato nello screenshot:
Mi aspetto che il controller temporale riconosca automaticamente che i livelli sono fasi temporali ma il pulsante "Automatico" è inattivo. Qualcuno ha esperienza con i livelli raster in "Controller temporale"? Come posso modificare i miei dati per essere interpretati correttamente? O questa funzionalità semplicemente non è implementata (poiché il controller temporale è abbastanza nuovo)?
Risposte
Ho scritto uno script PyQGIS per gestire la soluzione alternativa per i file NetCDF che utilizza il updateTemporalRangesegnale del controller temporale . Basta copiare il seguente script nella finestra dell'editor di QGIS Python, modificare le date, eseguire lo script e utilizzare Temporal Controller come al solito.
import datetime
# These functions are part of https://github.com/GispoCoding/qgis_plugin_tools/blob/master/tools/raster_layers.py
def set_raster_renderer_to_singleband(layer: QgsRasterLayer, band: int = 1) -> None:
"""
Set raster renderer to singleband
:param layer: raster layer
"""
# https://gis.stackexchange.com/a/377631/123927 and https://gis.stackexchange.com/a/157573/123927
provider: QgsRasterDataProvider = layer.dataProvider()
renderer: QgsSingleBandGrayRenderer = QgsSingleBandGrayRenderer(layer.dataProvider(), band)
stats: QgsRasterBandStats = provider.bandStatistics(band, QgsRasterBandStats.All, layer.extent(), 0)
min_val = max(stats.minimumValue, 0)
max_val = max(stats.maximumValue, 0)
enhancement = QgsContrastEnhancement(renderer.dataType(band))
contrast_enhancement = QgsContrastEnhancement.StretchToMinimumMaximum
enhancement.setContrastEnhancementAlgorithm(contrast_enhancement, True)
enhancement.setMinimumValue(min_val)
enhancement.setMaximumValue(max_val)
layer.setRenderer(renderer)
layer.renderer().setContrastEnhancement(enhancement)
layer.triggerRepaint()
def set_band_based_on_range(layer: QgsRasterLayer, t_range: QgsDateTimeRange) -> int:
"""
:param layer: raster layer
:param t_range: temporal range
:return: band number
"""
band_num = 1
tprops: QgsRasterLayerTemporalProperties = layer.temporalProperties()
if tprops.isVisibleInTemporalRange(t_range) and t_range.begin().isValid() and t_range.end().isValid():
if tprops.mode() == QgsRasterLayerTemporalProperties.ModeFixedTemporalRange:
layer_t_range: QgsDateTimeRange = tprops.fixedTemporalRange()
start: datetime.datetime = layer_t_range.begin().toPyDateTime()
end: datetime.datetime = layer_t_range.end().toPyDateTime()
delta = (end - start) / layer.bandCount()
band_num = int((t_range.begin().toPyDateTime() - start) / delta) + 1
set_raster_renderer_to_singleband(layer, band_num)
return band_num
def set_fixed_temporal_range(layer: QgsRasterLayer, t_range: QgsDateTimeRange) -> None:
"""
Set fixed temporal range for raster layer
:param layer: raster layer
:param t_range: fixed temporal range
"""
mode = QgsRasterLayerTemporalProperties.ModeFixedTemporalRange
tprops: QgsRasterLayerTemporalProperties = layer.temporalProperties()
tprops.setMode(mode)
if t_range.begin().timeSpec() == 0 or t_range.end().timeSpec() == 0:
begin = t_range.begin()
end = t_range.end()
begin.setTimeSpec(Qt.TimeSpec(1))
end.setTimeSpec(Qt.TimeSpec(1))
t_range = QgsDateTimeRange(begin, end)
tprops.setFixedTemporalRange(t_range)
tprops.setIsActive(True)
def temporal_range_changed(t_range: QgsDateTimeRange):
layer = iface.activeLayer()
if isinstance(layer, QgsRasterLayer):
set_band_based_on_range(layer, t_range)
def set_range():
mode = QgsRasterLayerTemporalProperties.ModeFixedTemporalRange
temporal_controller: QgsTemporalController = iface.mapCanvas().temporalController()
temporal_controller.updateTemporalRange.connect(temporal_range_changed)
# Add one second to make the last frame visible
set_fixed_temporal_range(iface.activeLayer(), QgsDateTimeRange(datetime.datetime(2020, 11, 2, 15, 0), datetime.datetime(2020, 11, 3, 11, 0, 1)))
Ecco una semplice animazione gif che mostra l'utilizzo: