Repository navigation
[Bug]: Project Raster (#953) bilinear differs from gdalwarp when downsampling and next to no-data #973
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.tifThe 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
pick2510 commentedon Oct 27, 2026
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.projdoes, and usesaverage(ormodefor 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 indocs/user-guide/analysis.mdwould do: going coarser, bilinear and cubic skip detail and can give a jagged result, so useaverage, 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:
Samplerincrates/analysis/src/warp.rsalso 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.cubicwhere it falls back to bilinear.The docs sentence for point 1 can go in the same PR or a separate one, as you prefer.
GISspecialistAlejandraDuque commentedon Oct 27, 2026
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.