Expected behavior
Rasterizing any geometry that intersects the raster succeeds. This path is shared by RS_AsRaster, RS_Clip, RS_SetValues with a geometry, and RS_ZonalStats / RS_ZonalStatsAll. A point on the raster edge or on a grid line burns the pixel(s) it touches. A polygon that touches the raster only at an edge or a corner burns the touched pixel with allTouched = true and nothing with allTouched = false.
Actual behavior
On master (53e4e7b) these cases throw:
| Case |
Result |
| Point on the bottom-right corner of a 256x256 tile with 1/3600 degree pixels |
ArrayIndexOutOfBoundsException: Index 65536 out of bounds for length 65536 (full extent); Index 1 out of bounds for length 1 (cropped to the geometry) |
| Point on the row 0 / row 1 grid line |
ArrayIndexOutOfBoundsException: Index -246 out of bounds for length 65536 (full extent); Index -1 out of bounds for length 1 (cropped) |
| Polygon that touches the tile only at its bottom-right corner |
ClassCastException: class org.locationtech.jts.geom.Point cannot be cast to class org.locationtech.jts.geom.Polygon with both allTouched values, and from RS_ZonalStatsAll |
| Polygon whose clip against the raster is a GeometryCollection (polygons plus lines) |
ClassCastException: ... GeometryCollection cannot be cast to ... Polygon |
POLYGON ((1 1, 2 1, 2 0.5, 1 1)) on a 2x2 unit raster, allTouched=false |
ArrayIndexOutOfBoundsException: Index 4 out of bounds for length 4 |
Causes, all in Rasterization.java:
rasterizePoint walks the extent by repeatedly adding scaleX / scaleY to a world coordinate and counts the pixel index separately. 1/3600 has no exact double representation, so the running coordinate can fall a fraction of an ULP short of the extent's far edge and take one more step. JAI's banded raster setSample does no bounds check, so the write lands outside the data buffer. The same drift moves the tested cell edges a few ULPs off the grid lines.
rasterizePolygon casts the clipped geometry to Polygon. A polygon that only grazes the raster clips to a point, a line or a GeometryCollection.
fillPolygon writes an unpaired scanline intercept from a vertical edge on the extent maximum one column past the grid.
This came up with RS_ZonalStatsAll(rast, zone, 1, true) over Copernicus GLO-30 DEM tiles (256x256, 1/3600 degree). A zone that only touches a tile at its edge crashes the query. That happened for 750 of about 243k zones, so small samples do not reproduce it.
A randomized probe (seed 444) placed 400 points exactly on a horizontal grid line, 400 on a vertical one and 400 on grid corners of 1/3600 degree 64x64 tiles, every fifth tile bottom-up, with allTouched=true. On master: 2 crashes and 29 wrong-row burns with full-extent output, and 65 crashes with output cropped to the geometry.
Steps to reproduce
double p = 1.0 / 3600.0;
GeometryFactory gf = new GeometryFactory();
// 1. point on the tile's bottom-right corner
GridCoverage2D tile =
RasterConstructors.makeEmptyRaster(1, "D", 256, 256, -180.0, 60.0, p, -p, 0, 0, 4326);
Geometry corner = gf.createPoint(new Coordinate(-180.0 + 255 * p + p, 60.0 - 255 * p - p));
RasterConstructors.asRasterWithRasterExtent(corner, tile, "D", true, 150, null);
// ArrayIndexOutOfBoundsException: Index 65536 out of bounds for length 65536
// 2. point on the row 0 / row 1 grid line
double uly = 60.0 - 2 * p;
GridCoverage2D tile2 =
RasterConstructors.makeEmptyRaster(1, "D", 256, 256, -180.0, uly, p, -p, 0, 0, 4326);
Geometry onRow = gf.createPoint(new Coordinate(-180.0 + 10.5 * p, uly - p));
RasterConstructors.asRasterWithRasterExtent(onRow, tile2, "D", true, 150, null);
// ArrayIndexOutOfBoundsException: Index -246 out of bounds for length 65536
// 3. zone touching the tile only at its bottom-right corner
double ulx = -180.0 + 822 * p, uly3 = 60.0 - 546 * p;
GridCoverage2D tile3 =
RasterConstructors.makeEmptyRaster(1, "D", 256, 256, ulx, uly3, p, -p, 0, 0, 4326);
double right = ulx + 256 * p, bottom = uly3 - 256 * p;
Geometry zone = gf.createPolygon(new Coordinate[] {
new Coordinate(right, bottom), new Coordinate(right + 1, bottom),
new Coordinate(right + 1, bottom - 1), new Coordinate(right, bottom - 1),
new Coordinate(right, bottom)});
RasterBandAccessors.getZonalStatsAll(tile3, zone, 1, true);
// ClassCastException: Point cannot be cast to Polygon
// 4. unpaired scanline intercept past the grid
GridCoverage2D small = RasterConstructors.makeEmptyRaster(1, "D", 2, 2, 0, 2, 1, -1, 0, 0, 0);
Geometry tri = new WKTReader().read("POLYGON ((1 1, 2 1, 2 0.5, 1 1))");
RasterConstructors.asRasterWithRasterExtent(tri, small, "D", false, 150, null);
// ArrayIndexOutOfBoundsException: Index 4 out of bounds for length 4
Expected: 1 burns (255,255), 2 burns (10,0) and (10,1), 3 returns count 1 with allTouched=true and 0 with allTouched=false, and 4 burns nothing.
Proposed fix: walk rasterizePoint by integer column and row, clamped to the grid, and build each cell envelope from its index on the reference raster's grid. Delegate non-polygonal clips to rasterizeGeometry, keeping only their polygonal parts when allTouched is false. In fillPolygon, clamp fill ranges to the grid and discard out-of-grid unpaired intercepts. With that change case 2 no longer crashes but burns only (10,0). The missing (10,1) is a separate allTouched extent bug, filed separately.
Sedona version
master (53e4e7b, 2.0.0-SNAPSHOT)
API type
Java (reached from SQL through RS_AsRaster, RS_Clip, RS_SetValues and RS_ZonalStats / RS_ZonalStatsAll)
JRE version
11
Environment
Local unit tests (macOS)
Expected behavior
Rasterizing any geometry that intersects the raster succeeds. This path is shared by RS_AsRaster, RS_Clip, RS_SetValues with a geometry, and RS_ZonalStats / RS_ZonalStatsAll. A point on the raster edge or on a grid line burns the pixel(s) it touches. A polygon that touches the raster only at an edge or a corner burns the touched pixel with
allTouched = trueand nothing withallTouched = false.Actual behavior
On master (53e4e7b) these cases throw:
ArrayIndexOutOfBoundsException: Index 65536 out of bounds for length 65536(full extent);Index 1 out of bounds for length 1(cropped to the geometry)ArrayIndexOutOfBoundsException: Index -246 out of bounds for length 65536(full extent);Index -1 out of bounds for length 1(cropped)ClassCastException: class org.locationtech.jts.geom.Point cannot be cast to class org.locationtech.jts.geom.Polygonwith both allTouched values, and fromRS_ZonalStatsAllClassCastException: ... GeometryCollection cannot be cast to ... PolygonPOLYGON ((1 1, 2 1, 2 0.5, 1 1))on a 2x2 unit raster, allTouched=falseArrayIndexOutOfBoundsException: Index 4 out of bounds for length 4Causes, all in
Rasterization.java:rasterizePointwalks the extent by repeatedly addingscaleX/scaleYto a world coordinate and counts the pixel index separately. 1/3600 has no exact double representation, so the running coordinate can fall a fraction of an ULP short of the extent's far edge and take one more step. JAI's banded rastersetSampledoes no bounds check, so the write lands outside the data buffer. The same drift moves the tested cell edges a few ULPs off the grid lines.rasterizePolygoncasts the clipped geometry toPolygon. A polygon that only grazes the raster clips to a point, a line or a GeometryCollection.fillPolygonwrites an unpaired scanline intercept from a vertical edge on the extent maximum one column past the grid.This came up with
RS_ZonalStatsAll(rast, zone, 1, true)over Copernicus GLO-30 DEM tiles (256x256, 1/3600 degree). A zone that only touches a tile at its edge crashes the query. That happened for 750 of about 243k zones, so small samples do not reproduce it.A randomized probe (seed 444) placed 400 points exactly on a horizontal grid line, 400 on a vertical one and 400 on grid corners of 1/3600 degree 64x64 tiles, every fifth tile bottom-up, with allTouched=true. On master: 2 crashes and 29 wrong-row burns with full-extent output, and 65 crashes with output cropped to the geometry.
Steps to reproduce
Expected: 1 burns
(255,255), 2 burns(10,0)and(10,1), 3 returns count 1 with allTouched=true and 0 with allTouched=false, and 4 burns nothing.Proposed fix: walk
rasterizePointby integer column and row, clamped to the grid, and build each cell envelope from its index on the reference raster's grid. Delegate non-polygonal clips torasterizeGeometry, keeping only their polygonal parts when allTouched is false. InfillPolygon, clamp fill ranges to the grid and discard out-of-grid unpaired intercepts. With that change case 2 no longer crashes but burns only(10,0). The missing(10,1)is a separateallTouchedextent bug, filed separately.Sedona version
master (53e4e7b, 2.0.0-SNAPSHOT)
API type
Java (reached from SQL through RS_AsRaster, RS_Clip, RS_SetValues and RS_ZonalStats / RS_ZonalStatsAll)
JRE version
11
Environment
Local unit tests (macOS)