Skip to content

[GH-3365] Support NaN as a raster band nodata value - #3366

Merged
jiayuasu merged 1 commit into
apache:masterfrom
jiayuasu:nan-nodata-support
Sep 14, 2026
Merged

jiayuasu merged 1 commit into
apache:masterfrom
jiayuasu:nan-nodata-support

Conversation

@jiayuasu

Copy link
Copy Markdown
Member

Did you read the Contributor Guide?

Is this PR related to a ticket?

What changes were proposed in this PR?

Sedona used NaN as its internal marker for "this band has no nodata value", so a GeoTIFF whose GDAL_NODATA tag is nan (what GDAL writes for floating point rasters such as DTM/DSM tiles) was indistinguishable from one with no nodata at all: RS_BandNoDataValue returned null, every exclude-nodata path kept the NaN pixels, and RS_SetBandNoDataValue(raster, NaN) was a silent no-op.

  • RasterUtils.hasNoDataValue tells a NaN nodata value from a missing one; RasterUtils.isNoData compares pixels NaN-safely (and treats -0.0 as 0.0). RS_BandNoDataValue returns NaN for such bands, RS_SetBandNoDataValue can set, replace and remove it (NaN on an integral band throws).
  • Every consumer that tested presence with Double.isNaN(getNoDataValue(...)) or compared with == / != goes through the helpers: RS_Count, RS_SummaryStats*, RS_ZonalStats*, RS_BandIsNoData, RS_NormalizeAll, RS_SetPixelType (refuses NaN nodata on integral targets), RS_SetValues, RS_MinConvexHull (returns null when every pixel is nodata), RS_ReplaceNoDataValues / RS_NoDataValueMask.
  • The nodata mask used by bilinear/bicubic RS_Resample is a 0/1 flag restored from the band's nodata value. The old NaN-marker encoding could not restore a NaN nodata value and zeroed every valid pixel of integer rasters. Holes are filled as deep as the interpolation kernel reads before resampling.
  • RS_SetBandNoDataValue(..., replace=true) compares through the NaN-safe helper; the band copy itself comes from [GH-3330] Preserve other raster bands when replacing NoData #3347.
  • The raster serializer reads the with_bands override flags first: an explicit nodata=float("nan") clears any NODATA category; an omitted nodata with a dtype change keeps an inherited NaN nodata for float/double outputs and rejects it for integral ones.
  • Tile padding takes a nullable value end to end: null inherits the band's nodata value, an explicit NaN pads with NaN and declares NaN as the tiles' nodata value. The primitive double entry points stay for compatibility with NaN meaning "inherit".
  • Docs for RS_BandNoDataValue, RS_SetBandNoDataValue, RS_FromGeoTiff, RS_MinConvexHull, RS_Tile, RS_TileExplode.

The stock GeoTools 33.1 reader already keeps a NaN nodata category and the stock ImageIO-Ext 1.4.15 tag parser already accepts inf, so no reader change is needed. Writing a NaN nodata value back with RS_AsGeoTiff still needs a writer change and is a follow-up.

How was this patch tested?

  • New fixtures under spark/common/src/test/resources/raster_geotiff_nodata/ (4x4 Float32, GDAL_NODATA=nan, inf, -inf, with and without scale/offset; built with gdal_translate / tiffset).
  • New tests in RasterConstructorsTest, RasterBandAccessorsTest, RasterBandEditorsTest, RasterEditorsTest, GeometryFunctionsTest, SerdeTest, RasterUtilsTest, and rasteralgebraTest (read, counts, statistics, replace, RS_Value, RS_SetBandNoDataValue with double('NaN')).
  • Full common suite and rasteralgebraTest pass locally.

Did this PR include necessary documentation updates?

  • Yes, I have updated the documentation.

Sedona used NaN as its own marker for "no nodata value", so a GeoTIFF
whose GDAL_NODATA tag is nan (what GDAL writes for floating point
rasters) was indistinguishable from one without nodata: the value read
back as null, every exclude-nodata path kept the NaN pixels, and
RS_SetBandNoDataValue(raster, NaN) was a silent no-op.

RasterUtils.hasNoDataValue tells a NaN nodata value from a missing one
and RasterUtils.isNoData compares pixels NaN-safely; the nodata checks in
counts, statistics, zonal statistics, bandIsNoData, set/replace/remove
nodata, normalizeAll, setPixelType, the pixel editors, min convex hull,
resampling and tiling go through them. The resampling nodata mask is a
0/1 flag restored from the band's nodata value (the NaN-marker mask also
zeroed every valid pixel of integer rasters), holes are filled as deep
as the interpolation kernel reads, replace keeps untouched bands, the
serializer reads the with_bands override flags before deciding what a
NaN on the wire means, and tile padding takes a nullable value.

The stock GeoTools reader already keeps a NaN nodata category and the
stock ImageIO-Ext tag parser already accepts inf, so no reader change is
needed. Writing NaN nodata back with RS_AsGeoTiff is a follow-up.
@jiayuasu jiayuasu added this to the sedona-2.0.0 milestone Sep 14, 2026
@jiayuasu
jiayuasu merged commit 7a23ce2 into apache:master Sep 14, 2026
43 checks passed
@jiayuasu
jiayuasu deleted the nan-nodata-support branch September 14, 2026 02:07
james-willis added a commit to james-willis/sedona-db that referenced this pull request Sep 30, 2026
An xfail that starts passing is a result, not a non-event: it means the
behaviour it describes has changed, usually because an upstream fix landed,
and the marker is now lying about the state of the world. pytest's default
reports XPASS and exits 0, so that goes unnoticed and stale reasons
accumulate — the suite keeps asserting divergences that no longer exist, and
anyone reading one is sent to re-investigate a closed problem.

Turn xfail_strict on so the run fails and names the test instead. The setting
lives in a new root pytest.ini because CI runs each suite from its own
directory, so pytest walks up from there and one file covers every suite,
including ones added later. All 82 xfail markers are currently in
integration/spark-parity; the other Python suites have none, so this is
mostly a guard on future ones.

Four reasons were already stale, and are rewritten to name the release they
wait on. Sedona master supports NaN as a raster band nodata value
(apache/sedona#3366, with #3368 writing it to GeoTIFF), so both engines now
agree on the NaN cases in test_rs_bandnodatavalue and
test_rs_setbandnodatavalue. That is not in a release — 1.9.1 predates it, and
a release is what CI installs — so the tests keep their xfail, now saying so
rather than describing a divergence that is already fixed.
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.

Support NaN as a raster band nodata value

1 participant