Skip to content

[GH-3112] RS_AsRaster fills uncovered pixels with noDataValue - #3114

Merged
jiayuasu merged 13 commits into
apache:masterfrom
james-willis:fix/gh-3112-nodata-background
Jul 28, 2026
Merged

[GH-3112] RS_AsRaster fills uncovered pixels with noDataValue#3114
jiayuasu merged 13 commits into
apache:masterfrom
james-willis:fix/gh-3112-nodata-background

Conversation

@james-willis

@james-willis james-willis commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

Did you read the Contributor Guide?

Is this PR related to a ticket?

What changes were proposed in this PR?

RS_AsRaster previously only attached noDataValue as band metadata; pixels not covered by the geometry kept the allocation default of 0, a valid pixel value indistinguishable from data. Now Rasterization fills the freshly-allocated raster with the noDataValue before burning the geometry, so uncovered pixels (including interior holes) read back as the declared nodata — matching PostGIS ST_AsRaster (nodataval fills untouched cells) and the gdal_rasterize -init <nodata> -a_nodata <nodata> idiom. The fill happens before burning (not after) because a burn value of 0 would otherwise be indistinguishable from background.

Omitted noDataValue now inherits the reference band's nodata (used for both the band metadata and the background fill) instead of leaving an all-zero background with no nodata metadata. If the reference band has no nodata value — or its nodata is not representable in the output pixel type — the call errors with guidance to pass an explicit representable noDataValue to override (null opts out of a nodata value, and is reachable only through the programmatic API / the widest overload).

Representability validation. A noDataValue or burn value that cannot be represented exactly in the output pixel type is now rejected up front instead of silently coerced: an out-of-range or fractional value on an integer band, a value that does not round-trip through 32-bit float on an F band, or a non-finite NaN/±Infinity. Silent coercion would store a different number than the caller set (so the background read back as data), and for some inputs previously crashed deep in GeoTools. A burn value equal to the resolved noDataValue is also rejected, since every covered pixel would then read as nodata.

Because this validation lives in the shared rasterization path, RS_SetValues likewise now rejects a non-representable or non-finite value for the target band rather than silently coercing it — a consistency change to that function. RS_Clip is unaffected (its fixed mask marker is always representable). RS_SetValues still rasterizes its geometry mask with an explicit null nodata and reads the result purely as a 0/non-0 mask, so its masking behavior is otherwise unchanged.

Existing test expectations that asserted 0 backgrounds alongside a non-zero noDataValue are updated to expect the nodata — those expectations were pinning the bug from #3112.

How was this patch tested?

  • New testAsRasterBackgroundIsNoDataValue: a polygon with an interior hole where the hole and the outside must read back as the nodata; a value = 0 burn that must stay distinguishable from the nodata background; the geometry-extent output path; and the null-nodata default keeping 0. The test was written first and fails on master.
  • Additional tests cover: signed-zero (-0.0) fill; representability rejection of out-of-range/fractional integer, non-exact-float, and non-finite values; the inherited-nodata error (no nodata to inherit, and inherited value not representable in the output type); and the burn-value == noDataValue guard.
  • The full common module raster suite and the Spark rasteralgebraTest / rasterIOTest suites pass. RS_SetValues, RS_Clip, and the zonal-stats path (which pass null nodata and rely on 0-backed masks) are unaffected in their masking behavior.

Did this PR include necessary documentation updates?

  • Yes, I have updated the documentation.

@james-willis
james-willis marked this pull request as ready for review July 17, 2026 08:25
@james-willis
james-willis requested a review from jiayuasu as a code owner July 17, 2026 08:25
@james-willis
james-willis requested a review from prantogg July 17, 2026 17:51
Comment thread docs/api/sql/Raster-Operators/RS_AsRaster.md Outdated
@jiayuasu jiayuasu added this to the sedona-1.9.1 milestone Jul 19, 2026
# Conflicts:
#	common/src/test/java/org/apache/sedona/common/raster/RasterConstructorsTest.java
@james-willis
james-willis requested a review from paleolimbot July 20, 2026 20:57
…ead of coercing

Validate the noDataValue against the target pixel type before it is used as
the background fill and the band nodata metadata. An out-of-range value (for
example -1.0 or 300.0 on an unsigned 8-bit band) or a fractional value on an
integer band would previously be silently coerced into the sample, so the
background read back as data while the recorded nodata disagreed. Reject such
a value with an IllegalArgumentException naming the value and pixel type, and
reuse the validated value for both the fill and the metadata.
…aValue omitted

