Skip to content

[Bug]: Project Raster (#953) bilinear differs from gdalwarp when downsampling and next to no-data #973

Open

Description

What happened?

I compared raster.project with gdalwarp (GDAL 3.13.1) on the same output grid. For each case, gdalwarp got the exact CRS, bounds and size of RustGIS's output. The input is a 25 m DEM of Val Venosta (EPSG:25832, Float32, 1865 × 1373, no-data 0 on 107,345 cells) and its slope classes (Byte, 7 classes and a colour table).

These agree with GDAL:

Case Result
average, 100 m, same CRS (4 × 4 pixels per cell) all 153,925 cells within 0.0002
nearest, 25 m, to EPSG:3003 (Monte Mario / Italy zone 1, with the datum shift) all 2,453,300 cells identical
nearest, 100 m, to EPSG:3035 153,386 of 153,392 cells identical

mode differed in its ties; PR #972 makes it follow GDAL, and it then matches in all 153,537 cells. Two differences remain, both in bilinear, and both look like choices for you to make, so I didn't change them:

1. bilinear doesn't widen the kernel when downsampling. DEM at 100 m to EPSG:3035 (4× coarser): only 0.2 % of cells are equal; the mean difference is 3.24 m and the largest 40.0 m. gdalwarp scales the bilinear kernel to the output cell when the output is coarser. Disabling that (-wo XSCALE=1 -wo YSCALE=1) makes GDAL agree with RustGIS: largest difference 0.0037 m. #953 says bilinear and cubic sample at the cell centre, so this may be intended; then a sentence in the docs would help, since the result aliases where gdalwarp smooths. I didn't test cubic.

2. bilinear gives a value to cells whose centre falls on a no-data pixel. RustGIS fills 3,205 cells that gdalwarp leaves as no-data (DEM at 25 m to EPSG:3003), and 201 at 100 m to EPSG:3035. For all of them, the source pixel under the cell's centre is no-data. GDAL then writes no-data, and otherwise renormalises over the valid neighbours. In the EPSG:3003 case, the cells valid in both agree within 0.0005 m. The same difference was reported for terrio's warp in opengeos/terrio#307.

Steps to reproduce

rustgis-cli run --data dem.tif --cmd 'raster.project={"layer":"dem","crs":3035,"cellSize":100,"resampling":"bilinear","path":"rg.tif"}'
# gdalwarp on the same grid: CRS, -te and -ts read from rg.tif
gdalwarp -t_srs EPSG:3035 -te <xmin ymin xmax ymax of rg.tif> -ts <width height of rg.tif> -r bilinear -srcnodata 0 -dstnodata 0 -et 0 dem.tif gdal.tif

The same with "crs":3003,"cellSize":25 for the cells next to no-data.

Expected behavior

The same values as gdalwarp: (1) a kernel scaled to the output cell for bilinear and cubic when downsampling, or a note in the docs that they sample point-wise; (2) no-data where the pixel under the cell's centre is no-data. If you'd like either, I can open a pull request.

How are you running RustGIS?

rustgis-cli from a Windows build made by the Release workflow on my fork: main at 9285e05 plus an unrelated legend change (#889).

Version

main 9285e05 (after v0.4.0)

Operating system, browser and graphics

Windows 11

Sample data

Copernicus DEM GLO-30 resampled to 25 m over Val Venosta (Italy). The comparison scripts can be shared.

Activity

  1. pick2510 commented on Oct 27, 2026

    @pick2510
    Contributor

    Thanks for the careful comparison, and for #972.

    1. Bilinear and cubic when going coarser. This is intended. Project Raster reads bilinear and cubic at the cell centre, as GRASS's r.proj does, and uses average (or mode for classes) to go coarser. Widening the window as gdalwarp does would change every bilinear and cubic result made so far, so we'll keep it. The docs should say so plainly, though. A sentence under Resampling in docs/user-guide/analysis.md would do: going coarser, bilinear and cubic skip detail and can give a jagged result, so use average, which comes closest to gdalwarp's smoothing there.

    2. Cells whose centre falls on a no-data pixel. Agreed, that's a bug: RustGIS gives a value as long as any of the four neighbours is valid, so no-data areas shrink by up to half a pixel. Please go ahead with the PR and follow GDAL's rule: no-data where the pixel under the cell centre is no-data; otherwise average the valid neighbours as now. A few things to keep in mind:

    • Sampler in crates/analysis/src/warp.rs also serves the image warp with control points (feat(imagery): warp a raster with any control-point fit #669). Apply the rule to bands with a no-data value only. Images with an alpha band should keep blending by alpha at their edges.
    • Do the same for cubic where it falls back to bilinear.
    • Add a regression test with a small raster that has a no-data hole, one checking the cell over the hole and one checking its valid neighbours.

    The docs sentence for point 1 can go in the same PR or a separate one, as you prefer.

  2. GISspecialistAlejandraDuque commented on Oct 27, 2026

    @GISspecialistAlejandraDuque
    ContributorAuthor

    Thanks, I'll take it, with the docs sentence for point 1 in the same pull request. The rule goes in Sampler::sample, before both kernels, so it also covers cubic when it falls back to bilinear, and only where the raster has a no-data value, so alpha keeps blending. The regression test uses a 6 × 6 raster with a 2 × 2 no-data hole, with expected values from gdalwarp 3.12.2 and 3.13.1.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions