Skip to content

feat(rust/sedona-raster-functions): add RS_MinConvexHull - #1351

Merged
james-willis merged 2 commits into
apache:mainfrom
james-willis:james/rs-minconvexhull
Sep 25, 2026
Merged

james-willis merged 2 commits into
apache:mainfrom
james-willis:james/rs-minconvexhull

Conversation

@james-willis

@james-willis james-willis commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Adds RS_MinConvexHull(raster[, band]), Sedona Spark's footprint of a raster's data pixels. It returns the polygon over the smallest pixel-aligned rectangle of the grid that holds every non-nodata pixel, in world coordinates. In effect it is RS_ConvexHull with the nodata margin trimmed off. Without a band, a cell holds data if any band holds data there. The result is NULL when no pixel holds data, and a band with no nodata value covers the whole grid.

The output matches RS_ConvexHull: a planar geometry with the raster's CRS as an item-level CRS, and the same ring order (upper-left, upper-right, lower-right, lower-left), written through footprint::write_footprint_wkb. On a skewed grid the corners follow the grid axes.

Each band is scanned inward from its edges and the scan stops at the first data pixel from each side: rows down from the top, rows up from the bottom, then columns in from the left and right over the rows in between. A band that is mostly data, the common case, costs a handful of pixels instead of a full scan, and a mostly-nodata band costs at most about two full scans. Without a band argument, bands stop being scanned once the whole grid is covered. The bottom and side scans read the band through reversed and transposed views of the same buffer. A unit test checks the result against a full scan for every data mask of a 4x3 grid.

The semantics follow Sedona's main branch, not the 1.9.1 release. apache/sedona#3366 made an all-nodata raster return NULL and fixed NaN nodata after 1.9.1 shipped. In 1.9.1 an all-nodata raster gives a polygon built from Integer.MAX_VALUE sentinels, and a NaN nodata value matches no pixel, so both spark-parity cases are xfails until the suite's Sedona pin moves past it.

Shared pixel scan

This adds pixel_scan.rs, a crate-private helper that walks every pixel of a 2-D band through its NdBuffer strides, plus NodataMatcher, which compares integer pixels to nodata by bytes and float pixels numerically. The RS_BandIsNoData (#1350) and RS_SummaryStats (#1352) PRs carry the identical file, so whichever merges second rebases without conflicts in it.

Tests

  • Rust unit tests: margin trimming, the edge scan against a full scan, a band without nodata, all-nodata and NaN nodata, the band union, band errors (including a negative band), north-up and skewed world placement, and NULL propagation.
  • python/sedonadb/tests/functions/test_rs_minconvexhull.py: north-up and skewed GeoTIFFs read through RS_FromPath, checked against rasterio's affine applied to numpy's data-pixel bounds (exact, dyadic coefficients).
  • integration/spark-parity/test_rs_minconvexhull.py: anchored parity against Sedona Spark 1.9.1 for each band, all bands, skew, CRS, a band without nodata, a NULL band and an out-of-range band (8 pass, 2 xfail as described above). The skewed case uses exactly representable coefficients: SedonaDB and Spark order the affine additions differently, which can differ in the last bit for ordinary decimal skews (the same holds for RS_ConvexHull).

Find the data bounds by scanning in from each edge and stopping at the first
data pixel, and skip bands once the grid is covered, so a mostly-data band
costs a handful of pixels instead of a full scan (checked against a full scan
for every mask of a 4x3 grid). Place north-up fixtures with bbox=, add a
NaN-nodata xfail and a no-nodata parity case, anchor the NULL cases, and show
trimming in the docs example. The shared pixel scan rejects negative shapes
and lists its integer types explicitly.

@jiayuasu jiayuasu left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@james-willis
james-willis marked this pull request as ready for review September 25, 2026 18:42
@james-willis
james-willis merged commit 998a081 into apache:main Sep 25, 2026
17 checks passed
@james-willis
james-willis deleted the james/rs-minconvexhull branch October 1, 2026 20:02
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.

2 participants