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 45b3162c feat(sedona-raster-functions): Add RS_Rotation (#418)
45b3162c is described below
commit 45b3162c38515f918909f24f8054bf6c05c91201
Author: jp <[email protected]>
AuthorDate: Thu Dec 11 07:19:28 2025 -0800
feat(sedona-raster-functions): Add RS_Rotation (#418)
---
Cargo.lock | 1 +
.../benches/native-raster-functions.rs | 1 +
rust/sedona-raster-functions/src/register.rs | 1 +
.../sedona-raster-functions/src/rs_geotransform.rs | 39 ++++++++++++++++
rust/sedona-raster/Cargo.toml | 1 +
rust/sedona-raster/src/affine_transformation.rs | 53 ++++++++++++++++++++++
6 files changed, 96 insertions(+)
diff --git a/Cargo.lock b/Cargo.lock
index fba6d35f..bdca5d16 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -5150,6 +5150,7 @@ dependencies = [
name = "sedona-raster"
version = "0.3.0"
dependencies = [
+ "approx",
"arrow-array",
"arrow-buffer",
"arrow-schema",
diff --git a/rust/sedona-raster-functions/benches/native-raster-functions.rs
b/rust/sedona-raster-functions/benches/native-raster-functions.rs
index 4892ae2c..790b1e3d 100644
--- a/rust/sedona-raster-functions/benches/native-raster-functions.rs
+++ b/rust/sedona-raster-functions/benches/native-raster-functions.rs
@@ -41,6 +41,7 @@ fn criterion_benchmark(c: &mut Criterion) {
"rs_rastertoworldcoordy",
BenchmarkArgs::ArrayScalarScalar(Raster(64, 64), Int32(0, 63),
Int32(0, 63)),
);
+ benchmark::scalar(c, &f, "native-raster", "rs_rotation", Raster(64, 64));
benchmark::scalar(c, &f, "native-raster", "rs_scalex", Raster(64, 64));
benchmark::scalar(c, &f, "native-raster", "rs_scaley", Raster(64, 64));
benchmark::scalar(c, &f, "native-raster", "rs_skewx", Raster(64, 64));
diff --git a/rust/sedona-raster-functions/src/register.rs
b/rust/sedona-raster-functions/src/register.rs
index e2a21ba6..6db4dee4 100644
--- a/rust/sedona-raster-functions/src/register.rs
+++ b/rust/sedona-raster-functions/src/register.rs
@@ -39,6 +39,7 @@ pub fn default_function_set() -> FunctionSet {
register_scalar_udfs!(
function_set,
crate::rs_example::rs_example_udf,
+ crate::rs_geotransform::rs_rotation_udf,
crate::rs_geotransform::rs_scalex_udf,
crate::rs_geotransform::rs_scaley_udf,
crate::rs_geotransform::rs_skewx_udf,
diff --git a/rust/sedona-raster-functions/src/rs_geotransform.rs
b/rust/sedona-raster-functions/src/rs_geotransform.rs
index 805d4e32..8d3f7b8f 100644
--- a/rust/sedona-raster-functions/src/rs_geotransform.rs
+++ b/rust/sedona-raster-functions/src/rs_geotransform.rs
@@ -24,6 +24,7 @@ use datafusion_expr::{
scalar_doc_sections::DOC_SECTION_OTHER, ColumnarValue, Documentation,
Volatility,
};
use sedona_expr::scalar_udf::{SedonaScalarKernel, SedonaScalarUDF};
+use sedona_raster::affine_transformation::rotation;
use sedona_raster::traits::RasterRef;
use sedona_schema::{datatypes::SedonaType, matchers::ArgMatcher};
@@ -117,6 +118,21 @@ pub fn rs_skewy_udf() -> SedonaScalarUDF {
)
}
+/// RS_Rotation() scalar UDF implementation
+///
+/// Calculate the uniform rotation of the raster
+/// in radians based on the skew parameters.
+pub fn rs_rotation_udf() -> SedonaScalarUDF {
+ SedonaScalarUDF::new(
+ "rs_rotation",
+ vec![Arc::new(RsGeoTransform {
+ param: GeoTransformParam::Rotation,
+ })],
+ Volatility::Immutable,
+ Some(rs_rotation_doc()),
+ )
+}
+
fn rs_upperleftx_doc() -> Documentation {
Documentation::builder(
DOC_SECTION_OTHER,
@@ -183,8 +199,20 @@ fn rs_skewy_doc() -> Documentation {
.build()
}
+fn rs_rotation_doc() -> Documentation {
+ Documentation::builder(
+ DOC_SECTION_OTHER,
+ "Returns the uniform rotation of the raster in radians.".to_string(),
+ "RS_Rotation(raster: Raster)".to_string(),
+ )
+ .with_argument("raster", "Raster: Input raster")
+ .with_sql_example("SELECT RS_Rotation(RS_Example())".to_string())
+ .build()
+}
+
#[derive(Debug, Clone)]
enum GeoTransformParam {
+ Rotation,
ScaleX,
ScaleY,
SkewX,
@@ -222,6 +250,10 @@ impl SedonaScalarKernel for RsGeoTransform {
Some(raster) => {
let metadata = raster.metadata();
match self.param {
+ GeoTransformParam::Rotation => {
+ let rotation = rotation(&raster);
+ builder.append_value(rotation);
+ }
GeoTransformParam::ScaleX =>
builder.append_value(metadata.scale_x()),
GeoTransformParam::ScaleY =>
builder.append_value(metadata.scale_y()),
GeoTransformParam::SkewX =>
builder.append_value(metadata.skew_x()),
@@ -255,6 +287,10 @@ mod tests {
#[test]
fn udf_info() {
+ let udf: ScalarUDF = rs_rotation_udf().into();
+ assert_eq!(udf.name(), "rs_rotation");
+ assert!(udf.documentation().is_some());
+
let udf: ScalarUDF = rs_scalex_udf().into();
assert_eq!(udf.name(), "rs_scalex");
assert!(udf.documentation().is_some());
@@ -283,6 +319,7 @@ mod tests {
#[rstest]
fn udf_invoke(
#[values(
+ GeoTransformParam::Rotation,
GeoTransformParam::ScaleX,
GeoTransformParam::ScaleY,
GeoTransformParam::SkewX,
@@ -293,6 +330,7 @@ mod tests {
g: GeoTransformParam,
) {
let udf = match g {
+ GeoTransformParam::Rotation => rs_rotation_udf(),
GeoTransformParam::ScaleX => rs_scalex_udf(),
GeoTransformParam::ScaleY => rs_scaley_udf(),
GeoTransformParam::SkewX => rs_skewx_udf(),
@@ -304,6 +342,7 @@ mod tests {
let rasters = generate_test_rasters(3, Some(1)).unwrap();
let expected_values = match g {
+ GeoTransformParam::Rotation => vec![Some(-0.0), None,
Some(-1.2490457723982544)],
GeoTransformParam::ScaleX => vec![Some(0.0), None, Some(0.2)],
GeoTransformParam::ScaleY => vec![Some(0.0), None, Some(0.4)],
GeoTransformParam::SkewX => vec![Some(0.0), None, Some(0.6)],
diff --git a/rust/sedona-raster/Cargo.toml b/rust/sedona-raster/Cargo.toml
index 6a8d8896..f2757739 100644
--- a/rust/sedona-raster/Cargo.toml
+++ b/rust/sedona-raster/Cargo.toml
@@ -35,4 +35,5 @@ sedona-common = { workspace = true }
sedona-schema = { workspace = true }
[dev-dependencies]
+approx = { workspace = true }
sedona-testing = { path = "../sedona-testing" }
diff --git a/rust/sedona-raster/src/affine_transformation.rs
b/rust/sedona-raster/src/affine_transformation.rs
index a5787a94..4152d455 100644
--- a/rust/sedona-raster/src/affine_transformation.rs
+++ b/rust/sedona-raster/src/affine_transformation.rs
@@ -18,6 +18,13 @@
use crate::traits::RasterRef;
use arrow_schema::ArrowError;
+/// Computes the rotation angle (in radians) of the raster based on its
geotransform metadata.
+#[inline]
+pub fn rotation(raster: &dyn RasterRef) -> f64 {
+ let metadata = raster.metadata();
+ (-metadata.skew_x()).atan2(metadata.scale_x())
+}
+
/// Performs an affine transformation on the provided x and y coordinates
based on the geotransform
/// data in the raster.
///
@@ -75,6 +82,9 @@ pub fn to_raster_coordinate(
mod tests {
use super::*;
use crate::traits::{MetadataRef, RasterMetadata};
+ use approx::assert_relative_eq;
+ use std::f64::consts::FRAC_1_SQRT_2;
+ use std::f64::consts::PI;
struct TestRaster {
metadata: RasterMetadata,
@@ -92,6 +102,34 @@ mod tests {
}
}
+ #[test]
+ fn test_rotation() {
+ // 0 degree rotation -> gt[1.0, 0.0, 0.0, -1.0]
+ let raster = rotation_raster(1.0, -1.0, 0.0, 0.0);
+ let rot = rotation(&raster);
+ assert_eq!(rot, 0.0);
+
+ // pi/2 -> gt[0.0, -1.0, 1.0, 0.0]
+ let raster = rotation_raster(0.0, 0.0, -1.0, 1.0);
+ let rot = rotation(&raster);
+ assert_relative_eq!(rot, PI / 2.0, epsilon = 1e-6); // 90 degrees in
radians
+
+ // pi/4 -> gt[0.70710678, -0.70710678, 0.70710678, 0.70710678]
+ let raster = rotation_raster(FRAC_1_SQRT_2, FRAC_1_SQRT_2,
-FRAC_1_SQRT_2, FRAC_1_SQRT_2);
+ let rot = rotation(&raster);
+ assert_relative_eq!(rot, PI / 4.0, epsilon = 1e-6); // 45 degrees in
radians
+
+ // pi/3 -> gt[0.5, -0.866025, 0.866025, 0.5]
+ let raster = rotation_raster(0.5, 0.5, -0.866025, 0.866025);
+ let rot = rotation(&raster);
+ assert_relative_eq!(rot, PI / 3.0, epsilon = 1e-6); // 60 degrees in
radians
+
+ // pi -> gt[-1.0, 0.0, 0.0, -1.0]
+ let raster = rotation_raster(-1.0, -1.0, 0.0, 0.0);
+ let rot = rotation(&raster);
+ assert_relative_eq!(rot, -PI, epsilon = 1e-6); // 180 degrees in
radians
+ }
+
#[test]
fn test_to_world_coordinate() {
// Test case with rotation/skew
@@ -177,4 +215,19 @@ mod tests {
.to_string()
.contains("determinant is zero."));
}
+
+ fn rotation_raster(scale_x: f64, scale_y: f64, skew_x: f64, skew_y: f64)
-> TestRaster {
+ TestRaster {
+ metadata: RasterMetadata {
+ width: 10,
+ height: 20,
+ upperleft_x: 0.0,
+ upperleft_y: 0.0,
+ scale_x,
+ scale_y,
+ skew_x,
+ skew_y,
+ },
+ }
+ }
}