Repository navigation
to_geotiff: write best-practice symbology sidecars for continuous rasters (QGIS color ramp) #3537
Description
Reason or Problem
When you write a categorical raster with arr.xrs.to_geotiff(), xarray-spatial drops a PAM .aux.xml sidecar holding the class names and colors, so QGIS opens the file already showing labelled, coloured classes. Continuous rasters (DEMs, slope, NDVI, and so on) get no such treatment. They open in QGIS as a flat grayscale stretch with whatever default min/max GDAL guesses, and the user has to set up a colour ramp by hand every time.
Proposal
Add an opt-in color_ramp parameter to to_geotiff that, for continuous single-band rasters, writes best-practice symbology sidecars so QGIS applies a sensible colour ramp on open. This is the continuous-data counterpart to the categorical RAT sidecar we already write.
Design:
Two sidecars are written when color_ramp is set and the raster is continuous (no category_names attr) and single-band:
- A QGIS
.qmlstyle file (dem.qml, replacing the extension, which is the name QGIS auto-loads) with asinglebandpseudocolorrenderer and an interpolated colour ramp. This is what gives QGIS real colours. STATISTICS_MINIMUM/MAXIMUM/MEAN/STDDEVin the existing PAM.aux.xml(dem.tif.aux.xml), which GDAL tools and QGIS use for a min/max stretch even if the.qmlis ignored.
Colour ramps ship as hardcoded control points (viridis default, plus a small curated set) so there is no matplotlib runtime dependency. Statistics are computed with a single backend-aware reduction pass covering numpy, cupy, dask+numpy and dask+cupy. A color_ramp_range=(min, max) escape hatch skips the reduction for large dask graphs.
Usage:
dem.xrs.to_geotiff('dem.tif', color_ramp='viridis')
# writes dem.tif, dem.qml, dem.tif.aux.xmlOpen dem.tif in QGIS and it renders with the viridis ramp stretched across the data range.
Value: Continuous rasters written by xarray-spatial open in QGIS looking right without manual styling, matching the experience categorical rasters already get.
Stakeholders and Impacts
Anyone writing continuous rasters for use in QGIS. Touches the geotiff writer (to_geotiff wrapper) and a new _symbology module plus the existing _pam sidecar module. Default behaviour is unchanged because the feature is opt-in.
Drawbacks
Writes extra sidecar files. The .qml format is QGIS-specific (other GIS tools ignore it, though they still read the GDAL statistics). For the dask streaming write path, computing statistics adds a separate reduction pass unless color_ramp_range is supplied.
Alternatives
- Statistics only, no
.qml: works in all GDAL tools but only gives a grayscale stretch, no colour. - Automatic for every continuous write: rejected to avoid surprise side-files and a forced statistics pass on writes that do not want symbology.
- A GDAL colour table embedded in the TIFF: discrete/paletted only, not suited to a continuous ramp.
Unresolved Questions
On a constant raster (min == max) the statistics sidecar is still written but the .qml is skipped, since a single-stop ramp is degenerate.
Activity
Address review: skip symbology under pack, doc notes, VRT/pack tests (#…
Mirror color_ramp kwargs on _write_geotiff_gpu for signature parity (#…
Write continuous-raster symbology sidecars from to_geotiff (#3537) (#…
geotiff: cover symbology sidecars on dask, dask+GPU, 3D single-band, …