This is an automated email from the ASF dual-hosted git repository.
paleolimbot 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 c9b72a01 test(rust/sedona-raster-functions): Add multi-band regression
tests for RS_* raster functions (#747)
c9b72a01 is described below
commit c9b72a012eedd0c02310b570592c1777ba9582fb
Author: James Willis <[email protected]>
AuthorDate: Wed Apr 1 18:51:22 2026 -0700
test(rust/sedona-raster-functions): Add multi-band regression tests for
RS_* raster functions (#747)
---
.../src/rs_band_accessors.rs | 58 ++++++++++++
rust/sedona-raster-functions/src/rs_numbands.rs | 11 +++
rust/sedona-testing/src/rasters.rs | 104 +++++++++++++++++++++
3 files changed, 173 insertions(+)
diff --git a/rust/sedona-raster-functions/src/rs_band_accessors.rs
b/rust/sedona-raster-functions/src/rs_band_accessors.rs
index a2288363..ee1a308e 100644
--- a/rust/sedona-raster-functions/src/rs_band_accessors.rs
+++ b/rust/sedona-raster-functions/src/rs_band_accessors.rs
@@ -431,6 +431,64 @@ mod tests {
assert!(float_array.is_null(0));
}
+ #[test]
+ fn udf_bandpixeltype_multi_band() {
+ let udf: ScalarUDF = rs_bandpixeltype_udf().into();
+ let tester = ScalarUdfTester::new(udf, vec![RASTER,
SedonaType::Arrow(DataType::Int32)]);
+
+ let rasters = sedona_testing::rasters::generate_multi_band_raster();
+
+ // Band 1: UInt8
+ let result = tester
+ .invoke_array_scalar(Arc::new(rasters.clone()), 1_i32)
+ .unwrap();
+ let arr = result.as_any().downcast_ref::<StringArray>().unwrap();
+ assert_eq!(arr.value(0), "UNSIGNED_8BITS");
+
+ // Band 2: UInt16
+ let result = tester
+ .invoke_array_scalar(Arc::new(rasters.clone()), 2_i32)
+ .unwrap();
+ let arr = result.as_any().downcast_ref::<StringArray>().unwrap();
+ assert_eq!(arr.value(0), "UNSIGNED_16BITS");
+
+ // Band 3: Float32
+ let result = tester
+ .invoke_array_scalar(Arc::new(rasters), 3_i32)
+ .unwrap();
+ let arr = result.as_any().downcast_ref::<StringArray>().unwrap();
+ assert_eq!(arr.value(0), "REAL_32BITS");
+ }
+
+ #[test]
+ fn udf_bandnodatavalue_multi_band() {
+ let udf: ScalarUDF = rs_bandnodatavalue_udf().into();
+ let tester = ScalarUdfTester::new(udf, vec![RASTER,
SedonaType::Arrow(DataType::Int32)]);
+
+ let rasters = sedona_testing::rasters::generate_multi_band_raster();
+
+ // Band 1: nodata=255 (UInt8)
+ let result = tester
+ .invoke_array_scalar(Arc::new(rasters.clone()), 1_i32)
+ .unwrap();
+ let arr = result.as_any().downcast_ref::<Float64Array>().unwrap();
+ assert_eq!(arr.value(0), 255.0);
+
+ // Band 2: nodata=0 (UInt16)
+ let result = tester
+ .invoke_array_scalar(Arc::new(rasters.clone()), 2_i32)
+ .unwrap();
+ let arr = result.as_any().downcast_ref::<Float64Array>().unwrap();
+ assert_eq!(arr.value(0), 0.0);
+
+ // Band 3: no nodata (Float32)
+ let result = tester
+ .invoke_array_scalar(Arc::new(rasters), 3_i32)
+ .unwrap();
+ let arr = result.as_any().downcast_ref::<Float64Array>().unwrap();
+ assert!(arr.is_null(0));
+ }
+
#[test]
fn udf_bandnodatavalue_non_existing_band() {
let udf: ScalarUDF = rs_bandnodatavalue_udf().into();
diff --git a/rust/sedona-raster-functions/src/rs_numbands.rs
b/rust/sedona-raster-functions/src/rs_numbands.rs
index d3389f1c..f25c4df4 100644
--- a/rust/sedona-raster-functions/src/rs_numbands.rs
+++ b/rust/sedona-raster-functions/src/rs_numbands.rs
@@ -108,4 +108,15 @@ mod tests {
let result = tester.invoke_scalar(ScalarValue::Null).unwrap();
tester.assert_scalar_result_equals(result, ScalarValue::UInt32(None));
}
+
+ #[test]
+ fn udf_numbands_multi_band() {
+ let udf: ScalarUDF = rs_numbands_udf().into();
+ let tester = ScalarUdfTester::new(udf, vec![RASTER]);
+
+ let rasters = sedona_testing::rasters::generate_multi_band_raster();
+ let expected: Arc<dyn arrow_array::Array> =
Arc::new(UInt32Array::from(vec![Some(3)]));
+ let result = tester.invoke_array(Arc::new(rasters)).unwrap();
+ assert_array_equal(&result, &expected);
+ }
}
diff --git a/rust/sedona-testing/src/rasters.rs
b/rust/sedona-testing/src/rasters.rs
index 2aab413c..d3094047 100644
--- a/rust/sedona-testing/src/rasters.rs
+++ b/rust/sedona-testing/src/rasters.rs
@@ -221,6 +221,77 @@ pub fn raster_from_single_band(
builder.finish().expect("finish")
}
+/// Builds a single raster with 3 bands of different types for testing
multi-band operations.
+/// Band 1: UInt8 (nodata=255), Band 2: UInt16 (nodata=0), Band 3: Float32 (no
nodata).
+/// Each band is 2x2 pixels.
+pub fn generate_multi_band_raster() -> StructArray {
+ let mut builder = RasterBuilder::new(1);
+ let crs = lnglat().unwrap().to_crs_string();
+ let metadata = RasterMetadata {
+ width: 2,
+ height: 2,
+ upperleft_x: 10.0,
+ upperleft_y: 20.0,
+ scale_x: 0.5,
+ scale_y: -0.5,
+ skew_x: 0.0,
+ skew_y: 0.0,
+ };
+ builder.start_raster(&metadata, Some(&crs)).unwrap();
+
+ // Band 1: UInt8, nodata=255
+ builder
+ .start_band(BandMetadata {
+ datatype: BandDataType::UInt8,
+ nodata_value: Some(vec![255u8]),
+ storage_type: StorageType::InDb,
+ outdb_url: None,
+ outdb_band_id: None,
+ })
+ .unwrap();
+ builder
+ .band_data_writer()
+ .append_value([1u8, 2u8, 3u8, 4u8]);
+ builder.finish_band().unwrap();
+
+ // Band 2: UInt16, nodata=0
+ builder
+ .start_band(BandMetadata {
+ datatype: BandDataType::UInt16,
+ nodata_value: Some(vec![0u8, 0u8]),
+ storage_type: StorageType::InDb,
+ outdb_url: None,
+ outdb_band_id: None,
+ })
+ .unwrap();
+ let band2_data: Vec<u8> = [100u16, 200u16, 300u16, 400u16]
+ .iter()
+ .flat_map(|v| v.to_le_bytes())
+ .collect();
+ builder.band_data_writer().append_value(&band2_data);
+ builder.finish_band().unwrap();
+
+ // Band 3: Float32, no nodata
+ builder
+ .start_band(BandMetadata {
+ datatype: BandDataType::Float32,
+ nodata_value: None,
+ storage_type: StorageType::InDb,
+ outdb_url: None,
+ outdb_band_id: None,
+ })
+ .unwrap();
+ let band3_data: Vec<u8> = [1.5f32, 2.5f32, 3.5f32, 4.5f32]
+ .iter()
+ .flat_map(|v| v.to_le_bytes())
+ .collect();
+ builder.band_data_writer().append_value(&band3_data);
+ builder.finish_band().unwrap();
+
+ builder.finish_raster().unwrap();
+ builder.finish().unwrap()
+}
+
/// 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(
@@ -526,6 +597,39 @@ mod tests {
assert_raster_equal(&raster1, &raster2);
}
+ #[test]
+ fn test_generate_multi_band_raster() {
+ let struct_array = generate_multi_band_raster();
+ let raster_array = RasterStructArray::new(&struct_array);
+ assert_eq!(raster_array.len(), 1);
+
+ let raster = raster_array.get(0).unwrap();
+ let metadata = raster.metadata();
+ assert_eq!(metadata.width(), 2);
+ assert_eq!(metadata.height(), 2);
+ assert_eq!(metadata.upper_left_x(), 10.0);
+ assert_eq!(metadata.upper_left_y(), 20.0);
+
+ let bands = raster.bands();
+ assert_eq!(bands.len(), 3);
+
+ // Band 1: UInt8, nodata=255
+ let b1 = bands.band(1).unwrap();
+ assert_eq!(b1.metadata().data_type().unwrap(), BandDataType::UInt8);
+ assert_eq!(b1.metadata().nodata_value(), Some(&[255u8][..]));
+ assert_eq!(b1.data(), &[1u8, 2, 3, 4]);
+
+ // Band 2: UInt16, nodata=0
+ let b2 = bands.band(2).unwrap();
+ assert_eq!(b2.metadata().data_type().unwrap(), BandDataType::UInt16);
+ assert_eq!(b2.metadata().nodata_value(), Some(&[0u8, 0][..]));
+
+ // Band 3: Float32, no nodata
+ let b3 = bands.band(3).unwrap();
+ assert_eq!(b3.metadata().data_type().unwrap(), BandDataType::Float32);
+ assert_eq!(b3.metadata().nodata_value(), None);
+ }
+
#[test]
#[should_panic = "Raster upper left x does not match"]
fn test_raster_different_metadata() {