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 bfb94e96 feat(c/sedona-geos): Implement ST_MinimumClearanceLine (#316)
bfb94e96 is described below
commit bfb94e962428309550bdbdcde600844fd095cd9d
Author: Abeeujah <[email protected]>
AuthorDate: Tue Nov 18 02:24:00 2025 +0100
feat(c/sedona-geos): Implement ST_MinimumClearanceLine (#316)
---
c/sedona-geos/benches/geos-functions.rs | 3 +
c/sedona-geos/src/lib.rs | 1 +
c/sedona-geos/src/register.rs | 2 +
c/sedona-geos/src/st_minimumclearance_line.rs | 142 ++++++++++++++++++++++
python/sedonadb/tests/functions/test_functions.py | 62 ++++++++++
5 files changed, 210 insertions(+)
diff --git a/c/sedona-geos/benches/geos-functions.rs
b/c/sedona-geos/benches/geos-functions.rs
index 3653ec12..623cd90c 100644
--- a/c/sedona-geos/benches/geos-functions.rs
+++ b/c/sedona-geos/benches/geos-functions.rs
@@ -228,6 +228,9 @@ fn criterion_benchmark(c: &mut Criterion) {
benchmark::scalar(c, &f, "geos", "st_length", LineString(10));
benchmark::scalar(c, &f, "geos", "st_length", LineString(500));
+ benchmark::scalar(c, &f, "geos", "st_minimumclearanceline",
LineString(10));
+ benchmark::scalar(c, &f, "geos", "st_minimumclearanceline",
LineString(500));
+
benchmark::scalar(
c,
&f,
diff --git a/c/sedona-geos/src/lib.rs b/c/sedona-geos/src/lib.rs
index 29ba87d3..254a0e5e 100644
--- a/c/sedona-geos/src/lib.rs
+++ b/c/sedona-geos/src/lib.rs
@@ -32,6 +32,7 @@ mod st_isvalid;
mod st_isvalidreason;
mod st_length;
mod st_makevalid;
+mod st_minimumclearance_line;
mod st_perimeter;
mod st_polygonize_agg;
mod st_reverse;
diff --git a/c/sedona-geos/src/register.rs b/c/sedona-geos/src/register.rs
index 36c6cf6a..80c17fd5 100644
--- a/c/sedona-geos/src/register.rs
+++ b/c/sedona-geos/src/register.rs
@@ -31,6 +31,7 @@ use crate::{
st_isvalidreason::st_is_valid_reason_impl,
st_length::st_length_impl,
st_makevalid::st_make_valid_impl,
+ st_minimumclearance_line::st_minimum_clearance_line_impl,
st_perimeter::st_perimeter_impl,
st_polygonize_agg::st_polygonize_agg_impl,
st_reverse::st_reverse_impl,
@@ -74,6 +75,7 @@ pub fn scalar_kernels() -> Vec<(&'static str,
ScalarKernelRef)> {
("st_isvalidreason", st_is_valid_reason_impl()),
("st_length", st_length_impl()),
("st_makevalid", st_make_valid_impl()),
+ ("st_minimumclearanceline", st_minimum_clearance_line_impl()),
("st_overlaps", st_overlaps_impl()),
("st_perimeter", st_perimeter_impl()),
("st_reverse", st_reverse_impl()),
diff --git a/c/sedona-geos/src/st_minimumclearance_line.rs
b/c/sedona-geos/src/st_minimumclearance_line.rs
new file mode 100644
index 00000000..6af8ceb7
--- /dev/null
+++ b/c/sedona-geos/src/st_minimumclearance_line.rs
@@ -0,0 +1,142 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use std::sync::Arc;
+
+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_geometry::wkb_factory::WKB_MIN_PROBABLE_BYTES;
+use sedona_schema::{
+ datatypes::{SedonaType, WKB_GEOMETRY},
+ matchers::ArgMatcher,
+};
+
+use crate::executor::GeosExecutor;
+
+/// ST_MinimumClearanceLine() implementation using the geos crate
+pub fn st_minimum_clearance_line_impl() -> ScalarKernelRef {
+ Arc::new(STMinimumClearanceLine {})
+}
+
+#[derive(Debug)]
+struct STMinimumClearanceLine {}
+
+impl SedonaScalarKernel for STMinimumClearanceLine {
+ fn return_type(&self, args: &[SedonaType]) -> Result<Option<SedonaType>> {
+ let matcher = ArgMatcher::new(vec![ArgMatcher::is_geometry()],
WKB_GEOMETRY);
+
+ matcher.match_args(args)
+ }
+
+ fn invoke_batch(
+ &self,
+ arg_types: &[SedonaType],
+ args: &[ColumnarValue],
+ ) -> Result<ColumnarValue> {
+ let executor = GeosExecutor::new(arg_types, args);
+ let mut builder = BinaryBuilder::with_capacity(
+ executor.num_iterations(),
+ WKB_MIN_PROBABLE_BYTES * executor.num_iterations(),
+ );
+
+ executor.execute_wkb_void(|maybe_wkb| {
+ match maybe_wkb {
+ Some(wkb) => {
+ invoke_scalar(&wkb, &mut builder)?;
+ builder.append_value([]);
+ }
+ _ => builder.append_null(),
+ }
+
+ Ok(())
+ })?;
+
+ executor.finish(Arc::new(builder.finish()))
+ }
+}
+
+fn invoke_scalar(geos_geom: &geos::Geometry, writer: &mut impl std::io::Write)
-> Result<()> {
+ let geometry = geos_geom.minimum_clearance_line().map_err(|e| {
+ DataFusionError::Execution(format!(
+ "Failed to calculate geometry's minimum clearance line: {e}"
+ ))
+ })?;
+
+ let wkb = geometry
+ .to_wkb()
+ .map_err(|e| DataFusionError::Execution(format!("Failed to convert to
wkb: {e}")))?;
+
+ writer.write_all(wkb.as_ref())?;
+ Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+ use rstest::rstest;
+ use sedona_expr::scalar_udf::SedonaScalarUDF;
+ use sedona_schema::datatypes::WKB_VIEW_GEOMETRY;
+ use sedona_testing::{create::create_array, testers::ScalarUdfTester};
+
+ use super::*;
+
+ #[rstest]
+ fn udf(#[values(WKB_GEOMETRY, WKB_VIEW_GEOMETRY)] sedona_type: SedonaType)
{
+ let udf = SedonaScalarUDF::from_kernel(
+ "st_minimumclearanceline",
+ st_minimum_clearance_line_impl(),
+ );
+ let tester = ScalarUdfTester::new(udf.into(),
vec![sedona_type.clone()]);
+ tester.assert_return_type(WKB_GEOMETRY);
+
+ let input_wkt = vec![
+ None,
+ Some("POLYGON ((0 0, 1 0, 1 1, 0.5 3.2e-4, 0 0))"),
+ Some("MULTIPOLYGON(((26 125, 26 200, 126 200, 126 125, 26 125 ),(
51 150, 101 150, 76 175, 51 150 )),(( 151 100, 151 200, 176 175, 151 100 )))"),
+ Some("LINESTRING (5 107, 54 84, 101 100)"),
+ Some("POLYGON((0 0,0 3,3 3,3 0,0 0),(1 1,1 2,2 2,2 1,1 1))"),
+ Some("POLYGON((0 0,0 1,0 1,1 1,1 0,0 0,0 0))"),
+ Some("LINESTRING (0 0, 1 1, 2 2)"),
+ Some("MULTIPOLYGON(((0.5 0.5,0 0,0 1,0.5 0.5)),((0.5 0.5,1 1,1
0,0.5 0.5)),((2.5 2.5,2 2,2 3,2.5 2.5)),((2.5 2.5,3 3,3 2,2.5 2.5)))"),
+ Some("POINT (1 1)"),
+ Some("GEOMETRYCOLLECTION(POINT(1 1),MULTIPOLYGON(((0 2,1 1,0 0,0
2)),((2 0,1 1,2 2,2 0))))"),
+ Some("POLYGON EMPTY"),
+ Some("POLYGON((0 0,3 0,3 3,2 1,1 3,0 3,0 0))"),
+ ];
+ let expected = create_array(
+ &[
+ None,
+ Some("LINESTRING(0.5 0.00032,0.5 0)"),
+ Some("LINESTRING(76 175,76 150)"),
+ Some("LINESTRING(54 84,101 100)"),
+ Some("LINESTRING(1 1,1 2)"),
+ Some("LINESTRING(0 0,0 1)"),
+ Some("LINESTRING(0 0,1 1)"),
+ Some("LINESTRING(2.5 2.5,3 2.5)"),
+ Some("LINESTRING EMPTY"),
+ Some("LINESTRING(1 1,2 1)"),
+ Some("LINESTRING EMPTY"),
+ Some("LINESTRING(1 3,0 3)"),
+ ],
+ &WKB_GEOMETRY,
+ );
+
+ assert_eq!(&tester.invoke_wkb_array(input_wkt).unwrap(), &expected);
+ }
+}
diff --git a/python/sedonadb/tests/functions/test_functions.py
b/python/sedonadb/tests/functions/test_functions.py
index 8cb1847a..07f9d47a 100644
--- a/python/sedonadb/tests/functions/test_functions.py
+++ b/python/sedonadb/tests/functions/test_functions.py
@@ -2092,6 +2092,68 @@ def test_st_makevalid(eng, geom, expected):
)
[email protected]("eng", [SedonaDB, PostGIS])
[email protected](
+ ("geom", "expected"),
+ [
+ (
+ None,
+ None,
+ ),
+ (
+ "POLYGON ((0 0, 1 0, 1 1, 0.5 3.2e-4, 0 0))",
+ "LINESTRING (0.5 0.00032, 0.5 0)",
+ ),
+ (
+ "MULTIPOLYGON(((26 125, 26 200, 126 200, 126 125, 26 125 ),( 51
150, 101 150, 76 175, 51 150 )),(( 151 100, 151 200, 176 175, 151 100 )))",
+ "LINESTRING (76 175, 76 150)",
+ ),
+ (
+ "LINESTRING (5 107, 54 84, 101 100)",
+ "LINESTRING (54 84, 101 100)",
+ ),
+ (
+ "POLYGON((0 0,0 3,3 3,3 0,0 0),(1 1,1 2,2 2,2 1,1 1))",
+ "LINESTRING (1 1, 1 2)",
+ ),
+ (
+ "POLYGON((0 0,0 1,0 1,1 1,1 0,0 0,0 0))",
+ "LINESTRING (0 0, 0 1)",
+ ),
+ (
+ "LINESTRING (0 0, 1 1, 2 2)",
+ "LINESTRING (0 0, 1 1)",
+ ),
+ (
+ "MULTIPOLYGON(((0.5 0.5,0 0,0 1,0.5 0.5)),((0.5 0.5,1 1,1 0,0.5
0.5)),((2.5 2.5,2 2,2 3,2.5 2.5)),((2.5 2.5,3 3,3 2,2.5 2.5)))",
+ "LINESTRING (2.5 2.5, 3 2.5)",
+ ),
+ (
+ "POINT (1 1)",
+ "LINESTRING EMPTY",
+ ),
+ (
+ "GEOMETRYCOLLECTION(POINT(1 1),MULTIPOLYGON(((0 2,1 1,0 0,0
2)),((2 0,1 1,2 2,2 0))))",
+ "LINESTRING (1 1, 2 1)",
+ ),
+ (
+ "POLYGON EMPTY",
+ "LINESTRING EMPTY",
+ ),
+ (
+ "POLYGON((0 0,3 0,3 3,2 1,1 3,0 3,0 0))",
+ "LINESTRING (1 3, 0 3)",
+ ),
+ ],
+)
+def test_st_minimum_clearance_line(eng, geom, expected):
+ eng = eng.create_or_skip()
+ eng.assert_query_result(
+ f"SELECT ST_MinimumClearanceLine({geom_or_null(geom)})",
+ expected,
+ )
+
+
@pytest.mark.parametrize("eng", [SedonaDB, PostGIS])
@pytest.mark.parametrize(
("geom", "expected"),