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 a95d9a48 fix(sedona-raster-functions): Handle array inputs in 
coordinate conversion functions (#532)
a95d9a48 is described below

commit a95d9a488aacf17f794065bb0409cf456ca24603
Author: Kristin Cowalcijk <[email protected]>
AuthorDate: Thu Jan 22 15:32:05 2026 +0800

    fix(sedona-raster-functions): Handle array inputs in coordinate conversion 
functions (#532)
    
    Previously, RS_RasterToWorldCoord and RS_WorldToRasterCoord only handled 
scalar inputs and would error with 'Expected scalar ... argument' when array 
inputs were passed.
    
    This commit fixes both functions to properly handle array inputs by:
    - Using into_array() to expand scalar inputs to arrays
    - Processing all values in a loop with proper null handling
    
    Added test cases for array input handling.
---
 .../src/rs_rastercoordinate.rs                     | 102 ++++++++++++++-------
 .../src/rs_worldcoordinate.rs                      | 100 +++++++++++++++-----
 2 files changed, 145 insertions(+), 57 deletions(-)

diff --git a/rust/sedona-raster-functions/src/rs_rastercoordinate.rs 
b/rust/sedona-raster-functions/src/rs_rastercoordinate.rs
index 5b00a3e0..d3bb59d2 100644
--- a/rust/sedona-raster-functions/src/rs_rastercoordinate.rs
+++ b/rust/sedona-raster-functions/src/rs_rastercoordinate.rs
@@ -19,8 +19,8 @@ use std::{sync::Arc, vec};
 use crate::executor::RasterExecutor;
 use arrow_array::builder::{BinaryBuilder, Int64Builder};
 use arrow_schema::DataType;
-use datafusion_common::ScalarValue;
-use datafusion_common::{error::Result, exec_err};
+use datafusion_common::cast::as_float64_array;
+use datafusion_common::error::Result;
 use datafusion_expr::{
     scalar_doc_sections::DOC_SECTION_OTHER, ColumnarValue, Documentation, 
Volatility,
 };
@@ -139,17 +139,29 @@ impl SedonaScalarKernel for RsCoordinateMapper {
         let executor = RasterExecutor::new(arg_types, args);
         let mut builder = 
Int64Builder::with_capacity(executor.num_iterations());
 
-        let coord_opt = extract_scalar_coord(&args[1], &args[2])?;
+        // Expand world x and y coordinate parameters to arrays and cast to 
Float64
+        let world_x_array = args[1].clone().cast_to(&DataType::Float64, None)?;
+        let world_x_array = 
world_x_array.into_array(executor.num_iterations())?;
+        let world_x_array = as_float64_array(&world_x_array)?;
+        let world_y_array = args[2].clone().cast_to(&DataType::Float64, None)?;
+        let world_y_array = 
world_y_array.into_array(executor.num_iterations())?;
+        let world_y_array = as_float64_array(&world_y_array)?;
+        let mut world_x_iter = world_x_array.iter();
+        let mut world_y_iter = world_y_array.iter();
+
         executor.execute_raster_void(|_i, raster_opt| {
-            match (raster_opt, coord_opt) {
-                (Some(raster), (Some(x), Some(y))) => {
+            let x_opt = world_x_iter.next().unwrap();
+            let y_opt = world_y_iter.next().unwrap();
+
+            match (raster_opt, x_opt, y_opt) {
+                (Some(raster), Some(x), Some(y)) => {
                     let (raster_x, raster_y) = to_raster_coordinate(&raster, 
x, y)?;
                     match self.coord {
                         Coord::X => builder.append_value(raster_x),
                         Coord::Y => builder.append_value(raster_y),
                     };
                 }
-                (_, _) => builder.append_null(),
+                (_, _, _) => builder.append_null(),
             }
             Ok(())
         })?;
@@ -188,16 +200,28 @@ impl SedonaScalarKernel for RsCoordinatePoint {
             item.len() * executor.num_iterations(),
         );
 
-        let coord_opt = extract_scalar_coord(&args[1], &args[2])?;
+        // Expand world x and y coordinate parameters to arrays and cast to 
Float64
+        let world_x_array = args[1].clone().cast_to(&DataType::Float64, None)?;
+        let world_x_array = 
world_x_array.into_array(executor.num_iterations())?;
+        let world_x_array = as_float64_array(&world_x_array)?;
+        let world_y_array = args[2].clone().cast_to(&DataType::Float64, None)?;
+        let world_y_array = 
world_y_array.into_array(executor.num_iterations())?;
+        let world_y_array = as_float64_array(&world_y_array)?;
+        let mut world_x_iter = world_x_array.iter();
+        let mut world_y_iter = world_y_array.iter();
+
         executor.execute_raster_void(|_i, raster_opt| {
-            match (raster_opt, coord_opt) {
-                (Some(raster), (Some(world_x), Some(world_y))) => {
+            let x_opt = world_x_iter.next().unwrap();
+            let y_opt = world_y_iter.next().unwrap();
+
+            match (raster_opt, x_opt, y_opt) {
+                (Some(raster), Some(world_x), Some(world_y)) => {
                     let (raster_x, raster_y) = to_raster_coordinate(&raster, 
world_x, world_y)?;
                     item[5..13].copy_from_slice(&(raster_x as 
f64).to_le_bytes());
                     item[13..21].copy_from_slice(&(raster_y as 
f64).to_le_bytes());
                     builder.append_value(item);
                 }
-                (_, _) => builder.append_null(),
+                (_, _, _) => builder.append_null(),
             }
             Ok(())
         })?;
@@ -206,28 +230,6 @@ impl SedonaScalarKernel for RsCoordinatePoint {
     }
 }
 
-fn extract_float_scalar(arg: &ColumnarValue) -> Result<Option<f64>> {
-    match arg {
-        ColumnarValue::Scalar(scalar) => {
-            let f64_val = scalar.cast_to(&DataType::Float64)?;
-            match f64_val {
-                ScalarValue::Float64(Some(v)) => Ok(Some(v)),
-                _ => Ok(None),
-            }
-        }
-        _ => exec_err!("Expected scalar float argument for coordinate"),
-    }
-}
-
-fn extract_scalar_coord(
-    x_arg: &ColumnarValue,
-    y_arg: &ColumnarValue,
-) -> Result<(Option<f64>, Option<f64>)> {
-    let x_opt = extract_float_scalar(x_arg)?;
-    let y_opt = extract_float_scalar(y_arg)?;
-    Ok((x_opt, y_opt))
-}
-
 #[cfg(test)]
 mod tests {
     use super::*;
@@ -330,4 +332,40 @@ mod tests {
             .to_string()
             .contains("determinant is zero"));
     }
+
+    #[rstest]
+    fn udf_invoke_xy_with_array_coords(#[values(Coord::Y, Coord::X)] coord: 
Coord) {
+        let udf = match coord {
+            Coord::X => rs_worldtorastercoordx_udf(),
+            Coord::Y => rs_worldtorastercoordy_udf(),
+        };
+        let tester = ScalarUdfTester::new(
+            udf.into(),
+            vec![
+                RASTER,
+                SedonaType::Arrow(DataType::Float64),
+                SedonaType::Arrow(DataType::Float64),
+            ],
+        );
+
+        // Use only raster 1 (invertible) with different world coordinates
+        // Raster 1: upper_left=(2,3), scale_x=0.1, scale_y=-0.2, skew_x=0.03, 
skew_y=0.04
+        // For world coords (2,3) -> raster (0,0) (the upper left corner)
+        // For world coords (2.1, 2.8) -> need to solve the inverse
+        let rasters = generate_test_rasters(2, Some(0)).unwrap();
+        let world_x = Arc::new(arrow_array::Float64Array::from(vec![2.0, 
2.0]));
+        let world_y = Arc::new(arrow_array::Float64Array::from(vec![3.0, 
3.0]));
+
+        let expected_values = match coord {
+            Coord::X => vec![None, Some(0_i64)],
+            Coord::Y => vec![None, Some(0_i64)],
+        };
+        let expected: Arc<dyn arrow_array::Array> =
+            Arc::new(arrow_array::Int64Array::from(expected_values));
+
+        let result = tester
+            .invoke_arrays(vec![Arc::new(rasters), world_x, world_y])
+            .unwrap();
+        assert_array_equal(&result, &expected);
+    }
 }
diff --git a/rust/sedona-raster-functions/src/rs_worldcoordinate.rs 
b/rust/sedona-raster-functions/src/rs_worldcoordinate.rs
index b342a403..4bd264c6 100644
--- a/rust/sedona-raster-functions/src/rs_worldcoordinate.rs
+++ b/rust/sedona-raster-functions/src/rs_worldcoordinate.rs
@@ -19,7 +19,8 @@ use std::{sync::Arc, vec};
 use crate::executor::RasterExecutor;
 use arrow_array::builder::{BinaryBuilder, Float64Builder};
 use arrow_schema::DataType;
-use datafusion_common::{error::Result, exec_err, ScalarValue};
+use datafusion_common::cast::as_int64_array;
+use datafusion_common::error::Result;
 use datafusion_expr::{
     scalar_doc_sections::DOC_SECTION_OTHER, ColumnarValue, Documentation, 
Volatility,
 };
@@ -136,9 +137,20 @@ impl SedonaScalarKernel for RsCoordinateMapper {
         let executor = RasterExecutor::new(arg_types, args);
         let mut builder = 
Float64Builder::with_capacity(executor.num_iterations());
 
-        let (x_opt, y_opt) = get_scalar_coord(&args[1], &args[2])?;
+        // Expand x and y coordinate parameters to arrays and cast to Int64
+        let x_array = args[1].clone().cast_to(&DataType::Int64, None)?;
+        let x_array = x_array.into_array(executor.num_iterations())?;
+        let x_array = as_int64_array(&x_array)?;
+        let y_array = args[2].clone().cast_to(&DataType::Int64, None)?;
+        let y_array = y_array.into_array(executor.num_iterations())?;
+        let y_array = as_int64_array(&y_array)?;
+        let mut x_iter = x_array.iter();
+        let mut y_iter = y_array.iter();
 
         executor.execute_raster_void(|_i, raster_opt| {
+            let x_opt = x_iter.next().unwrap();
+            let y_opt = y_iter.next().unwrap();
+
             match (raster_opt, x_opt, y_opt) {
                 (Some(raster), Some(x), Some(y)) => {
                     let (world_x, world_y) = to_world_coordinate(&raster, x, 
y);
@@ -186,9 +198,20 @@ impl SedonaScalarKernel for RsCoordinatePoint {
             item.len() * executor.num_iterations(),
         );
 
-        let (x_opt, y_opt) = get_scalar_coord(&args[1], &args[2])?;
+        // Expand x and y coordinate parameters to arrays and cast to Int64
+        let x_array = args[1].clone().cast_to(&DataType::Int64, None)?;
+        let x_array = x_array.into_array(executor.num_iterations())?;
+        let x_array = as_int64_array(&x_array)?;
+        let y_array = args[2].clone().cast_to(&DataType::Int64, None)?;
+        let y_array = y_array.into_array(executor.num_iterations())?;
+        let y_array = as_int64_array(&y_array)?;
+        let mut x_iter = x_array.iter();
+        let mut y_iter = y_array.iter();
 
         executor.execute_raster_void(|_i, raster_opt| {
+            let x_opt = x_iter.next().unwrap();
+            let y_opt = y_iter.next().unwrap();
+
             match (raster_opt, x_opt, y_opt) {
                 (Some(raster), Some(x), Some(y)) => {
                     let (world_x, world_y) = to_world_coordinate(&raster, x, 
y);
@@ -205,31 +228,10 @@ impl SedonaScalarKernel for RsCoordinatePoint {
     }
 }
 
-fn extract_int_scalar(arg: &ColumnarValue) -> Result<Option<i64>> {
-    match arg {
-        ColumnarValue::Scalar(scalar) => {
-            let i64_val = scalar.cast_to(&DataType::Int64)?;
-            match i64_val {
-                ScalarValue::Int64(Some(v)) => Ok(Some(v)),
-                _ => Ok(None),
-            }
-        }
-        _ => exec_err!("Expected scalar integer argument for coordinate"),
-    }
-}
-
-fn get_scalar_coord(
-    x_arg: &ColumnarValue,
-    y_arg: &ColumnarValue,
-) -> Result<(Option<i64>, Option<i64>)> {
-    let x_opt = extract_int_scalar(x_arg)?;
-    let y_opt = extract_int_scalar(y_arg)?;
-    Ok((x_opt, y_opt))
-}
-
 #[cfg(test)]
 mod tests {
     use super::*;
+    use arrow_array::Array;
     use datafusion_expr::ScalarUDF;
     use rstest::rstest;
     use sedona_schema::datatypes::{RASTER, WKB_GEOMETRY};
@@ -307,4 +309,52 @@ mod tests {
             .unwrap();
         assert_array_equal(&result, expected);
     }
+
+    #[rstest]
+    fn udf_invoke_xy_with_array_coords(#[values(Coord::Y, Coord::X)] coord: 
Coord) {
+        let udf = match coord {
+            Coord::X => rs_rastertoworldcoordx_udf(),
+            Coord::Y => rs_rastertoworldcoordy_udf(),
+        };
+        let tester = ScalarUdfTester::new(
+            udf.into(),
+            vec![
+                RASTER,
+                SedonaType::Arrow(DataType::Int32),
+                SedonaType::Arrow(DataType::Int32),
+            ],
+        );
+
+        let rasters = generate_test_rasters(3, Some(1)).unwrap();
+        // Test with different pixel coordinates for each raster
+        // Raster 0: upper_left=(1,2), scales=(0,0), so any pixel gives (1,2)
+        // Raster 1: null
+        // Raster 2: upper_left=(3,4), scale_x=0.2, scale_y=-0.4, skew_x=0.06, 
skew_y=0.08
+        //           At pixel (1,2): x = 3 + 0.2*1 + 0.06*2 = 3.32, y = 4 + 
0.08*1 + (-0.4)*2 = 3.28
+        let x_coords = Arc::new(arrow_array::Int32Array::from(vec![0, 0, 1]));
+        let y_coords = Arc::new(arrow_array::Int32Array::from(vec![0, 0, 2]));
+
+        let result = tester
+            .invoke_arrays(vec![Arc::new(rasters), x_coords, y_coords])
+            .unwrap();
+
+        let float_array = result
+            .as_any()
+            .downcast_ref::<arrow_array::Float64Array>()
+            .expect("Expected Float64Array");
+
+        // Check each value with approximate comparison due to floating point
+        match coord {
+            Coord::X => {
+                assert!((float_array.value(0) - 1.0).abs() < 1e-10);
+                assert!(float_array.is_null(1));
+                assert!((float_array.value(2) - 3.32).abs() < 1e-10);
+            }
+            Coord::Y => {
+                assert!((float_array.value(0) - 2.0).abs() < 1e-10);
+                assert!(float_array.is_null(1));
+                assert!((float_array.value(2) - 3.28).abs() < 1e-10);
+            }
+        }
+    }
 }

Reply via email to