apache / apache/sedona

Clearing NoData on one raster band changes another band's MapAlgebra results

Open
#3,324 1 comment 0 reactions 0 assignees View on GitHub
bug
Dominant language
Java
Stars
2.4k
Forks
784
Avg merge
1d 12h
Merged PRs (30d)
58

Description

## Expected behavior

Clearing NoData on band 2 should leave band 1's NoData handling unchanged. If band 1 still reports NoData `0`, MapAlgebra should continue treating its zero-valued pixels as NoData.

This is a follow-up to #3312, tracked separately from the SQL NULL-handling fix in #3311.

## Actual behavior

Starting with a two-band GeoTIFF whose bands both use NoData `0`, clearing only band 2 leaves the band metadata at `(0, null)`, but changes band 1's result for `out = rast[0] + 1;` from `NaN` to `1.0`.

That change happens in memory, before writing the cleared raster. The clear path removes the shared `GC_NODATA` property from the coverage and image, while band 1's sample dimension still declares NoData `0`. Jiffle reads the image property, so it starts treating band 1's zeros as ordinary data.

A subsequent GeoTIFF round-trip also changes `(0, null)` back to `(0, 0)`. The writer uses one dataset-wide NoData value, so mixed per-band states cannot be preserved by the current path.

## Steps to reproduce

Run in JShell with `sedona-common` and its dependencies on the classpath. The first GeoTIFF round-trip creates an input with the image-level NoData property used by real GeoTIFFs. `rast[0]` refers to band 1.

```java
import org.apache.sedona.common.raster.*;

var raster = RasterConstructors.makeEmptyRaster(
2, 20, 20, 0, 0, 1, -1, 0, 0, 4326);
raster = RasterBandEditors.setBandNoDataValue(raster, 1, 0.0);
raster = RasterBandEditors.setBandNoDataValue(raster, 2, 0.0);
var loaded = RasterConstructors.fromGeoTiff(RasterOutputs.asGeoTiff(raster));

var before = MapAlgebra.mapAlgebra(loaded, "d", "out = rast[0] + 1;");
System.out.println(before.getRenderedImage().getData().getSampleDouble(0, 0, 0));
// NaN

var cleared = RasterBandEditors.setBandNoDataValue(loaded, 2, null);
System.out.println(RasterBandAccessors.getBandNoDataValue(cleared, 1));
// 0.0
System.out.println(RasterBandAccessors.getBandNoDataValue(cleared, 2));
// null

var after = MapAlgebra.mapAlgebra(cleared, "d", "out = rast[0] + 1;");
System.out.println(after.getRenderedImage().getData().getSampleDouble(0, 0, 0));
// 1.0 (expected NaN: band 1 was not cleared)

var roundTrip = RasterConstructors.fromGeoTiff(RasterOutputs.asGeoTiff(cleared));
System.out.println(RasterBandAccessors.getBandNoDataValue(roundTrip, 2));
// 0.0 (band 2's cleared NoData is restored)
```

The regression coverage should check both bands after clearing either one, including MapAlgebra before serialization. For GeoTIFF output, rejecting unsupported mixed NoData states would be preferable to silently changing them.

## Sedona version

`2.0.0-SNAPSHOT`, reproduced on #3312 at `2b06236af00c0789c9e0eff69d9562caf6e90209`.

## API type

Java (`sedona-common`; used by the SQL raster functions).

## JRE version

17, GeoTools 33.1.

## Environment

Local macOS. The reproducer does not require Spark.

## Existing issues

- [x] I searched the existing issues and did not find a duplicate.

Contributor guide

Open the contributing guide

Research direction

Start with RasterBandEditors.setBandNoDataValue, RasterBandAccessors, MapAlgebra.mapAlgebra, and the GeoTIFF round-trip in the supplied JShell reproducer. Add regression coverage for clearing either band, checking both metadata and MapAlgebra before serialization; GeoTIFF output should not silently restore a cleared value in an unsupported mixed NoData state.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
data
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.