The overloads without a noDataValue argument now inherit the reference
raster's first band nodata value, using it both as the output band's
nodata metadata and as the fill for the pixels the geometry does not
cover, and error when the reference band has no nodata value to inherit.
An explicit null noDataValue on the widest overload still opts out of a
nodata value and keeps a zero background.

RS_Clip and RS_SetValues rasterize a 0-background mask, so they now pass
an explicit null noDataValue to keep that zero background instead of
relying on the omitted-argument default.
Comment thread common/src/main/java/org/apache/sedona/common/utils/RasterUtils.java Outdated
Comment thread common/src/main/java/org/apache/sedona/common/raster/Rasterization.java Outdated
@jiayuasu

Copy link
Copy Markdown
Member

@james-willis address conflict?

@james-willis

Copy link
Copy Markdown
Collaborator Author

@james-willis address conflict?

I had a comment on the thread about no data values vs unburned value and how to represent. I want to resolve on that question.

Resolve RasterConstructorsTest conflict: combine master's exact
cell-traversal (DDA) burn positions with this branch's background fill.
Uncovered pixels are filled with the band noDataValue (3.0 / 5.0), and
the noDataValue call arguments master had flipped to 0d are restored so
the fill is exercised.
fillBackground skipped the fill when the background compared equal to 0,
which is true for both +0.0 and -0.0. A -0.0 noDataValue therefore left
uncovered pixels at the zero-initialized +0.0 while the band's nodata
metadata was -0.0. RS_Count(band, excludeNoData=true) compares pixels to
the metadata with Double.compare, which distinguishes the two zeros, so
the uncovered pixels were miscounted as data.

Skip the fill only for +0.0 (Double.compare(x, 0.0) == 0), so -0.0 is
explicitly filled and pixel value and nodata metadata agree.
… the band's float32 storage

A nodata value is a sentinel that must round-trip exactly through the
band's storage. On a 32-bit float band, assertNoDataValueRepresentable
previously rejected only magnitudes that overflowed Float.MAX_VALUE,
accepting values like 0.1 that are not exactly representable in float32
(stored as 0.10000000149...). The recorded sentinel then differed from
the caller's value, so comparisons against it would miss, and 0.1 in
particular crashed deep in GeoTools.

Reject any finite value that does not survive the double->float->double
round-trip. Overflow to +/-Infinity is caught by the same test; NaN and
the infinities are exactly representable and remain allowed.
@jiayuasu

Copy link
Copy Markdown
Member

@james-willis I am fine with the PostGIS behavior which treats the NoData value and unburned value the same. And that's the behavior we have been following.

@paleolimbot

Copy link
Copy Markdown
Member

Sorry, I missed that thread. I don't have strong feelings about what exactly this should do.

…tion

- Inherit path: rethrow an actionable inherit-specific error when the
  reference band nodata is not representable in the output pixel type,
  pointing at the explicit-noDataValue override.
- Validate the burn value for representability (mirrors noDataValue) and
  reject a burn value equal to the noDataValue.
- Reject non-finite (NaN / +/-Infinity) nodata and value on float/double
  bands up front instead of silently dropping (NaN) or crashing in
  GeoTools (Infinity).
- Fix nodata-less RS_AsRaster tutorial examples that now error.
Comment thread common/src/main/java/org/apache/sedona/common/raster/RasterConstructors.java Outdated
A burn value is ordinary pixel data, not a nodata sentinel, so validate it with
RasterUtils.assertBurnValueRepresentable: a fractional value on a float/double
band (e.g. 0.1 on 'F') is accepted and rounded to the pixel type, as PostGIS
ST_AsRaster and gdal_rasterize do, while an out-of-range or fractional integer
burn is still rejected. The noDataValue keeps the strict exact-storage check.

Also reject negative zero as a noDataValue on integer pixel types: an integer
sample cannot preserve the sign, so a -0.0 sentinel reads back as +0 and would
be counted as data (it previously slipped past the range/fractional check since
-0.0 compares equal to +0.0). It remains valid on float/double bands.
@jiayuasu
jiayuasu merged commit ff98e17 into apache:master Jul 28, 2026
44 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

RS_AsRaster: pixels not covered by the geometry get value 0 instead of noDataValue

3 participants