QGIS Temporal Controller mit Rasterdaten

Oct 20 2020

Ab QGIS 3.14 ist TimeManager tot, es lebe der Temporal Controller! Es gibt viele Tutorials, wie "Temporal Controller" mit Vektorebenen umgeht, was gut aussieht. Ich arbeite jedoch viel mit Rasterdaten und kann keine Informationen finden, wie ich ein ähnliches Verhalten wie in TimeManager erzielen kann.

Ich habe eine NetCDF wie diese:

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" ;
}

Wenn ich diese Datei in QGIS lade, werden die Zeitschritte als Bänder geladen (auf der Symbologie-Registerkarte werden sie als Band 1: time=52244 (days since 1858-11-17 00:00:00)usw. angezeigt ...). Der Temporal-Tab bietet jedoch nur einen festen Zeitbereich, wie im Screenshot gezeigt:

Ich würde erwarten, dass der Temporal Controller automatisch erkennt, dass die Ebenen Zeitschritte sind, aber die Schaltfläche "Automatisch" inaktiv ist. Hat jemand Erfahrung mit Rasterebenen in "Temporal Controller"? Wie kann ich meine Daten so ändern, dass sie richtig interpretiert werden? Oder ist diese Funktionalität einfach nicht implementiert (da der Temporal Controller ziemlich neu ist)?

Antworten

1 Joonala Nov 13 2020 at 13:27

Ich habe ein PyQGIS-Skript geschrieben, um die Problemumgehung für NetCDF-Dateien zu handhaben, bei der das updateTemporalRangeSignal des temporären Controllers verwendet wird. Kopieren Sie einfach das folgende Skript in das QGIS Python-Editorfenster, bearbeiten Sie Ihre Daten, führen Sie das Skript aus und verwenden Sie Temporal Controller wie gewohnt.

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))) 

Hier ist eine einfache GIF-Animation, die die Verwendung zeigt: