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 5493b8e  feat: Implement ST_Crosses and ST_Overlaps predicates (#204)
5493b8e is described below

commit 5493b8e48641a5f48d32a56b1c7aaeb0a20d8bb5
Author: Abeeujah <[email protected]>
AuthorDate: Sun Oct 12 03:55:28 2025 +0100

    feat: Implement ST_Crosses and ST_Overlaps predicates (#204)
---
 c/sedona-geos/benches/geos-functions.rs            | 28 +++++++
 c/sedona-geos/src/binary_predicates.rs             | 89 +++++++++++++++++++++-
 c/sedona-geos/src/geos.rs                          | 18 +++++
 c/sedona-geos/src/register.rs                      |  6 +-
 python/sedonadb/tests/functions/test_predicates.py | 85 +++++++++++++++++++++
 5 files changed, 222 insertions(+), 4 deletions(-)

diff --git a/c/sedona-geos/benches/geos-functions.rs 
b/c/sedona-geos/benches/geos-functions.rs
index 74152c5..34382bd 100644
--- a/c/sedona-geos/benches/geos-functions.rs
+++ b/c/sedona-geos/benches/geos-functions.rs
@@ -267,6 +267,34 @@ fn criterion_benchmark(c: &mut Criterion) {
         "st_within",
         ArrayScalar(Polygon(10), Polygon(500)),
     );
+    benchmark::scalar(
+        c,
+        &f,
+        "geos",
+        "st_crosses",
+        ArrayScalar(Polygon(10), Polygon(10)),
+    );
+    benchmark::scalar(
+        c,
+        &f,
+        "geos",
+        "st_crosses",
+        ArrayScalar(Polygon(10), Polygon(500)),
+    );
+    benchmark::scalar(
+        c,
+        &f,
+        "geos",
+        "st_overlaps",
+        ArrayScalar(Polygon(10), Polygon(10)),
+    );
+    benchmark::scalar(
+        c,
+        &f,
+        "geos",
+        "st_overlaps",
+        ArrayScalar(Polygon(10), Polygon(500)),
+    );
 }
 
 criterion_group!(benches, criterion_benchmark);
diff --git a/c/sedona-geos/src/binary_predicates.rs 
b/c/sedona-geos/src/binary_predicates.rs
index d66a09b..90ece9b 100644
--- a/c/sedona-geos/src/binary_predicates.rs
+++ b/c/sedona-geos/src/binary_predicates.rs
@@ -17,8 +17,8 @@
 use std::sync::Arc;
 
 use crate::geos::{
-    BinaryPredicate, Contains, CoveredBy, Covers, Disjoint, Equals, 
GeosPredicate, Intersects,
-    Touches, Within,
+    BinaryPredicate, Contains, CoveredBy, Covers, Crosses, Disjoint, Equals, 
GeosPredicate,
+    Intersects, Overlaps, Touches, Within,
 };
 use arrow_array::builder::BooleanBuilder;
 use arrow_schema::DataType;
@@ -61,6 +61,14 @@ pub fn st_within_impl() -> ScalarKernelRef {
     Arc::new(GeosPredicate::<Within>::default())
 }
 
+pub fn st_crosses_impl() -> ScalarKernelRef {
+    Arc::new(GeosPredicate::<Crosses>::default())
+}
+
+pub fn st_overlaps_impl() -> ScalarKernelRef {
+    Arc::new(GeosPredicate::<Overlaps>::default())
+}
+
 impl<Op: BinaryPredicate> SedonaScalarKernel for GeosPredicate<Op> {
     fn return_type(&self, args: &[SedonaType]) -> Result<Option<SedonaType>> {
         let matcher: ArgMatcher = ArgMatcher::new(
@@ -377,4 +385,81 @@ mod tests {
         let expected: ArrayRef = arrow_array!(Boolean, [Some(true), 
Some(false), None]);
         assert_array_equal(&tester.invoke_array_array(arg1, arg2).unwrap(), 
&expected);
     }
+
+    #[rstest]
+    fn crosses_udf(#[values(WKB_GEOMETRY, WKB_VIEW_GEOMETRY)] sedona_type: 
SedonaType) {
+        use datafusion_common::ScalarValue;
+        let udf = SedonaScalarUDF::from_kernel("st_crosses", 
st_crosses_impl());
+        let tester = ScalarUdfTester::new(udf.into(), 
vec![sedona_type.clone(), sedona_type]);
+        tester.assert_return_type(DataType::Boolean);
+
+        let result = tester
+            .invoke_scalar_scalar("LINESTRING (0 0, 1 1)", "LINESTRING (0 1, 1 
0)")
+            .unwrap();
+        tester.assert_scalar_result_equals(result, true);
+
+        let result = tester
+            .invoke_scalar_scalar(ScalarValue::Null, ScalarValue::Null)
+            .unwrap();
+        assert!(result.is_null());
+
+        let arg1 = create_array(
+            &[
+                Some("LINESTRING (0 0, 1 1)"),
+                Some("LINESTRING (0 0, 1 0)"),
+                None,
+            ],
+            &WKB_GEOMETRY,
+        );
+        let arg2 = create_array(
+            &[
+                Some("LINESTRING (0 1, 1 0)"),
+                Some("POLYGON ((2 2, 2 3, 3 3, 3 2, 2 2))"),
+                Some("LINESTRING (0 0, 1 1)"),
+            ],
+            &WKB_GEOMETRY,
+        );
+        let expected: ArrayRef = arrow_array!(Boolean, [Some(true), 
Some(false), None]);
+        assert_array_equal(&tester.invoke_array_array(arg1, arg2).unwrap(), 
&expected);
+    }
+
+    #[rstest]
+    fn overlaps_udf(#[values(WKB_GEOMETRY, WKB_VIEW_GEOMETRY)] sedona_type: 
SedonaType) {
+        use datafusion_common::ScalarValue;
+        let udf = SedonaScalarUDF::from_kernel("st_overlaps", 
st_overlaps_impl());
+        let tester = ScalarUdfTester::new(udf.into(), 
vec![sedona_type.clone(), sedona_type]);
+        tester.assert_return_type(DataType::Boolean);
+
+        let result = tester
+            .invoke_scalar_scalar(
+                "POLYGON ((0 0, 0 2, 2 2, 2 0, 0 0))",
+                "POLYGON ((1 1, 1 3, 3 3, 3 1, 1 1))",
+            )
+            .unwrap();
+        tester.assert_scalar_result_equals(result, true);
+
+        let result = tester
+            .invoke_scalar_scalar(ScalarValue::Null, ScalarValue::Null)
+            .unwrap();
+        assert!(result.is_null());
+
+        let arg1 = create_array(
+            &[
+                Some("POLYGON ((0 0, 0 2, 2 2, 2 0, 0 0))"),
+                Some("LINESTRING (0 0, 2 0)"),
+                None,
+            ],
+            &WKB_GEOMETRY,
+        );
+        let arg2 = create_array(
+            &[
+                Some("POLYGON ((1 1, 1 3, 3 3, 3 1, 1 1))"),
+                Some("LINESTRING (2 0, 3 0)"),
+                Some("POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))"),
+            ],
+            &WKB_GEOMETRY,
+        );
+        let expected: ArrayRef = arrow_array!(Boolean, [Some(true), 
Some(false), None]);
+        assert_array_equal(&tester.invoke_array_array(arg1, arg2).unwrap(), 
&expected);
+    }
 }
diff --git a/c/sedona-geos/src/geos.rs b/c/sedona-geos/src/geos.rs
index 6a96dd1..eeaed35 100644
--- a/c/sedona-geos/src/geos.rs
+++ b/c/sedona-geos/src/geos.rs
@@ -96,3 +96,21 @@ impl BinaryPredicate for Touches {
         lhs.touches(rhs)
     }
 }
+
+/// Check if the geometries cross
+#[derive(Debug, Default)]
+pub struct Crosses {}
+impl BinaryPredicate for Crosses {
+    fn evaluate(lhs: &Geometry, rhs: &Geometry) -> GResult<bool> {
+        lhs.crosses(rhs)
+    }
+}
+
+/// Check if the geometries overlap
+#[derive(Debug, Default)]
+pub struct Overlaps {}
+impl BinaryPredicate for Overlaps {
+    fn evaluate(lhs: &Geometry, rhs: &Geometry) -> GResult<bool> {
+        lhs.overlaps(rhs)
+    }
+}
diff --git a/c/sedona-geos/src/register.rs b/c/sedona-geos/src/register.rs
index 15e18c6..f349f7e 100644
--- a/c/sedona-geos/src/register.rs
+++ b/c/sedona-geos/src/register.rs
@@ -24,8 +24,8 @@ use crate::{
 };
 
 use crate::binary_predicates::{
-    st_contains_impl, st_covered_by_impl, st_covers_impl, st_disjoint_impl, 
st_equals_impl,
-    st_intersects_impl, st_touches_impl, st_within_impl,
+    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,
 };
 
 use crate::overlay::{
@@ -54,5 +54,7 @@ pub fn scalar_kernels() -> Vec<(&'static str, 
ScalarKernelRef)> {
         ("st_touches", st_touches_impl()),
         ("st_union", st_union_impl()),
         ("st_within", st_within_impl()),
+        ("st_crosses", st_crosses_impl()),
+        ("st_overlaps", st_overlaps_impl()),
     ]
 }
diff --git a/python/sedonadb/tests/functions/test_predicates.py 
b/python/sedonadb/tests/functions/test_predicates.py
index 77b3f03..9760ddc 100644
--- a/python/sedonadb/tests/functions/test_predicates.py
+++ b/python/sedonadb/tests/functions/test_predicates.py
@@ -357,3 +357,88 @@ def test_st_within_skipped(eng, geom1, geom2, expected):
         f"SELECT ST_Within({geom_or_null(geom1)}, {geom_or_null(geom2)})",
         expected,
     )
+
+
[email protected]("eng", [SedonaDB, PostGIS])
[email protected](
+    ("geom1", "geom2", "expected"),
+    [
+        (None, None, None),
+        ("POINT (0 0)", None, None),
+        (None, "POINT (0 0)", None),
+        ("POINT (0 0)", "POINT EMPTY", False),
+        ("POINT (0 0)", "POINT (0 0)", False),
+        ("POINT (0.5 0.5)", "LINESTRING (0 0, 1 1)", False),
+        ("POINT (0 0)", "LINESTRING (0 0, 1 1)", False),
+        ("POINT (0.5 0.5)", "POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))", False),
+        ("POINT (0 0)", "POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))", False),
+        ("LINESTRING (0 0, 1 1)", "LINESTRING (0 1, 1 0)", True),
+        ("LINESTRING (0 0, 1 1)", "LINESTRING (1 1, 2 2)", False),
+        ("LINESTRING (0 0, 2 2)", "LINESTRING (1 1, 3 3)", False),
+        ("LINESTRING (-1 -1, 1 1)", "POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))", 
True),
+        ("LINESTRING (-1 0, 0 0)", "POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))", 
False),
+        (
+            "LINESTRING (0.1 0.1, 0.5 0.5)",
+            "POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))",
+            False,
+        ),
+        (
+            "POLYGON ((0 0, 2 0, 2 2, 0 2, 0 0))",
+            "POLYGON ((1 1, 3 1, 3 3, 1 3, 1 1))",
+            False,
+        ),
+    ],
+)
+def test_st_crosses(eng, geom1, geom2, expected):
+    eng = eng.create_or_skip()
+    eng.assert_query_result(
+        f"SELECT ST_Crosses({geom_or_null(geom1)}, {geom_or_null(geom2)})",
+        expected,
+    )
+
+
[email protected]("eng", [SedonaDB, PostGIS])
[email protected](
+    ("geom1", "geom2", "expected"),
+    [
+        (None, None, None),
+        ("POINT (0 0)", None, None),
+        (None, "POINT (0 0)", None),
+        ("POINT (0 0)", "POINT EMPTY", False),
+        ("POINT (0 0)", "LINESTRING (0 0, 1 1)", False),
+        ("LINESTRING (0 0, 2 2)", "POLYGON ((1 1, 3 1, 3 3, 1 3, 1 1))", 
False),
+        ("MULTIPOINT ((0 0), (1 1))", "MULTIPOINT ((1 1), (2 2))", True),
+        ("MULTIPOINT ((0 0), (1 1))", "MULTIPOINT ((0 0), (1 1))", False),
+        ("POINT (0 0)", "POINT (0 0)", False),
+        ("LINESTRING (0 0, 2 2)", "LINESTRING (1 1, 3 3)", True),
+        ("LINESTRING (0 0, 1 1)", "LINESTRING (0 1, 1 0)", False),
+        ("LINESTRING (0 0, 1 1)", "LINESTRING (1 1, 2 2)", False),
+        ("LINESTRING (0 0, 1 1)", "LINESTRING (0 0, 1 1)", False),
+        (
+            "POLYGON ((0 0, 2 0, 2 2, 0 2, 0 0))",
+            "POLYGON ((1 1, 3 1, 3 3, 1 3, 1 1))",
+            True,
+        ),
+        (
+            "POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))",
+            "POLYGON ((1 0, 2 0, 2 1, 1 1, 1 0))",
+            False,
+        ),
+        (
+            "POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))",
+            "POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))",
+            False,
+        ),
+        (
+            "POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0))",
+            "POLYGON ((1 1, 2 1, 2 2, 1 2, 1 1))",
+            False,
+        ),
+    ],
+)
+def test_st_overlaps(eng, geom1, geom2, expected):
+    eng = eng.create_or_skip()
+    eng.assert_query_result(
+        f"SELECT ST_Overlaps({geom_or_null(geom1)}, {geom_or_null(geom2)})",
+        expected,
+    )

Reply via email to