feat(rust/sedona-raster-functions): Add RS_RasterToWorldCoord as Point (#405)
diff --git a/rust/sedona-raster-functions/benches/native-raster-functions.rs b/rust/sedona-raster-functions/benches/native-raster-functions.rs
index ca7a1f4..8c3df52 100644
--- a/rust/sedona-raster-functions/benches/native-raster-functions.rs
+++ b/rust/sedona-raster-functions/benches/native-raster-functions.rs
@@ -25,6 +25,13 @@
c,
&f,
"native-raster",
+ "rs_rastertoworldcoord",
+ BenchmarkArgs::ArrayScalarScalar(Raster(64, 64), Int32(0, 63), Int32(0, 63)),
+ );
+ benchmark::scalar(
+ c,
+ &f,
+ "native-raster",
"rs_rastertoworldcoordx",
BenchmarkArgs::ArrayScalarScalar(Raster(64, 64), Int32(0, 63), Int32(0, 63)),
);
diff --git a/rust/sedona-raster-functions/src/register.rs b/rust/sedona-raster-functions/src/register.rs
index c850e36..d177caa 100644
--- a/rust/sedona-raster-functions/src/register.rs
+++ b/rust/sedona-raster-functions/src/register.rs
@@ -47,6 +47,7 @@
crate::rs_geotransform::rs_upperlefty_udf,
crate::rs_size::rs_height_udf,
crate::rs_size::rs_width_udf,
+ crate::rs_worldcoordinate::rs_rastertoworldcoord_udf,
crate::rs_worldcoordinate::rs_rastertoworldcoordx_udf,
crate::rs_worldcoordinate::rs_rastertoworldcoordy_udf,
);
diff --git a/rust/sedona-raster-functions/src/rs_worldcoordinate.rs b/rust/sedona-raster-functions/src/rs_worldcoordinate.rs
index 09e9089..b342a40 100644
--- a/rust/sedona-raster-functions/src/rs_worldcoordinate.rs
+++ b/rust/sedona-raster-functions/src/rs_worldcoordinate.rs
@@ -17,7 +17,7 @@
use std::{sync::Arc, vec};
use crate::executor::RasterExecutor;
-use arrow_array::builder::Float64Builder;
+use arrow_array::builder::{BinaryBuilder, Float64Builder};
use arrow_schema::DataType;
use datafusion_common::{error::Result, exec_err, ScalarValue};
use datafusion_expr::{
@@ -25,6 +25,7 @@
};
use sedona_expr::scalar_udf::{SedonaScalarKernel, SedonaScalarUDF};
use sedona_raster::affine_transformation::to_world_coordinate;
+use sedona_schema::datatypes::Edges;
use sedona_schema::{datatypes::SedonaType, matchers::ArgMatcher};
/// RS_RasterToWorldCoordY() scalar UDF implementation
@@ -51,6 +52,18 @@
)
}
+/// RS_RasterToWorldCoord() scalar UDF documentation
+///
+/// Converts pixel coordinates to world coordinates
+pub fn rs_rastertoworldcoord_udf() -> SedonaScalarUDF {
+ SedonaScalarUDF::new(
+ "rs_rastertoworldcoord",
+ vec![Arc::new(RsCoordinatePoint {})],
+ Volatility::Immutable,
+ Some(rs_rastertoworldcoord_doc()),
+ )
+}
+
fn rs_rastertoworldcoordy_doc() -> Documentation {
Documentation::builder(
DOC_SECTION_OTHER,
@@ -77,6 +90,19 @@
.build()
}
+fn rs_rastertoworldcoord_doc() -> Documentation {
+ Documentation::builder(
+ DOC_SECTION_OTHER,
+ "Returns the upper left X and Y coordinates of the given row and column of the given raster geometric units of the geo-referenced raster as a Point geometry. If any out of bounds values are given, the X and Y coordinates of the assumed point considering existing raster pixel size and skew values will be returned.".to_string(),
+ "RS_RasterToWorldCoord(raster: Raster, x: Integer, y: Integer)".to_string(),
+ )
+ .with_argument("raster", "Raster: Input raster")
+ .with_argument("x", "Integer: Column x into the raster")
+ .with_argument("y", "Integer: Row y into the raster")
+ .with_sql_example("SELECT RS_RasterToWorldCoord(RS_Example(), 0, 0)".to_string())
+ .build()
+}
+
#[derive(Debug, Clone)]
enum Coord {
X,
@@ -130,6 +156,55 @@
}
}
+#[derive(Debug)]
+struct RsCoordinatePoint;
+impl SedonaScalarKernel for RsCoordinatePoint {
+ fn return_type(&self, args: &[SedonaType]) -> Result<Option<SedonaType>> {
+ let matcher = ArgMatcher::new(
+ vec![
+ ArgMatcher::is_raster(),
+ ArgMatcher::is_integer(),
+ ArgMatcher::is_integer(),
+ ],
+ SedonaType::Wkb(Edges::Planar, None),
+ );
+
+ matcher.match_args(args)
+ }
+
+ fn invoke_batch(
+ &self,
+ arg_types: &[SedonaType],
+ args: &[ColumnarValue],
+ ) -> Result<ColumnarValue> {
+ let executor = RasterExecutor::new(arg_types, args);
+ let mut item: [u8; 21] = [0x00; 21];
+ item[0] = 0x01;
+ item[1] = 0x01;
+ let mut builder = BinaryBuilder::with_capacity(
+ executor.num_iterations(),
+ item.len() * executor.num_iterations(),
+ );
+
+ let (x_opt, y_opt) = get_scalar_coord(&args[1], &args[2])?;
+
+ executor.execute_raster_void(|_i, raster_opt| {
+ match (raster_opt, x_opt, y_opt) {
+ (Some(raster), Some(x), Some(y)) => {
+ let (world_x, world_y) = to_world_coordinate(&raster, x, y);
+ item[5..13].copy_from_slice(&world_x.to_le_bytes());
+ item[13..21].copy_from_slice(&world_y.to_le_bytes());
+ builder.append_value(item);
+ }
+ (_, _, _) => builder.append_null(),
+ }
+ Ok(())
+ })?;
+
+ executor.finish(Arc::new(builder.finish()))
+ }
+}
+
fn extract_int_scalar(arg: &ColumnarValue) -> Result<Option<i64>> {
match arg {
ColumnarValue::Scalar(scalar) => {
@@ -157,8 +232,9 @@
use super::*;
use datafusion_expr::ScalarUDF;
use rstest::rstest;
- use sedona_schema::datatypes::RASTER;
+ 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::testers::ScalarUdfTester;
@@ -171,10 +247,14 @@
let udf: ScalarUDF = rs_rastertoworldcoordx_udf().into();
assert_eq!(udf.name(), "rs_rastertoworldcoordx");
assert!(udf.documentation().is_some());
+
+ let udf: ScalarUDF = rs_rastertoworldcoord_udf().into();
+ assert_eq!(udf.name(), "rs_rastertoworldcoord");
+ assert!(udf.documentation().is_some());
}
#[rstest]
- fn udf_invoke(#[values(Coord::Y, Coord::X)] coord: Coord) {
+ fn udf_invoke_xy(#[values(Coord::Y, Coord::X)] coord: Coord) {
let udf = match coord {
Coord::X => rs_rastertoworldcoordx_udf(),
Coord::Y => rs_rastertoworldcoordy_udf(),
@@ -202,4 +282,29 @@
.unwrap();
assert_array_equal(&result, &expected);
}
+
+ #[rstest]
+ fn udf_invoke_pt() {
+ let udf = rs_rastertoworldcoord_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();
+ // At 0,0 expect the upper left corner of the test values
+ let expected = &create_array(
+ &[Some("POINT (1 2)"), None, Some("POINT (3 4)")],
+ &WKB_GEOMETRY,
+ );
+
+ let result = tester
+ .invoke_array_scalar_scalar(Arc::new(rasters), 0_i32, 0_i32)
+ .unwrap();
+ assert_array_equal(&result, expected);
+ }
}