This is an automated email from the ASF dual-hosted git repository.

kontinuation pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/sedona-db.git


The following commit(s) were added to refs/heads/main by this push:
     new 0bf4ad70 fix(raster): RS_Envelope returns axis-aligned bounding box 
for skewed rasters (#594)
0bf4ad70 is described below

commit 0bf4ad7005e157c96d616a5eb3e544ad698da133
Author: Kristin Cowalcijk <[email protected]>
AuthorDate: Wed Feb 11 20:39:13 2026 +0800

    fix(raster): RS_Envelope returns axis-aligned bounding box for skewed 
rasters (#594)
    
    ## Summary
    
    - **Fix `RS_Envelope`** to return the axis-aligned bounding box (AABB) 
instead of the convex hull for skewed/rotated rasters, matching PostGIS 
`ST_Envelope` semantics.
    - **Fix `generate_test_rasters`** to give raster `i=0` non-zero scales via 
`i.max(1)`, so it has an invertible geotransform.
    - **Add `build_noninvertible_raster()`** helper to `sedona-testing` for 
tests that need a raster with zero scales/skews.
    - Update cascading test expectations in `rs_envelope`, `rs_geotransform`, 
and `rs_rastercoordinate`.
---
 rust/sedona-raster-functions/src/rs_envelope.rs    | 54 +++++++++++++---------
 .../sedona-raster-functions/src/rs_geotransform.rs |  4 +-
 .../src/rs_rastercoordinate.rs                     |  7 ++-
 rust/sedona-testing/src/rasters.rs                 | 41 ++++++++++++++--
 4 files changed, 73 insertions(+), 33 deletions(-)

diff --git a/rust/sedona-raster-functions/src/rs_envelope.rs 
b/rust/sedona-raster-functions/src/rs_envelope.rs
index 8ecb60cb..7aa1d193 100644
--- a/rust/sedona-raster-functions/src/rs_envelope.rs
+++ b/rust/sedona-raster-functions/src/rs_envelope.rs
@@ -94,25 +94,37 @@ impl SedonaScalarKernel for RsEnvelope {
     }
 }
 
-/// Create WKB for a polygon for the raster
+/// Create WKB for the axis-aligned bounding box (envelope) of the raster.
+///
+/// This computes the four corners of the raster in world coordinates, then
+/// derives the min/max X and Y to produce an axis-aligned bounding box.
+/// For skewed/rotated rasters, this differs from the convex hull.
 fn create_envelope_wkb(raster: &dyn RasterRef, out: &mut impl std::io::Write) 
-> Result<()> {
-    // Compute the four corners of the raster in world coordinates.
-    // Due to skew/rotation in the affine transformation, each corner must be
-    // computed individually.
-
     let width = raster.metadata().width() as i64;
     let height = raster.metadata().height() as i64;
 
-    // Compute the four corners in pixel coordinates:
-    // Upper-left (0, 0), Upper-right (width, 0), Lower-right (width, height), 
Lower-left (0, height)
+    // Compute the four corners in world coordinates
     let (ulx, uly) = to_world_coordinate(raster, 0, 0);
     let (urx, ury) = to_world_coordinate(raster, width, 0);
     let (lrx, lry) = to_world_coordinate(raster, width, height);
     let (llx, lly) = to_world_coordinate(raster, 0, height);
 
+    // Compute the axis-aligned bounding box
+    let min_x = ulx.min(urx).min(lrx).min(llx);
+    let max_x = ulx.max(urx).max(lrx).max(llx);
+    let min_y = uly.min(ury).min(lry).min(lly);
+    let max_y = uly.max(ury).max(lry).max(lly);
+
     write_wkb_polygon(
         out,
-        [(ulx, uly), (urx, ury), (lrx, lry), (llx, lly), (ulx, 
uly)].into_iter(),
+        [
+            (min_x, min_y),
+            (max_x, min_y),
+            (max_x, max_y),
+            (min_x, max_y),
+            (min_x, min_y),
+        ]
+        .into_iter(),
     )
     .map_err(|e| DataFusionError::External(e.into()))?;
 
@@ -143,26 +155,22 @@ mod tests {
         let udf = rs_envelope_udf();
         let tester = ScalarUdfTester::new(udf.into(), vec![RASTER]);
 
-        let rasters = generate_test_rasters(3, Some(0)).unwrap();
+        // i=0: no skew, i=1: null, i=2: skewed (scale=0.2,-0.4, 
skew=0.06,0.08)
+        let rasters = generate_test_rasters(3, Some(1)).unwrap();
 
-        // Corners computed using gdal:
-        // Raster 1:
-        // Envelope corner coordinates (X, Y):
-        // (2.00000000, 3.00000000)
-        // (2.20000000, 3.08000000)
-        // (2.29000000, 2.48000000)
-        // (2.09000000, 2.40000000)
+        // Reference values verified against PostGIS ST_Envelope:
+        //
+        // Raster 0 (i=0): width=1, height=2, ul=(1,2), scale=(0.1,-0.2), 
skew=(0,0)
+        //   No skew, so envelope = convex hull
         //
-        // Raster 2:
-        // (3.00000000, 4.00000000)
-        // (3.60000000, 4.24000000)
-        // (3.84000000, 2.64000000)
-        // (3.24000000, 2.40000000)
+        // Raster 2 (i=2): width=3, height=4, ul=(3,4), scale=(0.2,-0.4), 
skew=(0.06,0.08)
+        //   Corners: (3,4), (3.6,4.24), (3.84,2.64), (3.24,2.4)
+        //   AABB: x=[3, 3.84], y=[2.4, 4.24]
         let expected = &create_array(
             &[
+                Some("POLYGON ((1 1.6, 1.1 1.6, 1.1 2, 1 2, 1 1.6))"),
                 None,
-                Some("POLYGON ((2.0 3.0, 2.2 3.08, 2.29 2.48, 2.09 2.4, 2.0 
3.0))"),
-                Some("POLYGON ((3.0 4.0, 3.6 4.24, 3.84 2.64, 3.24 2.4, 3.0 
4.0))"),
+                Some("POLYGON ((3 2.4, 3.84 2.4, 3.84 4.24, 3 4.24, 3 2.4))"),
             ],
             &WKB_GEOMETRY,
         );
