Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -152,11 +152,14 @@ public static GridCoverage2D setValues(

if (keepNoData) {
noDataValue = RasterBandAccessors.getBandNoDataValue(raster, band);
rasterizedGeom =
RasterConstructors.asRaster(geom, raster, bandDataType, allTouched, value, noDataValue);
} else {
rasterizedGeom = RasterConstructors.asRaster(geom, raster, bandDataType, allTouched, value);
}
// The rasterized geometry is only read as a mask below, where 0 marks pixels outside the
// geometry, so its band must not carry a nodata value (that would fill the mask's background
// with it). Passing an explicit null noDataValue keeps the mask's background at 0; the
// noDataValue-less overloads would instead inherit the reference band's nodata value and fill
// the background with it.
rasterizedGeom =
RasterConstructors.asRaster(geom, raster, bandDataType, allTouched, value, null);

Raster rasterizedGeomData = RasterUtils.getRaster(rasterizedGeom.getRenderedImage());
double colX = RasterAccessors.getUpperLeftX(rasterizedGeom),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -365,9 +365,13 @@ public static GridCoverage2D clip(
writableRaster.setSamples(0, 0, rasterWidth, rasterHeight, 0, array);
}

// rasterize the geometry to iterate over the clipped raster
// rasterize the geometry to iterate over the clipped raster. The mask is read as a
// 0-background mask below (a pixel value of 0 marks "outside the geometry"), so pass an
// explicit null noDataValue to keep that zero background. The noDataValue-less overloads would
// instead inherit the reference band's nodata value and fill the background with it, which the
// mask logic would then misread as covered pixels.
GridCoverage2D maskRaster =
RasterConstructors.asRaster(geometry, raster, bandType, allTouched, 150);
RasterConstructors.asRaster(geometry, raster, bandType, allTouched, 150, null);
Raster maskData = RasterUtils.getRaster(maskRaster.getRenderedImage());
double[] maskMetadata = RasterAccessors.metadata(maskRaster);
int maskWidth = (int) maskMetadata[2], maskHeight = (int) maskMetadata[3];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,8 @@ private static NetcdfFile openNetCdfBytes(byte[] bytes) throws IOException {
* @param pixelType The data type of pixel/cell of resultant raster
* @param allTouched When set to true, rasterizes all pixels touched by geom
* @param value The value of the pixel of the resultant raster
* @param noDataValue The noDataValue of the resultant raster
* @param noDataValue The noDataValue of the resultant raster; pixels not covered by the geometry
* are set to this value
* @param useGeometryExtent The way to generate extent of the resultant raster. Use the extent of
* the geometry to convert if true, else use the extent of the reference raster
* @return Rasterized Geometry
Expand All @@ -130,8 +131,36 @@ public static GridCoverage2D asRaster(
boolean useGeometryExtent)
throws FactoryException {

// Reject a burn value that cannot be stored as a whole number in an integer pixel type (for
// example 265 -> 9 in an unsigned 8-bit band), which would burn a different number than the
// caller asked for. Unlike the noDataValue this is ordinary pixel data, not a sentinel, so a
// fractional value on a float / double band is accepted and rounded to the pixel type (matching
// PostGIS ST_AsRaster and gdal_rasterize) rather than rejected.
RasterUtils.assertBurnValueRepresentable(value, pixelType);

// Reject a noDataValue that cannot be represented in the target pixel type rather than
// silently coercing it: writing an out-of-range or fractional value into an integer sample
// stores a different number than the value recorded as the band's nodata metadata, so the
// background would read back as data. Validating once here keeps the background fill and the
// nodata metadata below using the same value.
if (noDataValue != null) {
noDataValue = RasterUtils.assertNoDataValueRepresentable(noDataValue, pixelType);
}

// If the burn value equals the noDataValue, every covered pixel reads back as nodata, so the
// geometry would vanish from the output. Reject rather than produce an all-nodata raster; this
// also catches the default burn value of 1 colliding with an inherited nodata of 1.0.
if (noDataValue != null && Double.compare(value, noDataValue) == 0) {
Comment thread
james-willis marked this conversation as resolved.
throw new IllegalArgumentException(
String.format(
"RS_AsRaster: burn value (%s) equals the noDataValue, so every covered pixel would "
+ "read as nodata; use a different value or noDataValue",
value));
}

List<Object> objects =
Rasterization.rasterize(geom, raster, pixelType, value, useGeometryExtent, allTouched);
Rasterization.rasterize(
geom, raster, pixelType, value, useGeometryExtent, allTouched, noDataValue);

WritableRaster writableRaster = (WritableRaster) objects.get(0);
GridCoverage2D rasterized = (GridCoverage2D) objects.get(1);
Expand Down Expand Up @@ -178,7 +207,8 @@ public static GridCoverage2D asRaster(

/**
* Returns a raster that is converted from the geometry provided. A convenience function for
* asRaster.
* asRaster. Because this overload takes no noDataValue argument, the noDataValue is inherited
* from the reference raster band (see {@link #inheritReferenceNoDataValue}).
*
* @param geom The geometry to convert
* @param raster The reference raster
Expand All @@ -188,12 +218,14 @@ public static GridCoverage2D asRaster(
*/
public static GridCoverage2D asRaster(Geometry geom, GridCoverage2D raster, String pixelType)
throws FactoryException {
return asRaster(geom, raster, pixelType, false, 1, null);
return asRaster(
geom, raster, pixelType, false, 1, inheritReferenceNoDataValue(raster, pixelType));
}

/**
* Returns a raster that is converted from the geometry provided. A convenience function for
* asRaster.
* asRaster. Because this overload takes no noDataValue argument, the noDataValue is inherited
* from the reference raster band (see {@link #inheritReferenceNoDataValue}).
*
* @param geom The geometry to convert
* @param raster The reference raster
Expand All @@ -205,12 +237,14 @@ public static GridCoverage2D asRaster(Geometry geom, GridCoverage2D raster, Stri
public static GridCoverage2D asRaster(
Geometry geom, GridCoverage2D raster, String pixelType, boolean allTouched)
throws FactoryException {
return asRaster(geom, raster, pixelType, allTouched, 1, null);
return asRaster(
geom, raster, pixelType, allTouched, 1, inheritReferenceNoDataValue(raster, pixelType));
}

/**
* Returns a raster that is converted from the geometry provided. A convenience function for
* asRaster.
* asRaster. Because this overload takes no noDataValue argument, the noDataValue is inherited
* from the reference raster band (see {@link #inheritReferenceNoDataValue}).
*
* @param geom The geometry to convert
* @param raster The reference raster
Expand All @@ -223,7 +257,46 @@ public static GridCoverage2D asRaster(
public static GridCoverage2D asRaster(
Geometry geom, GridCoverage2D raster, String pixelType, boolean allTouched, double value)
throws FactoryException {
return asRaster(geom, raster, pixelType, allTouched, value, null);
return asRaster(
geom, raster, pixelType, allTouched, value, inheritReferenceNoDataValue(raster, pixelType));
}

/**
* Resolves the noDataValue that the overloads without a noDataValue argument inherit from the
* reference raster. Those overloads fill every pixel the geometry does not cover with, and record
* as the output band's nodata metadata, the reference raster's first band nodata value. A
* reference band that carries no nodata value leaves nothing to inherit, so this rejects the call
* rather than silently producing a raster with no nodata value and a zero background; the caller
* then either passes an explicit noDataValue, or passes a null noDataValue through the widest
* overload to opt out of a nodata value entirely.
*
* @param raster the reference raster whose first band nodata value is inherited
* @param pixelType the output pixel type the inherited value must be representable in
* @return the reference band's nodata value
* @throws IllegalArgumentException when the reference band has no nodata value to inherit, or
* when its nodata value is not representable in {@code pixelType}
*/
private static double inheritReferenceNoDataValue(GridCoverage2D raster, String pixelType) {
Double referenceNoDataValue = RasterBandAccessors.getBandNoDataValue(raster, 1);
if (referenceNoDataValue == null) {
throw new IllegalArgumentException(
"RS_AsRaster: noDataValue omitted but reference raster band 1 has no nodata value to "
+ "inherit; pass an explicit noDataValue, or pass NULL for no nodata");
}
// The inherited value must be representable in the output pixel type. The core asRaster would
// reject it too, but with a generic message that neither says the value was inherited nor how
// to override it; rethrow with actionable inherit-specific guidance instead.
try {
RasterUtils.assertNoDataValueRepresentable(referenceNoDataValue, pixelType);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException(
String.format(
"RS_AsRaster: the reference raster band 1 nodata value (%s) is not representable in "
+ "the output pixel type '%s'; pass an explicit noDataValue representable in '%s' "
+ "to override it",
referenceNoDataValue, pixelType, pixelType));
}
return referenceNoDataValue;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,18 @@ protected static List<Object> rasterize(
boolean useGeometryExtent,
boolean allTouched)
throws FactoryException {
return rasterize(geom, raster, pixelType, value, useGeometryExtent, allTouched, null);
}

protected static List<Object> rasterize(
Geometry geom,
GridCoverage2D raster,
String pixelType,
double value,
boolean useGeometryExtent,
boolean allTouched,
Double backgroundValue)
throws FactoryException {

// Validate the input geometry and raster metadata
double[] metadata = RasterAccessors.metadata(raster);
Expand All @@ -61,6 +73,8 @@ protected static List<Object> rasterize(
RasterizationParams params =
calculateRasterizationParams(raster, useGeometryExtent, metadata, geomExtent, pixelType);

fillBackground(params.writableRaster, backgroundValue);

rasterizeGeometry(raster, metadata, geom, params, geomExtent, value, allTouched);

// Create a GridCoverage2D for the rasterized result
Expand All @@ -86,6 +100,31 @@ protected static List<Object> rasterize(
return objects;
}

/**
* Fills every pixel of the freshly-allocated raster with the background value, so pixels never
* covered by the geometry read back as that value instead of the allocation default of 0.
*
* <p>A null background, or a background of positive zero, keeps the zero-initialized allocation
* as-is. Negative zero, however, must still be filled: it is a legitimate noDataValue that is
* distinct from the allocation's {@code +0.0}. RS_Count (and other nodata-aware readers) compare
* pixels against the band's nodata metadata with {@link Double#compare}, which orders {@code
* -0.0} before {@code +0.0}, so a {@code -0.0} nodata left unfilled would leave the untouched
* pixels as {@code +0.0} and be miscounted as data. Filling makes the pixel value and the nodata
* metadata agree on the sign of zero.
*/
private static void fillBackground(WritableRaster writableRaster, Double backgroundValue) {
// Double.compare(x, 0.0) == 0 is true only for +0.0; -0.0 compares as -1 and is filled.
if (backgroundValue == null || Double.compare(backgroundValue, 0.0) == 0) {
return;
}
int width = writableRaster.getWidth();
double[] row = new double[width];
Arrays.fill(row, backgroundValue);
Comment thread
james-willis marked this conversation as resolved.
for (int y = 0; y < writableRaster.getHeight(); y++) {
writableRaster.setSamples(0, y, width, 1, 0, row);
}
}

private static void rasterizeGeometry(
GridCoverage2D raster,
double[] metadata,
Expand Down
Loading
Loading