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 90658675 feat(c/sedona-geos): Implement item crs support for 
GEOS-based kernels (#509)
90658675 is described below

commit 90658675a6b849356599e2ae86477c0f8e60545c
Author: Dewey Dunnington <[email protected]>
AuthorDate: Thu Jan 15 08:54:11 2026 -0600

    feat(c/sedona-geos): Implement item crs support for GEOS-based kernels 
(#509)
---
 c/sedona-geos/src/register.rs                    | 154 ++++++++++-------------
 c/sedona-geos/src/st_area.rs                     |  16 ++-
 c/sedona-geos/src/st_boundary.rs                 |  21 +++-
 c/sedona-geos/src/st_buffer.rs                   |  28 ++++-
 c/sedona-geos/src/st_centroid.rs                 |  23 +++-
 c/sedona-geos/src/st_concavehull.rs              |  33 ++++-
 c/sedona-geos/src/st_convexhull.rs               |  23 +++-
 c/sedona-geos/src/st_dwithin.rs                  |  22 ++--
 c/sedona-geos/src/st_isring.rs                   |  16 ++-
 c/sedona-geos/src/st_issimple.rs                 |  16 ++-
 c/sedona-geos/src/st_isvalid.rs                  |  16 ++-
 c/sedona-geos/src/st_isvalidreason.rs            |  16 ++-
 c/sedona-geos/src/st_length.rs                   |  16 ++-
 c/sedona-geos/src/st_line_merge.rs               |  30 ++++-
 c/sedona-geos/src/st_makevalid.rs                |  23 +++-
 c/sedona-geos/src/st_minimumclearance.rs         |  16 ++-
 c/sedona-geos/src/st_minimumclearance_line.rs    |  24 +++-
 c/sedona-geos/src/st_nrings.rs                   |  18 ++-
 c/sedona-geos/src/st_numinteriorrings.rs         |  18 ++-
 c/sedona-geos/src/st_numpoints.rs                |  18 ++-
 c/sedona-geos/src/st_perimeter.rs                |  16 ++-
 c/sedona-geos/src/st_polygonize.rs               |  26 +++-
 c/sedona-geos/src/st_simplify.rs                 |  26 +++-
 c/sedona-geos/src/st_simplifypreservetopology.rs |  29 ++++-
 c/sedona-geos/src/st_snap.rs                     |  30 ++++-
 c/sedona-geos/src/st_unaryunion.rs               |  25 +++-
 26 files changed, 493 insertions(+), 206 deletions(-)

diff --git a/c/sedona-geos/src/register.rs b/c/sedona-geos/src/register.rs
index 82553bc2..39f80676 100644
--- a/c/sedona-geos/src/register.rs
+++ b/c/sedona-geos/src/register.rs
@@ -15,97 +15,77 @@
 // specific language governing permissions and limitations
 // under the License.
 use sedona_expr::aggregate_udf::SedonaAccumulatorRef;
-use sedona_expr::scalar_udf::ScalarKernelRef;
+use sedona_expr::scalar_udf::{IntoScalarKernelRefs, ScalarKernelRef};
 
-use crate::{
-    distance::st_distance_impl,
-    st_area::st_area_impl,
-    st_boundary::st_boundary_impl,
-    st_buffer::{st_buffer_impl, st_buffer_style_impl},
-    st_centroid::st_centroid_impl,
-    st_concavehull::{st_concave_hull_allow_holes_impl, st_concave_hull_impl},
-    st_convexhull::st_convex_hull_impl,
-    st_dwithin::st_dwithin_impl,
-    st_isring::st_is_ring_impl,
-    st_issimple::st_is_simple_impl,
-    st_isvalid::st_is_valid_impl,
-    st_isvalidreason::st_is_valid_reason_impl,
-    st_length::st_length_impl,
-    st_line_merge::st_line_merge_impl,
-    st_makevalid::st_make_valid_impl,
-    st_minimumclearance::st_minimum_clearance_impl,
-    st_minimumclearance_line::st_minimum_clearance_line_impl,
-    st_nrings::st_nrings_impl,
-    st_numinteriorrings::st_num_interior_rings_impl,
-    st_numpoints::st_num_points_impl,
-    st_perimeter::st_perimeter_impl,
-    st_polygonize::st_polygonize_impl,
-    st_polygonize_agg::st_polygonize_agg_impl,
-    st_simplify::st_simplify_impl,
-    st_simplifypreservetopology::st_simplify_preserve_topology_impl,
-    st_snap::st_snap_impl,
-    st_unaryunion::st_unary_union_impl,
-};
-
-use crate::binary_predicates::{
-    st_contains_impl, st_covered_by_impl, st_covers_impl, st_crosses_impl, 
st_disjoint_impl,
-    st_equals_impl, st_intersects_impl, st_overlaps_impl, st_touches_impl, 
st_within_impl,
-};
+macro_rules! define_scalar_kernels {
+    ($($name:expr => $impl:expr),* $(,)?) => {
+        vec![
+            $(
+                ($name, $impl().into_scalar_kernel_refs()),
+            )*
+        ]
+    };
+}
 
-use crate::overlay::{
-    st_difference_impl, st_intersection_impl, st_sym_difference_impl, 
st_union_impl,
-};
+macro_rules! define_aggregate_kernels {
+    ($($name:expr => $impl:expr),* $(,)?) => {
+        vec![
+            $(
+                ($name, $impl()),
+            )*
+        ]
+    };
+}
 
-pub fn scalar_kernels() -> Vec<(&'static str, ScalarKernelRef)> {
-    vec![
-        ("st_area", st_area_impl()),
-        ("st_boundary", st_boundary_impl()),
-        ("st_buffer", st_buffer_impl()),
-        ("st_buffer", st_buffer_style_impl()),
-        ("st_centroid", st_centroid_impl()),
-        ("st_concavehull", st_concave_hull_allow_holes_impl()),
-        ("st_concavehull", st_concave_hull_impl()),
-        ("st_contains", st_contains_impl()),
-        ("st_convexhull", st_convex_hull_impl()),
-        ("st_coveredby", st_covered_by_impl()),
-        ("st_covers", st_covers_impl()),
-        ("st_crosses", st_crosses_impl()),
-        ("st_difference", st_difference_impl()),
-        ("st_disjoint", st_disjoint_impl()),
-        ("st_distance", st_distance_impl()),
-        ("st_dwithin", st_dwithin_impl()),
-        ("st_equals", st_equals_impl()),
-        ("st_intersection", st_intersection_impl()),
-        ("st_intersects", st_intersects_impl()),
-        ("st_isring", st_is_ring_impl()),
-        ("st_issimple", st_is_simple_impl()),
-        ("st_isvalid", st_is_valid_impl()),
-        ("st_isvalidreason", st_is_valid_reason_impl()),
-        ("st_length", st_length_impl()),
-        ("st_linemerge", st_line_merge_impl()),
-        ("st_numinteriorrings", st_num_interior_rings_impl()),
-        ("st_numpoints", st_num_points_impl()),
-        ("st_nrings", st_nrings_impl()),
-        ("st_makevalid", st_make_valid_impl()),
-        ("st_minimumclearance", st_minimum_clearance_impl()),
-        ("st_minimumclearanceline", st_minimum_clearance_line_impl()),
-        ("st_overlaps", st_overlaps_impl()),
-        ("st_perimeter", st_perimeter_impl()),
-        ("st_polygonize", st_polygonize_impl()),
-        ("st_simplify", st_simplify_impl()),
-        (
-            "st_simplifypreservetopology",
-            st_simplify_preserve_topology_impl(),
-        ),
-        ("st_snap", st_snap_impl()),
-        ("st_symdifference", st_sym_difference_impl()),
-        ("st_touches", st_touches_impl()),
-        ("st_unaryunion", st_unary_union_impl()),
-        ("st_union", st_union_impl()),
-        ("st_within", st_within_impl()),
-    ]
+pub fn scalar_kernels() -> Vec<(&'static str, Vec<ScalarKernelRef>)> {
+    define_scalar_kernels!(
+        "st_area" => crate::st_area::st_area_impl,
+        "st_boundary" => crate::st_boundary::st_boundary_impl,
+        "st_buffer" => crate::st_buffer::st_buffer_impl,
+        "st_buffer" => crate::st_buffer::st_buffer_style_impl,
+        "st_centroid" => crate::st_centroid::st_centroid_impl,
+        "st_concavehull" => 
crate::st_concavehull::st_concave_hull_allow_holes_impl,
+        "st_concavehull" => crate::st_concavehull::st_concave_hull_impl,
+        "st_contains" => crate::binary_predicates::st_contains_impl,
+        "st_convexhull" => crate::st_convexhull::st_convex_hull_impl,
+        "st_coveredby" => crate::binary_predicates::st_covered_by_impl,
+        "st_covers" => crate::binary_predicates::st_covers_impl,
+        "st_crosses" => crate::binary_predicates::st_crosses_impl,
+        "st_difference" => crate::overlay::st_difference_impl,
+        "st_disjoint" => crate::binary_predicates::st_disjoint_impl,
+        "st_distance" => crate::distance::st_distance_impl,
+        "st_dwithin" => crate::st_dwithin::st_dwithin_impl,
+        "st_equals" => crate::binary_predicates::st_equals_impl,
+        "st_intersection" => crate::overlay::st_intersection_impl,
+        "st_intersects" => crate::binary_predicates::st_intersects_impl,
+        "st_isring" => crate::st_isring::st_is_ring_impl,
+        "st_issimple" => crate::st_issimple::st_is_simple_impl,
+        "st_isvalid" => crate::st_isvalid::st_is_valid_impl,
+        "st_isvalidreason" => crate::st_isvalidreason::st_is_valid_reason_impl,
+        "st_length" => crate::st_length::st_length_impl,
+        "st_linemerge" => crate::st_line_merge::st_line_merge_impl,
+        "st_makevalid" => crate::st_makevalid::st_make_valid_impl,
+        "st_minimumclearance" => 
crate::st_minimumclearance::st_minimum_clearance_impl,
+        "st_minimumclearanceline" => 
crate::st_minimumclearance_line::st_minimum_clearance_line_impl,
+        "st_nrings" => crate::st_nrings::st_nrings_impl,
+        "st_numinteriorrings" => 
crate::st_numinteriorrings::st_num_interior_rings_impl,
+        "st_numpoints" => crate::st_numpoints::st_num_points_impl,
+        "st_overlaps" => crate::binary_predicates::st_overlaps_impl,
+        "st_perimeter" => crate::st_perimeter::st_perimeter_impl,
+        "st_polygonize" => crate::st_polygonize::st_polygonize_impl,
+        "st_simplify" => crate::st_simplify::st_simplify_impl,
+        "st_simplifypreservetopology" => 
crate::st_simplifypreservetopology::st_simplify_preserve_topology_impl,
+        "st_snap" => crate::st_snap::st_snap_impl,
+        "st_symdifference" => crate::overlay::st_sym_difference_impl,
+        "st_touches" => crate::binary_predicates::st_touches_impl,
+        "st_unaryunion" => crate::st_unaryunion::st_unary_union_impl,
+        "st_union" => crate::overlay::st_union_impl,
+        "st_within" => crate::binary_predicates::st_within_impl,
+    )
 }
 
 pub fn aggregate_kernels() -> Vec<(&'static str, SedonaAccumulatorRef)> {
-    vec![("st_polygonize_agg", st_polygonize_agg_impl())]
+    define_aggregate_kernels!(
+        "st_polygonize_agg" => 
crate::st_polygonize_agg::st_polygonize_agg_impl,
+    )
 }
diff --git a/c/sedona-geos/src/st_area.rs b/c/sedona-geos/src/st_area.rs
index 7c9bead7..7cd7bf82 100644
--- a/c/sedona-geos/src/st_area.rs
+++ b/c/sedona-geos/src/st_area.rs
@@ -21,14 +21,17 @@ use arrow_schema::DataType;
 use datafusion_common::{error::Result, DataFusionError};
 use datafusion_expr::ColumnarValue;
 use geos::Geom;
-use sedona_expr::scalar_udf::{ScalarKernelRef, SedonaScalarKernel};
+use sedona_expr::{
+    item_crs::ItemCrsKernel,
+    scalar_udf::{ScalarKernelRef, SedonaScalarKernel},
+};
 use sedona_schema::{datatypes::SedonaType, matchers::ArgMatcher};
 
 use crate::executor::GeosExecutor;
 
 /// ST_Area() implementation using the geos crate
-pub fn st_area_impl() -> ScalarKernelRef {
-    Arc::new(STArea {})
+pub fn st_area_impl() -> Vec<ScalarKernelRef> {
+    ItemCrsKernel::wrap_impl(STArea {})
 }
 
 #[derive(Debug)]
@@ -78,13 +81,16 @@ mod tests {
     use datafusion_common::ScalarValue;
     use rstest::rstest;
     use sedona_expr::scalar_udf::SedonaScalarUDF;
-    use sedona_schema::datatypes::{WKB_GEOMETRY, WKB_VIEW_GEOMETRY};
+    use sedona_schema::datatypes::{WKB_GEOMETRY, WKB_GEOMETRY_ITEM_CRS, 
WKB_VIEW_GEOMETRY};
     use sedona_testing::testers::ScalarUdfTester;
 
     use super::*;
 
     #[rstest]
-    fn udf(#[values(WKB_GEOMETRY, WKB_VIEW_GEOMETRY)] sedona_type: SedonaType) 
{
+    fn udf(
+        #[values(WKB_GEOMETRY, WKB_VIEW_GEOMETRY, 
WKB_GEOMETRY_ITEM_CRS.clone())]
+        sedona_type: SedonaType,
+    ) {
         let udf = SedonaScalarUDF::from_impl("st_area", st_area_impl());
         let tester = ScalarUdfTester::new(udf.into(), vec![sedona_type]);
         tester.assert_return_type(DataType::Float64);
diff --git a/c/sedona-geos/src/st_boundary.rs b/c/sedona-geos/src/st_boundary.rs
index f2dbc744..e1f72b6b 100644
--- a/c/sedona-geos/src/st_boundary.rs
+++ b/c/sedona-geos/src/st_boundary.rs
@@ -21,7 +21,10 @@ use arrow_array::builder::BinaryBuilder;
 use datafusion_common::{error::Result, DataFusionError};
 use datafusion_expr::ColumnarValue;
 use geos::{Geom, Geometry, GeometryTypes};
-use sedona_expr::scalar_udf::{ScalarKernelRef, SedonaScalarKernel};
+use sedona_expr::{
+    item_crs::ItemCrsKernel,
+    scalar_udf::{ScalarKernelRef, SedonaScalarKernel},
+};
 use sedona_geometry::wkb_factory::WKB_MIN_PROBABLE_BYTES;
 use sedona_schema::{
     datatypes::{SedonaType, WKB_GEOMETRY},
@@ -32,8 +35,8 @@ use crate::executor::GeosExecutor;
 use crate::geos_to_wkb::write_geos_geometry;
 
 /// ST_Boundary() implementation using the geos crate
-pub fn st_boundary_impl() -> ScalarKernelRef {
-    Arc::new(STBoundary {})
+pub fn st_boundary_impl() -> Vec<ScalarKernelRef> {
+    ItemCrsKernel::wrap_impl(STBoundary {})
 }
 
 #[derive(Debug)]
@@ -284,7 +287,7 @@ fn collect_boundary_components(
 mod tests {
     use rstest::rstest;
     use sedona_expr::scalar_udf::SedonaScalarUDF;
-    use sedona_schema::datatypes::{WKB_GEOMETRY, WKB_VIEW_GEOMETRY};
+    use sedona_schema::datatypes::{WKB_GEOMETRY, WKB_GEOMETRY_ITEM_CRS, 
WKB_VIEW_GEOMETRY};
     use sedona_testing::testers::ScalarUdfTester;
 
     use super::*;
@@ -367,4 +370,14 @@ mod tests {
         let result = tester.invoke_scalar("GEOMETRYCOLLECTION EMPTY").unwrap();
         tester.assert_scalar_result_equals(result, "GEOMETRYCOLLECTION EMPTY");
     }
+
+    #[rstest]
+    fn udf_invoke_item_crs(#[values(WKB_GEOMETRY_ITEM_CRS.clone())] 
sedona_type: SedonaType) {
+        let udf = SedonaScalarUDF::from_impl("st_boundary", 
st_boundary_impl());
+        let tester = ScalarUdfTester::new(udf.into(), 
vec![sedona_type.clone()]);
+        tester.assert_return_type(sedona_type);
+
+        let result = tester.invoke_scalar("POINT (1 3)").unwrap();
+        tester.assert_scalar_result_equals(result, "GEOMETRYCOLLECTION EMPTY");
+    }
 }
diff --git a/c/sedona-geos/src/st_buffer.rs b/c/sedona-geos/src/st_buffer.rs
index 0c98da35..29718a72 100644
--- a/c/sedona-geos/src/st_buffer.rs
+++ b/c/sedona-geos/src/st_buffer.rs
@@ -23,7 +23,10 @@ use datafusion_common::error::Result;
 use datafusion_common::{DataFusionError, ScalarValue};
 use datafusion_expr::ColumnarValue;
 use geos::{BufferParams, CapStyle, Geom, JoinStyle};
-use sedona_expr::scalar_udf::{ScalarKernelRef, SedonaScalarKernel};
+use sedona_expr::{
+    item_crs::ItemCrsKernel,
+    scalar_udf::{ScalarKernelRef, SedonaScalarKernel},
+};
 use sedona_geometry::wkb_factory::WKB_MIN_PROBABLE_BYTES;
 use sedona_schema::{
     datatypes::{SedonaType, WKB_GEOMETRY},
@@ -46,8 +49,8 @@ use crate::geos_to_wkb::write_geos_geometry;
 /// - side: both, left, right
 /// - mitre_limit/miter_limit: numeric value
 /// - quad_segs/quadrant_segments: integer value
-pub fn st_buffer_impl() -> ScalarKernelRef {
-    Arc::new(STBuffer {})
+pub fn st_buffer_impl() -> Vec<ScalarKernelRef> {
+    ItemCrsKernel::wrap_impl(STBuffer {})
 }
 
 #[derive(Debug)]
@@ -72,8 +75,8 @@ impl SedonaScalarKernel for STBuffer {
     }
 }
 
-pub fn st_buffer_style_impl() -> ScalarKernelRef {
-    Arc::new(STBufferStyle {})
+pub fn st_buffer_style_impl() -> Vec<ScalarKernelRef> {
+    ItemCrsKernel::wrap_impl(STBufferStyle {})
 }
 #[derive(Debug)]
 struct STBufferStyle {}
@@ -296,7 +299,7 @@ mod tests {
     use datafusion_common::ScalarValue;
     use rstest::rstest;
     use sedona_expr::scalar_udf::SedonaScalarUDF;
-    use sedona_schema::datatypes::{WKB_GEOMETRY, WKB_VIEW_GEOMETRY};
+    use sedona_schema::datatypes::{WKB_GEOMETRY, WKB_GEOMETRY_ITEM_CRS, 
WKB_VIEW_GEOMETRY};
     use sedona_testing::compare::assert_array_equal;
     use sedona_testing::create::create_array;
     use sedona_testing::testers::ScalarUdfTester;
@@ -728,4 +731,17 @@ mod tests {
             "Should handle complex string with both sides."
         );
     }
+
+    #[rstest]
+    fn udf_invoke_item_crs(#[values(WKB_GEOMETRY_ITEM_CRS.clone())] 
sedona_type: SedonaType) {
+        let udf = SedonaScalarUDF::from_impl("st_buffer", st_buffer_impl());
+        let tester = ScalarUdfTester::new(
+            udf.into(),
+            vec![sedona_type.clone(), SedonaType::Arrow(DataType::Float64)],
+        );
+        tester.assert_return_type(sedona_type);
+
+        let result = tester.invoke_scalar_scalar("POINT (0 0)", 1.0).unwrap();
+        assert!(!result.is_null());
+    }
 }
diff --git a/c/sedona-geos/src/st_centroid.rs b/c/sedona-geos/src/st_centroid.rs
index 6750a2c7..793b0215 100644
--- a/c/sedona-geos/src/st_centroid.rs
+++ b/c/sedona-geos/src/st_centroid.rs
@@ -20,7 +20,10 @@ use arrow_array::builder::BinaryBuilder;
 use datafusion_common::{error::Result, DataFusionError};
 use datafusion_expr::ColumnarValue;
 use geos::Geom;
-use sedona_expr::scalar_udf::{ScalarKernelRef, SedonaScalarKernel};
+use sedona_expr::{
+    item_crs::ItemCrsKernel,
+    scalar_udf::{ScalarKernelRef, SedonaScalarKernel},
+};
 use sedona_geometry::wkb_factory::WKB_MIN_PROBABLE_BYTES;
 use sedona_schema::{
     datatypes::{SedonaType, WKB_GEOMETRY},
@@ -31,8 +34,8 @@ use crate::executor::GeosExecutor;
 use crate::geos_to_wkb::write_geos_geometry;
 
 /// ST_Centroid() implementation using the geos crate
-pub fn st_centroid_impl() -> ScalarKernelRef {
-    Arc::new(STCentroid {})
+pub fn st_centroid_impl() -> Vec<ScalarKernelRef> {
+    ItemCrsKernel::wrap_impl(STCentroid {})
 }
 
 #[derive(Debug)]
@@ -85,7 +88,7 @@ mod tests {
     use datafusion_common::ScalarValue;
     use rstest::rstest;
     use sedona_expr::scalar_udf::SedonaScalarUDF;
-    use sedona_schema::datatypes::{WKB_GEOMETRY, WKB_VIEW_GEOMETRY};
+    use sedona_schema::datatypes::{WKB_GEOMETRY, WKB_GEOMETRY_ITEM_CRS, 
WKB_VIEW_GEOMETRY};
     use sedona_testing::compare::assert_array_equal;
     use sedona_testing::create::create_array;
     use sedona_testing::testers::ScalarUdfTester;
@@ -118,4 +121,16 @@ mod tests {
         );
         assert_array_equal(&tester.invoke_wkb_array(input_wkt).unwrap(), 
&expected);
     }
+
+    #[rstest]
+    fn udf_invoke_item_crs(#[values(WKB_GEOMETRY_ITEM_CRS.clone())] 
sedona_type: SedonaType) {
+        let udf = SedonaScalarUDF::from_impl("st_centroid", 
st_centroid_impl());
+        let tester = ScalarUdfTester::new(udf.into(), 
vec![sedona_type.clone()]);
+        tester.assert_return_type(sedona_type);
+
+        let result = tester
+            .invoke_scalar("POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))")
+            .unwrap();
+        tester.assert_scalar_result_equals(result, "POINT (0.5 0.5)");
+    }
 }
diff --git a/c/sedona-geos/src/st_concavehull.rs 
b/c/sedona-geos/src/st_concavehull.rs
index 7f8e884e..31c31d9c 100644
--- a/c/sedona-geos/src/st_concavehull.rs
+++ b/c/sedona-geos/src/st_concavehull.rs
@@ -23,7 +23,10 @@ use datafusion_common::error::Result;
 use datafusion_common::{DataFusionError, ScalarValue};
 use datafusion_expr::ColumnarValue;
 use geos::Geom;
-use sedona_expr::scalar_udf::{ScalarKernelRef, SedonaScalarKernel};
+use sedona_expr::{
+    item_crs::ItemCrsKernel,
+    scalar_udf::{ScalarKernelRef, SedonaScalarKernel},
+};
 use sedona_geometry::wkb_factory::WKB_MIN_PROBABLE_BYTES;
 use sedona_schema::{
     datatypes::{SedonaType, WKB_GEOMETRY},
@@ -34,8 +37,8 @@ use crate::executor::GeosExecutor;
 use crate::geos_to_wkb::write_geos_geometry;
 
 /// ST_ConcaveHull() implementation using the geos crate
-pub fn st_concave_hull_allow_holes_impl() -> ScalarKernelRef {
-    Arc::new(STConcaveHullAllowHoles {})
+pub fn st_concave_hull_allow_holes_impl() -> Vec<ScalarKernelRef> {
+    ItemCrsKernel::wrap_impl(STConcaveHullAllowHoles {})
 }
 
 #[derive(Debug)]
@@ -64,8 +67,8 @@ impl SedonaScalarKernel for STConcaveHullAllowHoles {
     }
 }
 
-pub fn st_concave_hull_impl() -> ScalarKernelRef {
-    Arc::new(STConcaveHull {})
+pub fn st_concave_hull_impl() -> Vec<ScalarKernelRef> {
+    ItemCrsKernel::wrap_impl(STConcaveHull {})
 }
 
 #[derive(Debug)]
@@ -152,7 +155,7 @@ mod tests {
     use datafusion_common::ScalarValue;
     use rstest::rstest;
     use sedona_expr::scalar_udf::SedonaScalarUDF;
-    use sedona_schema::datatypes::{WKB_GEOMETRY, WKB_VIEW_GEOMETRY};
+    use sedona_schema::datatypes::{WKB_GEOMETRY, WKB_GEOMETRY_ITEM_CRS, 
WKB_VIEW_GEOMETRY};
     use sedona_testing::{
         compare::{assert_array_equal, assert_scalar_equal_wkb_geometry},
         create::create_array,
@@ -471,4 +474,22 @@ mod tests {
             .unwrap();
         tester.assert_scalar_result_equals(result, "POLYGON ((3 3, 1 1, 4 5, 5 
6, 3 3))");
     }
+
+    #[rstest]
+    fn udf_invoke_item_crs(#[values(WKB_GEOMETRY_ITEM_CRS.clone())] 
sedona_type: SedonaType) {
+        let udf = SedonaScalarUDF::from_impl("st_concavehull", 
st_concave_hull_impl());
+        let tester = ScalarUdfTester::new(
+            udf.into(),
+            vec![sedona_type.clone(), SedonaType::Arrow(DataType::Float64)],
+        );
+        tester.assert_return_type(sedona_type);
+
+        let result = tester
+            .invoke_scalar_scalar("POLYGON ((70 80, 50 60, 100 150, 160 170, 
70 80))", 0.2)
+            .unwrap();
+        tester.assert_scalar_result_equals(
+            result,
+            "POLYGON ((70 80, 50 60, 100 150, 160 170, 70 80))",
+        );
+    }
 }
diff --git a/c/sedona-geos/src/st_convexhull.rs 
b/c/sedona-geos/src/st_convexhull.rs
index 48c76c5d..a034ead1 100644
--- a/c/sedona-geos/src/st_convexhull.rs
+++ b/c/sedona-geos/src/st_convexhull.rs
@@ -20,7 +20,10 @@ use arrow_array::builder::BinaryBuilder;
 use datafusion_common::{error::Result, DataFusionError};
 use datafusion_expr::ColumnarValue;
 use geos::Geom;
-use sedona_expr::scalar_udf::{ScalarKernelRef, SedonaScalarKernel};
+use sedona_expr::{
+    item_crs::ItemCrsKernel,
+    scalar_udf::{ScalarKernelRef, SedonaScalarKernel},
+};
 use sedona_geometry::wkb_factory::WKB_MIN_PROBABLE_BYTES;
 use sedona_schema::{
     datatypes::{SedonaType, WKB_GEOMETRY},
@@ -31,8 +34,8 @@ use crate::executor::GeosExecutor;
 use crate::geos_to_wkb::write_geos_geometry;
 
 /// ST_ConvexHull() implementation using the geos crate
-pub fn st_convex_hull_impl() -> ScalarKernelRef {
-    Arc::new(STConvexHull {})
+pub fn st_convex_hull_impl() -> Vec<ScalarKernelRef> {
+    ItemCrsKernel::wrap_impl(STConvexHull {})
 }
 
 #[derive(Debug)]
@@ -85,7 +88,7 @@ mod tests {
     use datafusion_common::ScalarValue;
     use rstest::rstest;
     use sedona_expr::scalar_udf::SedonaScalarUDF;
-    use sedona_schema::datatypes::{WKB_GEOMETRY, WKB_VIEW_GEOMETRY};
+    use sedona_schema::datatypes::{WKB_GEOMETRY, WKB_GEOMETRY_ITEM_CRS, 
WKB_VIEW_GEOMETRY};
     use sedona_testing::compare::assert_array_equal;
     use sedona_testing::create::create_array;
     use sedona_testing::testers::ScalarUdfTester;
@@ -122,4 +125,16 @@ mod tests {
         );
         assert_array_equal(&tester.invoke_wkb_array(input_wkt).unwrap(), 
&expected);
     }
+
+    #[rstest]
+    fn udf_invoke_item_crs(#[values(WKB_GEOMETRY_ITEM_CRS.clone())] 
sedona_type: SedonaType) {
+        let udf = SedonaScalarUDF::from_impl("st_convex_hull", 
st_convex_hull_impl());
+        let tester = ScalarUdfTester::new(udf.into(), 
vec![sedona_type.clone()]);
+        tester.assert_return_type(sedona_type);
+
+        let result = tester
+            .invoke_scalar("MULTIPOINT ((0 0), (0 1), (1 1), (1 0))")
+            .unwrap();
+        tester.assert_scalar_result_equals(result, "POLYGON ((0 0, 0 1, 1 1, 1 
0, 0 0))");
+    }
 }
diff --git a/c/sedona-geos/src/st_dwithin.rs b/c/sedona-geos/src/st_dwithin.rs
index e24471c3..6f1708c8 100644
--- a/c/sedona-geos/src/st_dwithin.rs
+++ b/c/sedona-geos/src/st_dwithin.rs
@@ -21,14 +21,17 @@ use arrow_schema::DataType;
 use datafusion_common::{cast::as_float64_array, error::Result, 
DataFusionError};
 use datafusion_expr::ColumnarValue;
 use geos::Geom;
-use sedona_expr::scalar_udf::{ScalarKernelRef, SedonaScalarKernel};
+use sedona_expr::{
+    item_crs::ItemCrsKernel,
+    scalar_udf::{ScalarKernelRef, SedonaScalarKernel},
+};
 use sedona_schema::{datatypes::SedonaType, matchers::ArgMatcher};
 
 use crate::executor::GeosExecutor;
 
 /// Implementation of ST_DWithin using the geos crate
-pub fn st_dwithin_impl() -> ScalarKernelRef {
-    Arc::new(STDWithin {})
+pub fn st_dwithin_impl() -> Vec<ScalarKernelRef> {
+    ItemCrsKernel::wrap_impl(STDWithin {})
 }
 
 #[derive(Debug)]
@@ -85,7 +88,7 @@ mod tests {
     use datafusion_common::ScalarValue;
     use rstest::rstest;
     use sedona_expr::scalar_udf::SedonaScalarUDF;
-    use sedona_schema::datatypes::{WKB_GEOMETRY, WKB_VIEW_GEOMETRY};
+    use sedona_schema::datatypes::{WKB_GEOMETRY, WKB_GEOMETRY_ITEM_CRS, 
WKB_VIEW_GEOMETRY};
     use sedona_testing::compare::assert_array_equal;
     use sedona_testing::create::create_array;
     use sedona_testing::testers::ScalarUdfTester;
@@ -93,13 +96,16 @@ mod tests {
     use super::*;
 
     #[rstest]
-    fn udf(#[values(WKB_GEOMETRY, WKB_VIEW_GEOMETRY)] sedona_type: SedonaType) 
{
+    fn udf(
+        #[values(WKB_GEOMETRY, WKB_VIEW_GEOMETRY, 
WKB_GEOMETRY_ITEM_CRS.clone())]
+        sedona_type: SedonaType,
+    ) {
         let udf = SedonaScalarUDF::from_impl("st_dwithin", st_dwithin_impl());
         let tester = ScalarUdfTester::new(
             udf.into(),
             vec![
                 sedona_type.clone(),
-                sedona_type,
+                sedona_type.clone(),
                 SedonaType::Arrow(DataType::Float64),
             ],
         );
@@ -122,7 +128,7 @@ mod tests {
                 None,
                 Some("POINT EMPTY"),
             ],
-            &WKB_GEOMETRY,
+            &sedona_type,
         );
         let arg2 = create_array(
             &[
@@ -131,7 +137,7 @@ mod tests {
                 Some("POINT (0 0)"),
                 Some("POINT EMPTY"),
             ],
-            &WKB_GEOMETRY,
+            &sedona_type,
         );
         let distance = 1;
 
diff --git a/c/sedona-geos/src/st_isring.rs b/c/sedona-geos/src/st_isring.rs
index 0a6bb585..4f3697ee 100644
--- a/c/sedona-geos/src/st_isring.rs
+++ b/c/sedona-geos/src/st_isring.rs
@@ -22,14 +22,17 @@ use arrow_schema::DataType;
 use datafusion_common::{error::Result, DataFusionError};
 use datafusion_expr::ColumnarValue;
 use geos::{Geom, GeometryTypes};
-use sedona_expr::scalar_udf::{ScalarKernelRef, SedonaScalarKernel};
+use sedona_expr::{
+    item_crs::ItemCrsKernel,
+    scalar_udf::{ScalarKernelRef, SedonaScalarKernel},
+};
 use sedona_schema::{datatypes::SedonaType, matchers::ArgMatcher};
 
 use crate::executor::GeosExecutor;
 
 /// ST_IsRing() implementation using the geos crate
-pub fn st_is_ring_impl() -> ScalarKernelRef {
-    Arc::new(STIsRing {})
+pub fn st_is_ring_impl() -> Vec<ScalarKernelRef> {
+    ItemCrsKernel::wrap_impl(STIsRing {})
 }
 
 #[derive(Debug)]
@@ -98,14 +101,17 @@ mod tests {
     use arrow_array::{create_array as arrow_array, ArrayRef};
     use rstest::rstest;
     use sedona_expr::scalar_udf::SedonaScalarUDF;
-    use sedona_schema::datatypes::{WKB_GEOMETRY, WKB_VIEW_GEOMETRY};
+    use sedona_schema::datatypes::{WKB_GEOMETRY, WKB_GEOMETRY_ITEM_CRS, 
WKB_VIEW_GEOMETRY};
     use sedona_testing::compare::assert_array_equal;
     use sedona_testing::testers::ScalarUdfTester;
 
     use super::*;
 
     #[rstest]
-    fn udf(#[values(WKB_GEOMETRY, WKB_VIEW_GEOMETRY)] sedona_type: SedonaType) 
{
+    fn udf(
+        #[values(WKB_GEOMETRY, WKB_VIEW_GEOMETRY, 
WKB_GEOMETRY_ITEM_CRS.clone())]
+        sedona_type: SedonaType,
+    ) {
         let udf = SedonaScalarUDF::from_impl("st_isring", st_is_ring_impl());
         let tester = ScalarUdfTester::new(udf.into(), vec![sedona_type]);
         tester.assert_return_type(DataType::Boolean);
diff --git a/c/sedona-geos/src/st_issimple.rs b/c/sedona-geos/src/st_issimple.rs
index 1820a465..f7a52fae 100644
--- a/c/sedona-geos/src/st_issimple.rs
+++ b/c/sedona-geos/src/st_issimple.rs
@@ -22,14 +22,17 @@ use arrow_schema::DataType;
 use datafusion_common::{DataFusionError, Result};
 use datafusion_expr::ColumnarValue;
 use geos::Geom;
-use sedona_expr::scalar_udf::{ScalarKernelRef, SedonaScalarKernel};
+use sedona_expr::{
+    item_crs::ItemCrsKernel,
+    scalar_udf::{ScalarKernelRef, SedonaScalarKernel},
+};
 use sedona_schema::{datatypes::SedonaType, matchers::ArgMatcher};
 
 use crate::executor::GeosExecutor;
 
 /// ST_IsSimple() implementation using the geos crate
-pub fn st_is_simple_impl() -> ScalarKernelRef {
-    Arc::new(STIsSimple {})
+pub fn st_is_simple_impl() -> Vec<ScalarKernelRef> {
+    ItemCrsKernel::wrap_impl(STIsSimple {})
 }
 
 #[derive(Debug)]
@@ -80,13 +83,16 @@ mod tests {
     use datafusion_common::ScalarValue;
     use rstest::rstest;
     use sedona_expr::scalar_udf::SedonaScalarUDF;
-    use sedona_schema::datatypes::{WKB_GEOMETRY, WKB_VIEW_GEOMETRY};
+    use sedona_schema::datatypes::{WKB_GEOMETRY, WKB_GEOMETRY_ITEM_CRS, 
WKB_VIEW_GEOMETRY};
     use sedona_testing::testers::ScalarUdfTester;
 
     use super::*;
 
     #[rstest]
-    fn udf(#[values(WKB_GEOMETRY, WKB_VIEW_GEOMETRY)] sedona_type: SedonaType) 
{
+    fn udf(
+        #[values(WKB_GEOMETRY, WKB_VIEW_GEOMETRY, 
WKB_GEOMETRY_ITEM_CRS.clone())]
+        sedona_type: SedonaType,
+    ) {
         let udf = SedonaScalarUDF::from_impl("st_issimple", 
st_is_simple_impl());
         let tester = ScalarUdfTester::new(udf.into(), vec![sedona_type]);
         tester.assert_return_type(DataType::Boolean);
diff --git a/c/sedona-geos/src/st_isvalid.rs b/c/sedona-geos/src/st_isvalid.rs
index 50fb40df..cb183947 100644
--- a/c/sedona-geos/src/st_isvalid.rs
+++ b/c/sedona-geos/src/st_isvalid.rs
@@ -21,14 +21,17 @@ use arrow_schema::DataType;
 use datafusion_common::{DataFusionError, Result};
 use datafusion_expr::ColumnarValue;
 use geos::Geom;
-use sedona_expr::scalar_udf::{ScalarKernelRef, SedonaScalarKernel};
+use sedona_expr::{
+    item_crs::ItemCrsKernel,
+    scalar_udf::{ScalarKernelRef, SedonaScalarKernel},
+};
 use sedona_schema::{datatypes::SedonaType, matchers::ArgMatcher};
 
 use crate::executor::GeosExecutor;
 
 /// ST_IsValid() implementation using the geos crate
-pub fn st_is_valid_impl() -> ScalarKernelRef {
-    Arc::new(STIsValid {})
+pub fn st_is_valid_impl() -> Vec<ScalarKernelRef> {
+    ItemCrsKernel::wrap_impl(STIsValid {})
 }
 
 #[derive(Debug)]
@@ -81,13 +84,16 @@ mod tests {
     use datafusion_common::ScalarValue;
     use rstest::rstest;
     use sedona_expr::scalar_udf::SedonaScalarUDF;
-    use sedona_schema::datatypes::{WKB_GEOMETRY, WKB_VIEW_GEOMETRY};
+    use sedona_schema::datatypes::{WKB_GEOMETRY, WKB_GEOMETRY_ITEM_CRS, 
WKB_VIEW_GEOMETRY};
     use sedona_testing::testers::ScalarUdfTester;
 
     use super::*;
 
     #[rstest]
-    fn udf(#[values(WKB_GEOMETRY, WKB_VIEW_GEOMETRY)] sedona_type: SedonaType) 
{
+    fn udf(
+        #[values(WKB_GEOMETRY, WKB_VIEW_GEOMETRY, 
WKB_GEOMETRY_ITEM_CRS.clone())]
+        sedona_type: SedonaType,
+    ) {
         let udf = SedonaScalarUDF::from_impl("st_isvalid", st_is_valid_impl());
         let tester = ScalarUdfTester::new(udf.into(), vec![sedona_type]);
         tester.assert_return_type(DataType::Boolean);
diff --git a/c/sedona-geos/src/st_isvalidreason.rs 
b/c/sedona-geos/src/st_isvalidreason.rs
index 396c2ee9..527c520b 100644
--- a/c/sedona-geos/src/st_isvalidreason.rs
+++ b/c/sedona-geos/src/st_isvalidreason.rs
@@ -21,15 +21,18 @@ use arrow_array::builder::StringBuilder;
 use arrow_schema::DataType;
 use datafusion_common::{DataFusionError, Result};
 use geos::Geom;
-use sedona_expr::scalar_udf::{ScalarKernelRef, SedonaScalarKernel};
+use sedona_expr::{
+    item_crs::ItemCrsKernel,
+    scalar_udf::{ScalarKernelRef, SedonaScalarKernel},
+};
 use sedona_geometry::wkb_factory::WKB_MIN_PROBABLE_BYTES;
 use sedona_schema::{datatypes::SedonaType, matchers::ArgMatcher};
 
 use crate::executor::GeosExecutor;
 
 /// ST_IsValidReason() implementation using the geos crate
-pub fn st_is_valid_reason_impl() -> ScalarKernelRef {
-    Arc::new(STIsValidReason {})
+pub fn st_is_valid_reason_impl() -> Vec<ScalarKernelRef> {
+    ItemCrsKernel::wrap_impl(STIsValidReason {})
 }
 
 #[derive(Debug)]
@@ -82,13 +85,16 @@ mod tests {
     use datafusion_common::ScalarValue;
     use rstest::rstest;
     use sedona_expr::scalar_udf::SedonaScalarUDF;
-    use sedona_schema::datatypes::{WKB_GEOMETRY, WKB_VIEW_GEOMETRY};
+    use sedona_schema::datatypes::{WKB_GEOMETRY, WKB_GEOMETRY_ITEM_CRS, 
WKB_VIEW_GEOMETRY};
     use sedona_testing::testers::ScalarUdfTester;
 
     use super::*;
 
     #[rstest]
-    fn udf(#[values(WKB_GEOMETRY, WKB_VIEW_GEOMETRY)] sedona_type: SedonaType) 
{
+    fn udf(
+        #[values(WKB_GEOMETRY, WKB_VIEW_GEOMETRY, 
WKB_GEOMETRY_ITEM_CRS.clone())]
+        sedona_type: SedonaType,
+    ) {
         use arrow_array::Array;
 
         let udf = SedonaScalarUDF::from_impl("st_isvalidreason", 
st_is_valid_reason_impl());
diff --git a/c/sedona-geos/src/st_length.rs b/c/sedona-geos/src/st_length.rs
index 9ef119c2..d196c27d 100644
--- a/c/sedona-geos/src/st_length.rs
+++ b/c/sedona-geos/src/st_length.rs
@@ -24,14 +24,17 @@ use geos::{
     GResult, Geom,
     GeometryTypes::{GeometryCollection, LineString, MultiLineString},
 };
-use sedona_expr::scalar_udf::{ScalarKernelRef, SedonaScalarKernel};
+use sedona_expr::{
+    item_crs::ItemCrsKernel,
+    scalar_udf::{ScalarKernelRef, SedonaScalarKernel},
+};
 use sedona_schema::{datatypes::SedonaType, matchers::ArgMatcher};
 
 use crate::executor::GeosExecutor;
 
 /// ST_Length() implementation using the geos crate
-pub fn st_length_impl() -> ScalarKernelRef {
-    Arc::new(STLength {})
+pub fn st_length_impl() -> Vec<ScalarKernelRef> {
+    ItemCrsKernel::wrap_impl(STLength {})
 }
 
 #[derive(Debug)]
@@ -99,13 +102,16 @@ mod tests {
     use datafusion_common::ScalarValue;
     use rstest::rstest;
     use sedona_expr::scalar_udf::SedonaScalarUDF;
-    use sedona_schema::datatypes::{WKB_GEOMETRY, WKB_VIEW_GEOMETRY};
+    use sedona_schema::datatypes::{WKB_GEOMETRY, WKB_GEOMETRY_ITEM_CRS, 
WKB_VIEW_GEOMETRY};
     use sedona_testing::testers::ScalarUdfTester;
 
     use super::*;
 
     #[rstest]
-    fn udf(#[values(WKB_GEOMETRY, WKB_VIEW_GEOMETRY)] sedona_type: SedonaType) 
{
+    fn udf(
+        #[values(WKB_GEOMETRY, WKB_VIEW_GEOMETRY, 
WKB_GEOMETRY_ITEM_CRS.clone())]
+        sedona_type: SedonaType,
+    ) {
         let udf = SedonaScalarUDF::from_impl("st_length", st_length_impl());
         let tester = ScalarUdfTester::new(udf.into(), vec![sedona_type]);
         tester.assert_return_type(DataType::Float64);
diff --git a/c/sedona-geos/src/st_line_merge.rs 
b/c/sedona-geos/src/st_line_merge.rs
index db7146c8..4c1757b2 100644
--- a/c/sedona-geos/src/st_line_merge.rs
+++ b/c/sedona-geos/src/st_line_merge.rs
@@ -21,15 +21,18 @@ use arrow_array::builder::BinaryBuilder;
 use datafusion_common::{error::Result, DataFusionError, ScalarValue};
 use datafusion_expr::ColumnarValue;
 use geos::Geom;
-use sedona_expr::scalar_udf::{ScalarKernelRef, SedonaScalarKernel};
+use sedona_expr::{
+    item_crs::ItemCrsKernel,
+    scalar_udf::{ScalarKernelRef, SedonaScalarKernel},
+};
 use sedona_geometry::wkb_factory::WKB_MIN_PROBABLE_BYTES;
 use sedona_schema::{datatypes::WKB_GEOMETRY, matchers::ArgMatcher};
 
 use crate::executor::GeosExecutor;
 use crate::geos_to_wkb::write_geos_geometry;
 
-pub fn st_line_merge_impl() -> ScalarKernelRef {
-    Arc::new(STLineMerge {})
+pub fn st_line_merge_impl() -> Vec<ScalarKernelRef> {
+    ItemCrsKernel::wrap_impl(STLineMerge {})
 }
 
 #[derive(Debug)]
@@ -116,7 +119,9 @@ mod tests {
     use datafusion_common::ScalarValue;
     use rstest::rstest;
     use sedona_expr::scalar_udf::SedonaScalarUDF;
-    use sedona_schema::datatypes::{SedonaType, WKB_GEOMETRY, 
WKB_VIEW_GEOMETRY};
+    use sedona_schema::datatypes::{
+        SedonaType, WKB_GEOMETRY, WKB_GEOMETRY_ITEM_CRS, WKB_VIEW_GEOMETRY,
+    };
     use sedona_testing::create::create_array;
     use sedona_testing::testers::ScalarUdfTester;
 
@@ -178,4 +183,21 @@ mod tests {
             .unwrap();
         assert!(result.is_null());
     }
+
+    #[rstest]
+    fn udf_invoke_item_crs(#[values(WKB_GEOMETRY_ITEM_CRS.clone())] 
sedona_type: SedonaType) {
+        use arrow_schema::DataType;
+
+        let udf = SedonaScalarUDF::from_impl("st_linemerge", 
st_line_merge_impl());
+        let tester = ScalarUdfTester::new(
+            udf.into(),
+            vec![sedona_type.clone(), SedonaType::Arrow(DataType::Boolean)],
+        );
+        tester.assert_return_type(sedona_type);
+
+        let result = tester
+            .invoke_scalar_scalar("MULTILINESTRING ((0 0, 1 0), (1 0, 1 1))", 
false)
+            .unwrap();
+        tester.assert_scalar_result_equals(result, "LINESTRING (0 0, 1 0, 1 
1)");
+    }
 }
diff --git a/c/sedona-geos/src/st_makevalid.rs 
b/c/sedona-geos/src/st_makevalid.rs
index 71011846..cefc7b74 100644
--- a/c/sedona-geos/src/st_makevalid.rs
+++ b/c/sedona-geos/src/st_makevalid.rs
@@ -21,7 +21,10 @@ use arrow_array::builder::BinaryBuilder;
 use datafusion_common::{DataFusionError, Result};
 use datafusion_expr::ColumnarValue;
 use geos::Geom;
-use sedona_expr::scalar_udf::{ScalarKernelRef, SedonaScalarKernel};
+use sedona_expr::{
+    item_crs::ItemCrsKernel,
+    scalar_udf::{ScalarKernelRef, SedonaScalarKernel},
+};
 use sedona_geometry::wkb_factory::WKB_MIN_PROBABLE_BYTES;
 use sedona_schema::{
     datatypes::{SedonaType, WKB_GEOMETRY},
@@ -32,8 +35,8 @@ use crate::executor::GeosExecutor;
 use crate::geos_to_wkb::write_geos_geometry;
 
 /// ST_MakeValid() implementation using the geos crate
-pub fn st_make_valid_impl() -> ScalarKernelRef {
-    Arc::new(STMakeValid {})
+pub fn st_make_valid_impl() -> Vec<ScalarKernelRef> {
+    ItemCrsKernel::wrap_impl(STMakeValid {})
 }
 
 #[derive(Debug)]
@@ -86,7 +89,7 @@ fn invoke_scalar(geos_geom: &geos::Geometry, writer: &mut 
impl std::io::Write) -
 mod tests {
     use rstest::rstest;
     use sedona_expr::scalar_udf::SedonaScalarUDF;
-    use sedona_schema::datatypes::WKB_VIEW_GEOMETRY;
+    use sedona_schema::datatypes::{WKB_GEOMETRY_ITEM_CRS, WKB_VIEW_GEOMETRY};
     use sedona_testing::{create::create_array, testers::ScalarUdfTester};
 
     use super::*;
@@ -155,4 +158,16 @@ mod tests {
             "POLYGON((0 0,0 10,10 10,10 0,0 0),(5 5,5 5.0001,5.0001 
5.0001,5.0001 5,5 5))",
         );
     }
+
+    #[rstest]
+    fn udf_invoke_item_crs(#[values(WKB_GEOMETRY_ITEM_CRS.clone())] 
sedona_type: SedonaType) {
+        let udf = SedonaScalarUDF::from_impl("st_makevalid", 
st_make_valid_impl());
+        let tester = ScalarUdfTester::new(udf.into(), 
vec![sedona_type.clone()]);
+        tester.assert_return_type(sedona_type);
+
+        let result = tester
+            .invoke_scalar("POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))")
+            .unwrap();
+        tester.assert_scalar_result_equals(result, "POLYGON ((0 0, 0 1, 1 1, 1 
0, 0 0))");
+    }
 }
diff --git a/c/sedona-geos/src/st_minimumclearance.rs 
b/c/sedona-geos/src/st_minimumclearance.rs
index 1b183871..bdf8c6ee 100644
--- a/c/sedona-geos/src/st_minimumclearance.rs
+++ b/c/sedona-geos/src/st_minimumclearance.rs
@@ -22,14 +22,17 @@ use arrow_schema::DataType;
 use datafusion_common::{DataFusionError, Result};
 use datafusion_expr::ColumnarValue;
 use geos::Geom;
-use sedona_expr::scalar_udf::{ScalarKernelRef, SedonaScalarKernel};
+use sedona_expr::{
+    item_crs::ItemCrsKernel,
+    scalar_udf::{ScalarKernelRef, SedonaScalarKernel},
+};
 use sedona_schema::{datatypes::SedonaType, matchers::ArgMatcher};
 
 use crate::executor::GeosExecutor;
 
 /// ST_MinimumClearance() implementation using the geos crate
-pub fn st_minimum_clearance_impl() -> ScalarKernelRef {
-    Arc::new(STMinimumClearance {})
+pub fn st_minimum_clearance_impl() -> Vec<ScalarKernelRef> {
+    ItemCrsKernel::wrap_impl(STMinimumClearance {})
 }
 
 #[derive(Debug)]
@@ -77,13 +80,16 @@ mod tests {
     use arrow_array::{create_array, ArrayRef};
     use rstest::rstest;
     use sedona_expr::scalar_udf::SedonaScalarUDF;
-    use sedona_schema::datatypes::{WKB_GEOMETRY, WKB_VIEW_GEOMETRY};
+    use sedona_schema::datatypes::{WKB_GEOMETRY, WKB_GEOMETRY_ITEM_CRS, 
WKB_VIEW_GEOMETRY};
     use sedona_testing::testers::ScalarUdfTester;
 
     use super::*;
 
     #[rstest]
-    fn udf(#[values(WKB_GEOMETRY, WKB_VIEW_GEOMETRY)] sedona_type: SedonaType) 
{
+    fn udf(
+        #[values(WKB_GEOMETRY, WKB_VIEW_GEOMETRY, 
WKB_GEOMETRY_ITEM_CRS.clone())]
+        sedona_type: SedonaType,
+    ) {
         let udf = SedonaScalarUDF::from_impl("st_minimumclearance", 
st_minimum_clearance_impl());
         let tester = ScalarUdfTester::new(udf.into(), vec![sedona_type]);
         tester.assert_return_type(DataType::Float64);
diff --git a/c/sedona-geos/src/st_minimumclearance_line.rs 
b/c/sedona-geos/src/st_minimumclearance_line.rs
index 984bdcb7..947cce48 100644
--- a/c/sedona-geos/src/st_minimumclearance_line.rs
+++ b/c/sedona-geos/src/st_minimumclearance_line.rs
@@ -21,7 +21,10 @@ use arrow_array::builder::BinaryBuilder;
 use datafusion_common::{DataFusionError, Result};
 use datafusion_expr::ColumnarValue;
 use geos::Geom;
-use sedona_expr::scalar_udf::{ScalarKernelRef, SedonaScalarKernel};
+use sedona_expr::{
+    item_crs::ItemCrsKernel,
+    scalar_udf::{ScalarKernelRef, SedonaScalarKernel},
+};
 use sedona_geometry::wkb_factory::WKB_MIN_PROBABLE_BYTES;
 use sedona_schema::{
     datatypes::{SedonaType, WKB_GEOMETRY},
@@ -32,8 +35,8 @@ use crate::executor::GeosExecutor;
 use crate::geos_to_wkb::write_geos_geometry;
 
 /// ST_MinimumClearanceLine() implementation using the geos crate
-pub fn st_minimum_clearance_line_impl() -> ScalarKernelRef {
-    Arc::new(STMinimumClearanceLine {})
+pub fn st_minimum_clearance_line_impl() -> Vec<ScalarKernelRef> {
+    ItemCrsKernel::wrap_impl(STMinimumClearanceLine {})
 }
 
 #[derive(Debug)]
@@ -88,7 +91,7 @@ fn invoke_scalar(geos_geom: &geos::Geometry, writer: &mut 
impl std::io::Write) -
 mod tests {
     use rstest::rstest;
     use sedona_expr::scalar_udf::SedonaScalarUDF;
-    use sedona_schema::datatypes::WKB_VIEW_GEOMETRY;
+    use sedona_schema::datatypes::{WKB_GEOMETRY_ITEM_CRS, WKB_VIEW_GEOMETRY};
     use sedona_testing::{create::create_array, testers::ScalarUdfTester};
 
     use super::*;
@@ -134,4 +137,17 @@ mod tests {
 
         assert_eq!(&tester.invoke_wkb_array(input_wkt).unwrap(), &expected);
     }
+
+    #[rstest]
+    fn udf_invoke_item_crs(#[values(WKB_GEOMETRY_ITEM_CRS.clone())] 
sedona_type: SedonaType) {
+        let udf =
+            SedonaScalarUDF::from_impl("st_minimumclearanceline", 
st_minimum_clearance_line_impl());
+        let tester = ScalarUdfTester::new(udf.into(), 
vec![sedona_type.clone()]);
+        tester.assert_return_type(sedona_type);
+
+        let result = tester
+            .invoke_scalar("POLYGON ((0 0, 1 0, 1 1, 0.5 3.2e-4, 0 0))")
+            .unwrap();
+        tester.assert_scalar_result_equals(result, "LINESTRING(0.5 0.00032,0.5 
0)");
+    }
 }
diff --git a/c/sedona-geos/src/st_nrings.rs b/c/sedona-geos/src/st_nrings.rs
index 7893f0f6..e57e5036 100644
--- a/c/sedona-geos/src/st_nrings.rs
+++ b/c/sedona-geos/src/st_nrings.rs
@@ -23,11 +23,14 @@ use arrow_schema::DataType;
 use datafusion_common::{error::Result, DataFusionError};
 use datafusion_expr::ColumnarValue;
 use geos::{Geom, GeometryTypes};
-use sedona_expr::scalar_udf::{ScalarKernelRef, SedonaScalarKernel};
+use sedona_expr::{
+    item_crs::ItemCrsKernel,
+    scalar_udf::{ScalarKernelRef, SedonaScalarKernel},
+};
 use sedona_schema::{datatypes::SedonaType, matchers::ArgMatcher};
 
-pub fn st_nrings_impl() -> ScalarKernelRef {
-    Arc::new(STNRings {})
+pub fn st_nrings_impl() -> Vec<ScalarKernelRef> {
+    ItemCrsKernel::wrap_impl(STNRings {})
 }
 
 #[derive(Debug)]
@@ -112,14 +115,19 @@ mod tests {
     use datafusion_common::ScalarValue;
     use rstest::rstest;
     use sedona_expr::scalar_udf::SedonaScalarUDF;
-    use sedona_schema::datatypes::{SedonaType, WKB_GEOMETRY, 
WKB_VIEW_GEOMETRY};
+    use sedona_schema::datatypes::{
+        SedonaType, WKB_GEOMETRY, WKB_GEOMETRY_ITEM_CRS, WKB_VIEW_GEOMETRY,
+    };
     use sedona_testing::compare::assert_array_equal;
     use sedona_testing::testers::ScalarUdfTester;
 
     use super::*;
 
     #[rstest]
-    fn udf(#[values(WKB_GEOMETRY, WKB_VIEW_GEOMETRY)] sedona_type: SedonaType) 
{
+    fn udf(
+        #[values(WKB_GEOMETRY, WKB_VIEW_GEOMETRY, 
WKB_GEOMETRY_ITEM_CRS.clone())]
+        sedona_type: SedonaType,
+    ) {
         let udf = SedonaScalarUDF::from_impl("st_nrings", st_nrings_impl());
         let tester = ScalarUdfTester::new(udf.into(), vec![sedona_type]);
         tester.assert_return_type(DataType::Int32);
diff --git a/c/sedona-geos/src/st_numinteriorrings.rs 
b/c/sedona-geos/src/st_numinteriorrings.rs
index 3f127e54..71b368dd 100644
--- a/c/sedona-geos/src/st_numinteriorrings.rs
+++ b/c/sedona-geos/src/st_numinteriorrings.rs
@@ -23,11 +23,14 @@ use arrow_schema::DataType;
 use datafusion_common::{error::Result, DataFusionError};
 use datafusion_expr::ColumnarValue;
 use geos::{Geom, Geometry, GeometryTypes};
-use sedona_expr::scalar_udf::{ScalarKernelRef, SedonaScalarKernel};
+use sedona_expr::{
+    item_crs::ItemCrsKernel,
+    scalar_udf::{ScalarKernelRef, SedonaScalarKernel},
+};
 use sedona_schema::{datatypes::SedonaType, matchers::ArgMatcher};
 
-pub fn st_num_interior_rings_impl() -> ScalarKernelRef {
-    Arc::new(STNumInteriorRings {})
+pub fn st_num_interior_rings_impl() -> Vec<ScalarKernelRef> {
+    ItemCrsKernel::wrap_impl(STNumInteriorRings {})
 }
 
 #[derive(Debug)]
@@ -99,14 +102,19 @@ mod tests {
     use datafusion_common::ScalarValue;
     use rstest::rstest;
     use sedona_expr::scalar_udf::SedonaScalarUDF;
-    use sedona_schema::datatypes::{SedonaType, WKB_GEOMETRY, 
WKB_VIEW_GEOMETRY};
+    use sedona_schema::datatypes::{
+        SedonaType, WKB_GEOMETRY, WKB_GEOMETRY_ITEM_CRS, WKB_VIEW_GEOMETRY,
+    };
     use sedona_testing::compare::assert_array_equal;
     use sedona_testing::testers::ScalarUdfTester;
 
     use super::*;
 
     #[rstest]
-    fn udf(#[values(WKB_GEOMETRY, WKB_VIEW_GEOMETRY)] sedona_type: SedonaType) 
{
+    fn udf(
+        #[values(WKB_GEOMETRY, WKB_VIEW_GEOMETRY, 
WKB_GEOMETRY_ITEM_CRS.clone())]
+        sedona_type: SedonaType,
+    ) {
         let udf = SedonaScalarUDF::from_impl("st_numinteriorrings", 
st_num_interior_rings_impl());
         let tester = ScalarUdfTester::new(udf.into(), vec![sedona_type]);
         tester.assert_return_type(DataType::Int32);
diff --git a/c/sedona-geos/src/st_numpoints.rs 
b/c/sedona-geos/src/st_numpoints.rs
index fd77b53c..9112578a 100644
--- a/c/sedona-geos/src/st_numpoints.rs
+++ b/c/sedona-geos/src/st_numpoints.rs
@@ -23,11 +23,14 @@ use arrow_schema::DataType;
 use datafusion_common::{error::Result, DataFusionError};
 use datafusion_expr::ColumnarValue;
 use geos::{Geom, Geometry, GeometryTypes};
-use sedona_expr::scalar_udf::{ScalarKernelRef, SedonaScalarKernel};
+use sedona_expr::{
+    item_crs::ItemCrsKernel,
+    scalar_udf::{ScalarKernelRef, SedonaScalarKernel},
+};
 use sedona_schema::{datatypes::SedonaType, matchers::ArgMatcher};
 
-pub fn st_num_points_impl() -> ScalarKernelRef {
-    Arc::new(STNumPoints {})
+pub fn st_num_points_impl() -> Vec<ScalarKernelRef> {
+    ItemCrsKernel::wrap_impl(STNumPoints {})
 }
 
 #[derive(Debug)]
@@ -92,14 +95,19 @@ mod tests {
     use datafusion_common::ScalarValue;
     use rstest::rstest;
     use sedona_expr::scalar_udf::SedonaScalarUDF;
-    use sedona_schema::datatypes::{SedonaType, WKB_GEOMETRY, 
WKB_VIEW_GEOMETRY};
+    use sedona_schema::datatypes::{
+        SedonaType, WKB_GEOMETRY, WKB_GEOMETRY_ITEM_CRS, WKB_VIEW_GEOMETRY,
+    };
     use sedona_testing::compare::assert_array_equal;
     use sedona_testing::testers::ScalarUdfTester;
 
     use super::*;
 
     #[rstest]
-    fn udf(#[values(WKB_GEOMETRY, WKB_VIEW_GEOMETRY)] sedona_type: SedonaType) 
{
+    fn udf(
+        #[values(WKB_GEOMETRY, WKB_VIEW_GEOMETRY, 
WKB_GEOMETRY_ITEM_CRS.clone())]
+        sedona_type: SedonaType,
+    ) {
         let udf = SedonaScalarUDF::from_impl("st_numpoints", 
st_num_points_impl());
         let tester = ScalarUdfTester::new(udf.into(), vec![sedona_type]);
         tester.assert_return_type(DataType::Int32);
diff --git a/c/sedona-geos/src/st_perimeter.rs 
b/c/sedona-geos/src/st_perimeter.rs
index f3c80826..592a7461 100644
--- a/c/sedona-geos/src/st_perimeter.rs
+++ b/c/sedona-geos/src/st_perimeter.rs
@@ -24,13 +24,16 @@ use geos::{
     GResult, Geom,
     GeometryTypes::{GeometryCollection, MultiPolygon, Polygon},
 };
-use sedona_expr::scalar_udf::{ScalarKernelRef, SedonaScalarKernel};
+use sedona_expr::{
+    item_crs::ItemCrsKernel,
+    scalar_udf::{ScalarKernelRef, SedonaScalarKernel},
+};
 use sedona_schema::{datatypes::SedonaType, matchers::ArgMatcher};
 
 use crate::executor::GeosExecutor;
 
-pub fn st_perimeter_impl() -> ScalarKernelRef {
-    Arc::new(STPerimeter {})
+pub fn st_perimeter_impl() -> Vec<ScalarKernelRef> {
+    ItemCrsKernel::wrap_impl(STPerimeter {})
 }
 
 #[derive(Debug)]
@@ -98,13 +101,16 @@ mod tests {
     use datafusion_common::ScalarValue;
     use rstest::rstest;
     use sedona_expr::scalar_udf::SedonaScalarUDF;
-    use sedona_schema::datatypes::{WKB_GEOMETRY, WKB_VIEW_GEOMETRY};
+    use sedona_schema::datatypes::{WKB_GEOMETRY, WKB_GEOMETRY_ITEM_CRS, 
WKB_VIEW_GEOMETRY};
     use sedona_testing::testers::ScalarUdfTester;
 
     use super::*;
 
     #[rstest]
-    fn udf(#[values(WKB_GEOMETRY, WKB_VIEW_GEOMETRY)] sedona_type: SedonaType) 
{
+    fn udf(
+        #[values(WKB_GEOMETRY, WKB_VIEW_GEOMETRY, 
WKB_GEOMETRY_ITEM_CRS.clone())]
+        sedona_type: SedonaType,
+    ) {
         let udf = SedonaScalarUDF::from_impl("st_perimeter", 
st_perimeter_impl());
         let tester = ScalarUdfTester::new(udf.into(), vec![sedona_type]);
         tester.assert_return_type(DataType::Float64);
diff --git a/c/sedona-geos/src/st_polygonize.rs 
b/c/sedona-geos/src/st_polygonize.rs
index 71708984..3d7a280c 100644
--- a/c/sedona-geos/src/st_polygonize.rs
+++ b/c/sedona-geos/src/st_polygonize.rs
@@ -20,7 +20,10 @@ use std::sync::Arc;
 use arrow_array::builder::BinaryBuilder;
 use datafusion_common::{DataFusionError, Result};
 use datafusion_expr::ColumnarValue;
-use sedona_expr::scalar_udf::{ScalarKernelRef, SedonaScalarKernel};
+use sedona_expr::{
+    item_crs::ItemCrsKernel,
+    scalar_udf::{ScalarKernelRef, SedonaScalarKernel},
+};
 use sedona_geometry::wkb_factory::WKB_MIN_PROBABLE_BYTES;
 use sedona_schema::{
     datatypes::{SedonaType, WKB_GEOMETRY},
@@ -31,8 +34,8 @@ use crate::executor::GeosExecutor;
 use crate::geos_to_wkb::write_geos_geometry;
 
 /// ST_Polygonize() scalar implementation using GEOS
-pub fn st_polygonize_impl() -> ScalarKernelRef {
-    Arc::new(STPolygonize {})
+pub fn st_polygonize_impl() -> Vec<ScalarKernelRef> {
+    ItemCrsKernel::wrap_impl(STPolygonize {})
 }
 
 #[derive(Debug)]
@@ -83,7 +86,7 @@ fn invoke_scalar(geos_geom: &geos::Geometry, writer: &mut 
impl std::io::Write) -
 mod tests {
     use rstest::rstest;
     use sedona_expr::scalar_udf::SedonaScalarUDF;
-    use sedona_schema::datatypes::{SedonaType, WKB_VIEW_GEOMETRY};
+    use sedona_schema::datatypes::{SedonaType, WKB_GEOMETRY_ITEM_CRS, 
WKB_VIEW_GEOMETRY};
     use sedona_testing::{
         compare::assert_array_equal, create::create_array, 
testers::ScalarUdfTester,
     };
@@ -139,4 +142,19 @@ mod tests {
             &expected_geometries,
         );
     }
+
+    #[rstest]
+    fn udf_invoke_item_crs(#[values(WKB_GEOMETRY_ITEM_CRS.clone())] 
sedona_type: SedonaType) {
+        let udf = SedonaScalarUDF::from_impl("st_polygonize", 
st_polygonize_impl());
+        let tester = ScalarUdfTester::new(udf.into(), 
vec![sedona_type.clone()]);
+        tester.assert_return_type(sedona_type);
+
+        let result = tester
+            .invoke_scalar("LINESTRING(0 0, 0 1, 1 1, 1 0, 0 0)")
+            .unwrap();
+        tester.assert_scalar_result_equals(
+            result,
+            "GEOMETRYCOLLECTION(POLYGON((0 0, 0 1, 1 1, 1 0, 0 0)))",
+        );
+    }
 }
diff --git a/c/sedona-geos/src/st_simplify.rs b/c/sedona-geos/src/st_simplify.rs
index 8f76606f..0217533c 100644
--- a/c/sedona-geos/src/st_simplify.rs
+++ b/c/sedona-geos/src/st_simplify.rs
@@ -22,7 +22,10 @@ use arrow_schema::DataType;
 use datafusion_common::{cast::as_float64_array, DataFusionError, Result};
 use datafusion_expr::ColumnarValue;
 use geos::{Geom, Geometry, GeometryTypes};
-use sedona_expr::scalar_udf::{ScalarKernelRef, SedonaScalarKernel};
+use sedona_expr::{
+    item_crs::ItemCrsKernel,
+    scalar_udf::{ScalarKernelRef, SedonaScalarKernel},
+};
 use sedona_geometry::wkb_factory::WKB_MIN_PROBABLE_BYTES;
 use sedona_schema::{
     datatypes::{SedonaType, WKB_GEOMETRY},
@@ -33,8 +36,8 @@ use crate::executor::GeosExecutor;
 use crate::geos_to_wkb::write_geos_geometry;
 
 /// ST_Simplify() implementation using the geos crate
-pub fn st_simplify_impl() -> ScalarKernelRef {
-    Arc::new(STSimplify {})
+pub fn st_simplify_impl() -> Vec<ScalarKernelRef> {
+    ItemCrsKernel::wrap_impl(STSimplify {})
 }
 
 #[derive(Debug)]
@@ -147,7 +150,7 @@ mod tests {
     use datafusion_common::ScalarValue;
     use rstest::rstest;
     use sedona_expr::scalar_udf::SedonaScalarUDF;
-    use sedona_schema::datatypes::{WKB_GEOMETRY, WKB_VIEW_GEOMETRY};
+    use sedona_schema::datatypes::{WKB_GEOMETRY, WKB_GEOMETRY_ITEM_CRS, 
WKB_VIEW_GEOMETRY};
     use sedona_testing::{
         compare::assert_array_equal, create::create_array, 
testers::ScalarUdfTester,
     };
@@ -628,4 +631,19 @@ mod tests {
             .unwrap();
         tester.assert_scalar_result_equals(result, "LINESTRING(0 0, 10 0, 5 
15, 0 0)");
     }
+
+    #[rstest]
+    fn udf_invoke_item_crs(#[values(WKB_GEOMETRY_ITEM_CRS.clone())] 
sedona_type: SedonaType) {
+        let udf = SedonaScalarUDF::from_impl("st_simplify", 
st_simplify_impl());
+        let tester = ScalarUdfTester::new(
+            udf.into(),
+            vec![sedona_type.clone(), SedonaType::Arrow(DataType::Float64)],
+        );
+        tester.assert_return_type(sedona_type);
+
+        let result = tester
+            .invoke_scalar_scalar("LINESTRING(0 0, 1 1, 2 0, 3 1, 4 0)", 1.5)
+            .unwrap();
+        tester.assert_scalar_result_equals(result, "LINESTRING(0 0, 4 0)");
+    }
 }
diff --git a/c/sedona-geos/src/st_simplifypreservetopology.rs 
b/c/sedona-geos/src/st_simplifypreservetopology.rs
index 625ce19b..748e01fb 100644
--- a/c/sedona-geos/src/st_simplifypreservetopology.rs
+++ b/c/sedona-geos/src/st_simplifypreservetopology.rs
@@ -23,7 +23,10 @@ use datafusion_common::cast::as_float64_array;
 use datafusion_common::error::Result;
 use datafusion_common::DataFusionError;
 use datafusion_expr::ColumnarValue;
-use sedona_expr::scalar_udf::{ScalarKernelRef, SedonaScalarKernel};
+use sedona_expr::{
+    item_crs::ItemCrsKernel,
+    scalar_udf::{ScalarKernelRef, SedonaScalarKernel},
+};
 use sedona_geometry::wkb_factory::WKB_MIN_PROBABLE_BYTES;
 use sedona_schema::{
     datatypes::{SedonaType, WKB_GEOMETRY},
@@ -34,8 +37,8 @@ use crate::executor::GeosExecutor;
 use crate::geos_to_wkb::write_geos_geometry;
 
 /// ST_SimplifyPreserveTopology() implementation using the geos crate
-pub fn st_simplify_preserve_topology_impl() -> ScalarKernelRef {
-    Arc::new(STSimplifyPreserveTopology {})
+pub fn st_simplify_preserve_topology_impl() -> Vec<ScalarKernelRef> {
+    ItemCrsKernel::wrap_impl(STSimplifyPreserveTopology {})
 }
 
 #[derive(Debug)]
@@ -103,7 +106,7 @@ mod tests {
     use datafusion_common::ScalarValue;
     use rstest::rstest;
     use sedona_expr::scalar_udf::SedonaScalarUDF;
-    use sedona_schema::datatypes::{WKB_GEOMETRY, WKB_VIEW_GEOMETRY};
+    use sedona_schema::datatypes::{WKB_GEOMETRY, WKB_GEOMETRY_ITEM_CRS, 
WKB_VIEW_GEOMETRY};
     use sedona_testing::compare::assert_array_equal;
     use sedona_testing::create::create_array;
     use sedona_testing::testers::ScalarUdfTester;
@@ -226,4 +229,22 @@ mod tests {
             &expected_array,
         );
     }
+
+    #[rstest]
+    fn udf_invoke_item_crs(#[values(WKB_GEOMETRY_ITEM_CRS.clone())] 
sedona_type: SedonaType) {
+        let udf = SedonaScalarUDF::from_impl(
+            "st_simplifypreservetopology",
+            st_simplify_preserve_topology_impl(),
+        );
+        let tester = ScalarUdfTester::new(
+            udf.into(),
+            vec![sedona_type.clone(), SedonaType::Arrow(DataType::Float64)],
+        );
+        tester.assert_return_type(sedona_type);
+
+        let result = tester
+            .invoke_scalar_scalar("LINESTRING(0 0, 0 10, 0 51, 50 20, 30 20, 7 
32)", 2.0)
+            .unwrap();
+        tester.assert_scalar_result_equals(result, "LINESTRING(0 0,0 51,50 
20,30 20,7 32)");
+    }
 }
diff --git a/c/sedona-geos/src/st_snap.rs b/c/sedona-geos/src/st_snap.rs
index 88862b67..731671b5 100644
--- a/c/sedona-geos/src/st_snap.rs
+++ b/c/sedona-geos/src/st_snap.rs
@@ -22,7 +22,10 @@ use arrow_schema::DataType;
 use datafusion_common::{cast::as_float64_array, DataFusionError, Result};
 use datafusion_expr::ColumnarValue;
 use geos::Geom;
-use sedona_expr::scalar_udf::{ScalarKernelRef, SedonaScalarKernel};
+use sedona_expr::{
+    item_crs::ItemCrsKernel,
+    scalar_udf::{ScalarKernelRef, SedonaScalarKernel},
+};
 use sedona_geometry::wkb_factory::WKB_MIN_PROBABLE_BYTES;
 use sedona_schema::{
     datatypes::{SedonaType, WKB_GEOMETRY},
@@ -33,8 +36,8 @@ use crate::executor::GeosExecutor;
 use crate::geos_to_wkb::write_geos_geometry;
 
 /// ST_Snap() implementation using the geos crate
-pub fn st_snap_impl() -> ScalarKernelRef {
-    Arc::new(STSnap {})
+pub fn st_snap_impl() -> Vec<ScalarKernelRef> {
+    ItemCrsKernel::wrap_impl(STSnap {})
 }
 
 #[derive(Debug)]
@@ -115,7 +118,7 @@ fn invoke_scalar(
 mod tests {
     use rstest::rstest;
     use sedona_expr::scalar_udf::SedonaScalarUDF;
-    use sedona_schema::datatypes::{SedonaType, WKB_VIEW_GEOMETRY};
+    use sedona_schema::datatypes::{SedonaType, WKB_GEOMETRY_ITEM_CRS, 
WKB_VIEW_GEOMETRY};
     use sedona_testing::{
         compare::assert_array_equal, create::create_array, 
testers::ScalarUdfTester,
     };
@@ -254,4 +257,23 @@ mod tests {
             &expected_geometries,
         );
     }
+
+    #[rstest]
+    fn udf_invoke_item_crs(#[values(WKB_GEOMETRY_ITEM_CRS.clone())] 
sedona_type: SedonaType) {
+        let udf = SedonaScalarUDF::from_impl("st_snap", st_snap_impl());
+        let tester = ScalarUdfTester::new(
+            udf.into(),
+            vec![
+                sedona_type.clone(),
+                sedona_type.clone(),
+                SedonaType::Arrow(DataType::Float64),
+            ],
+        );
+        tester.assert_return_type(sedona_type);
+
+        let result = tester
+            .invoke_scalar_scalar_scalar("POINT (1.1 2.1)", "POINT (1 2)", 0.5)
+            .unwrap();
+        tester.assert_scalar_result_equals(result, "POINT (1 2)");
+    }
 }
diff --git a/c/sedona-geos/src/st_unaryunion.rs 
b/c/sedona-geos/src/st_unaryunion.rs
index d1cc3d19..ba08f0bf 100644
--- a/c/sedona-geos/src/st_unaryunion.rs
+++ b/c/sedona-geos/src/st_unaryunion.rs
@@ -21,7 +21,10 @@ use arrow_array::builder::BinaryBuilder;
 use datafusion_common::{DataFusionError, Result};
 use datafusion_expr::ColumnarValue;
 use geos::Geom;
-use sedona_expr::scalar_udf::{ScalarKernelRef, SedonaScalarKernel};
+use sedona_expr::{
+    item_crs::ItemCrsKernel,
+    scalar_udf::{ScalarKernelRef, SedonaScalarKernel},
+};
 use sedona_geometry::wkb_factory::WKB_MIN_PROBABLE_BYTES;
 use sedona_schema::datatypes::SedonaType;
 use sedona_schema::{datatypes::WKB_GEOMETRY, matchers::ArgMatcher};
@@ -30,8 +33,8 @@ use crate::executor::GeosExecutor;
 use crate::geos_to_wkb::write_geos_geometry;
 
 /// ST_UnaryUnion() implementation using the geos crate
-pub fn st_unary_union_impl() -> ScalarKernelRef {
-    Arc::new(STUnaryUnion {})
+pub fn st_unary_union_impl() -> Vec<ScalarKernelRef> {
+    ItemCrsKernel::wrap_impl(STUnaryUnion {})
 }
 
 #[derive(Debug)]
@@ -84,7 +87,7 @@ mod tests {
     use datafusion_common::ScalarValue;
     use rstest::rstest;
     use sedona_expr::scalar_udf::SedonaScalarUDF;
-    use sedona_schema::datatypes::{WKB_GEOMETRY, WKB_VIEW_GEOMETRY};
+    use sedona_schema::datatypes::{WKB_GEOMETRY, WKB_GEOMETRY_ITEM_CRS, 
WKB_VIEW_GEOMETRY};
     use sedona_testing::compare::assert_array_equal;
     use sedona_testing::create::create_array;
     use sedona_testing::testers::ScalarUdfTester;
@@ -130,4 +133,18 @@ mod tests {
         );
         assert_array_equal(&tester.invoke_wkb_array(input_wkt).unwrap(), 
&expected);
     }
+
+    #[rstest]
+    fn udf_invoke_item_crs(#[values(WKB_GEOMETRY_ITEM_CRS.clone())] 
sedona_type: SedonaType) {
+        let udf = SedonaScalarUDF::from_impl("st_unary_union", 
st_unary_union_impl());
+        let tester = ScalarUdfTester::new(udf.into(), 
vec![sedona_type.clone()]);
+        tester.assert_return_type(sedona_type);
+
+        let result = tester
+            .invoke_scalar(
+                "MULTIPOLYGON (((0 0, 1 0, 1 1, 0 1, 0 0)), ((1 0, 2 0, 2 1, 1 
1, 1 0)))",
+            )
+            .unwrap();
+        tester.assert_scalar_result_equals(result, "POLYGON ((0 0, 0 1, 1 1, 2 
1, 2 0, 1 0, 0 0))");
+    }
 }

Reply via email to