diff --git a/rust/sedona-raster-functions/src/rs_geotransform.rs 
b/rust/sedona-raster-functions/src/rs_geotransform.rs
index 0d98ee2b..a94e9517 100644
--- a/rust/sedona-raster-functions/src/rs_geotransform.rs
+++ b/rust/sedona-raster-functions/src/rs_geotransform.rs
@@ -343,8 +343,8 @@ mod tests {
         let rasters = generate_test_rasters(3, Some(1)).unwrap();
         let expected_values = match g {
             GeoTransformParam::Rotation => vec![Some(-0.0), None, 
Some(-0.29145679447786704)],
-            GeoTransformParam::ScaleX => vec![Some(0.0), None, Some(0.2)],
-            GeoTransformParam::ScaleY => vec![Some(-0.0), None, Some(-0.4)],
+            GeoTransformParam::ScaleX => vec![Some(0.1), None, Some(0.2)],
+            GeoTransformParam::ScaleY => vec![Some(-0.2), None, Some(-0.4)],
             GeoTransformParam::SkewX => vec![Some(0.0), None, Some(0.06)],
             GeoTransformParam::SkewY => vec![Some(0.0), None, Some(0.08)],
             GeoTransformParam::UpperLeftX => vec![Some(1.0), None, Some(3.0)],
diff --git a/rust/sedona-raster-functions/src/rs_rastercoordinate.rs 
b/rust/sedona-raster-functions/src/rs_rastercoordinate.rs
index bdfcf48f..b7e74356 100644
--- a/rust/sedona-raster-functions/src/rs_rastercoordinate.rs
+++ b/rust/sedona-raster-functions/src/rs_rastercoordinate.rs
@@ -240,7 +240,7 @@ mod tests {
     use sedona_schema::datatypes::{RASTER, WKB_GEOMETRY};
     use sedona_testing::compare::assert_array_equal;
     use sedona_testing::create::create_array;
-    use sedona_testing::rasters::generate_test_rasters;
+    use sedona_testing::rasters::{build_noninvertible_raster, 
generate_test_rasters};
     use sedona_testing::testers::ScalarUdfTester;
 
     #[test]
@@ -290,8 +290,7 @@ mod tests {
         assert_array_equal(&result, &expected);
 
         // Test that we correctly handle non-invertible geotransforms
-        // using non-invertible raster 0
-        let noninvertible_rasters = generate_test_rasters(2, None).unwrap();
+        let noninvertible_rasters = build_noninvertible_raster();
         let result_err =
             tester.invoke_array_scalar_scalar(Arc::new(noninvertible_rasters), 
2.0_f64, 3.0_f64);
         assert!(result_err.is_err());
@@ -323,7 +322,7 @@ mod tests {
         assert_array_equal(&result, expected);
 
         // Test that we correctly handle non-invertible geotransforms
-        let noninvertible_rasters = generate_test_rasters(2, None).unwrap();
+        let noninvertible_rasters = build_noninvertible_raster();
         let result_err =
             tester.invoke_array_scalar_scalar(Arc::new(noninvertible_rasters), 
2.0_f64, 3.0_f64);
         assert!(result_err.is_err());
diff --git a/rust/sedona-testing/src/rasters.rs 
b/rust/sedona-testing/src/rasters.rs
index 777ce1ae..16c7d7e1 100644
--- a/rust/sedona-testing/src/rasters.rs
+++ b/rust/sedona-testing/src/rasters.rs
@@ -44,8 +44,8 @@ pub fn generate_test_rasters(
             height: i as u64 + 2,
             upperleft_x: i as f64 + 1.0,
             upperleft_y: i as f64 + 2.0,
-            scale_x: i as f64 * 0.1,
-            scale_y: i as f64 * -0.2,
+            scale_x: i.max(1) as f64 * 0.1,
+            scale_y: i.max(1) as f64 * -0.2,
             skew_x: i as f64 * 0.03,
             skew_y: i as f64 * 0.04,
         };
@@ -149,6 +149,39 @@ pub fn generate_tiled_rasters(
     Ok(raster_builder.finish()?)
 }
 
+/// Builds a 1x1 single-band raster with a non-invertible geotransform (zero 
scales and skews).
+/// Useful for testing error handling of inverse affine transforms.
+pub fn build_noninvertible_raster() -> StructArray {
+    let mut builder = RasterBuilder::new(1);
+    let metadata = RasterMetadata {
+        width: 1,
+        height: 1,
+        upperleft_x: 0.0,
+        upperleft_y: 0.0,
+        scale_x: 0.0,
+        scale_y: 0.0,
+        skew_x: 0.0,
+        skew_y: 0.0,
+    };
+    let crs = lnglat().unwrap().to_crs_string();
+    builder
+        .start_raster(&metadata, Some(&crs))
+        .expect("start raster");
+    builder
+        .start_band(BandMetadata {
+            datatype: BandDataType::UInt8,
+            nodata_value: None,
+            storage_type: StorageType::InDb,
+            outdb_url: None,
+            outdb_band_id: None,
+        })
+        .expect("start band");
+    builder.band_data_writer().append_value([0u8]);
+    builder.finish_band().expect("finish band");
+    builder.finish_raster().expect("finish raster");
+    builder.finish().expect("finish")
+}
+
 /// Determine if this tile contains a corner of the overall grid and return 
its position
 /// Returns Some(position) if this tile contains a corner, None otherwise
 fn get_corner_position(
@@ -410,8 +443,8 @@ mod tests {
             assert_eq!(metadata.height(), i as u64 + 2);
             assert_eq!(metadata.upper_left_x(), i as f64 + 1.0);
             assert_eq!(metadata.upper_left_y(), i as f64 + 2.0);
-            assert_eq!(metadata.scale_x(), (i as f64) * 0.1);
-            assert_eq!(metadata.scale_y(), (i as f64) * -0.2);
+            assert_eq!(metadata.scale_x(), (i.max(1) as f64) * 0.1);
+            assert_eq!(metadata.scale_y(), (i.max(1) as f64) * -0.2);
             assert_eq!(metadata.skew_x(), (i as f64) * 0.03);
             assert_eq!(metadata.skew_y(), (i as f64) * 0.04);
 

Reply via email to