This is an automated email from the ASF dual-hosted git repository.

petern 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 21464d70 feat(c/sedona-geos): Implement ST_NumPoints (#386)
21464d70 is described below

commit 21464d70558b7ed295a64900eb03552fc3464798
Author: L_Sowmya <[email protected]>
AuthorDate: Sun Nov 30 11:03:18 2025 +0530

    feat(c/sedona-geos): Implement ST_NumPoints (#386)
    
    Co-authored-by: Peter Nguyen <[email protected]>
---
 c/sedona-geos/src/lib.rs                          |   1 +
 c/sedona-geos/src/register.rs                     |   2 +
 c/sedona-geos/src/st_numpoints.rs                 | 134 ++++++++++++++++++++++
 python/sedonadb/tests/functions/test_functions.py |  32 ++++++
 4 files changed, 169 insertions(+)

diff --git a/c/sedona-geos/src/lib.rs b/c/sedona-geos/src/lib.rs
index 10a2f127..a00bd69d 100644
--- a/c/sedona-geos/src/lib.rs
+++ b/c/sedona-geos/src/lib.rs
@@ -35,6 +35,7 @@ mod st_makevalid;
 mod st_minimumclearance;
 mod st_minimumclearance_line;
 mod st_numinteriorrings;
+mod st_numpoints;
 mod st_perimeter;
 mod st_polygonize;
 mod st_polygonize_agg;
diff --git a/c/sedona-geos/src/register.rs b/c/sedona-geos/src/register.rs
index 673996f3..76f63ce6 100644
--- a/c/sedona-geos/src/register.rs
+++ b/c/sedona-geos/src/register.rs
@@ -34,6 +34,7 @@ use crate::{
     st_minimumclearance::st_minimum_clearance_impl,
     st_minimumclearance_line::st_minimum_clearance_line_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,
@@ -77,6 +78,7 @@ pub fn scalar_kernels() -> Vec<(&'static str, 
ScalarKernelRef)> {
         ("st_isvalidreason", st_is_valid_reason_impl()),
         ("st_length", st_length_impl()),
         ("st_numinteriorrings", st_num_interior_rings_impl()),
+        ("st_numpoints", st_num_points_impl()),
         ("st_makevalid", st_make_valid_impl()),
         ("st_minimumclearance", st_minimum_clearance_impl()),
         ("st_minimumclearanceline", st_minimum_clearance_line_impl()),
diff --git a/c/sedona-geos/src/st_numpoints.rs 
b/c/sedona-geos/src/st_numpoints.rs
new file mode 100644
index 00000000..2a293967
--- /dev/null
+++ b/c/sedona-geos/src/st_numpoints.rs
@@ -0,0 +1,134 @@
+// 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 crate::executor::GeosExecutor;
+use arrow_array::builder::Int32Builder;
+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_schema::{datatypes::SedonaType, matchers::ArgMatcher};
+
+pub fn st_num_points_impl() -> ScalarKernelRef {
+    Arc::new(STNumPoints {})
+}
+
+#[derive(Debug)]
+struct STNumPoints {}
+
+impl SedonaScalarKernel for STNumPoints {
+    fn return_type(&self, args: &[SedonaType]) -> Result<Option<SedonaType>> {
+        let matcher = ArgMatcher::new(
+            vec![ArgMatcher::is_geometry()],
+            SedonaType::Arrow(DataType::Int32),
+        );
+
+        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 = 
Int32Builder::with_capacity(executor.num_iterations());
+        executor.execute_wkb_void(|maybe_geom| {
+            match maybe_geom {
+                None => builder.append_null(),
+                Some(geom) => {
+                    let res = invoke_scalar(&geom)?;
+                    match res {
+                        Some(n) => builder.append_value(n),
+                        None => builder.append_null(),
+                    }
+                }
+            }
+            Ok(())
+        })?;
+
+        executor.finish(Arc::new(builder.finish()))
+    }
+}
+
+fn invoke_scalar(geom: &Geometry) -> Result<Option<i32>> {
+    match geom.geometry_type() {
+        GeometryTypes::LineString => {
+            let count = geom.get_num_points().map_err(|e| {
+                DataFusionError::Execution(format!("Failed to get num points: 
{e}"))
+            })?;
+            Ok(Some(count as i32))
+        }
+        _ => Ok(None),
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use std::sync::Arc;
+
+    use arrow_array::{ArrayRef, Int32Array};
+    use arrow_schema::DataType;
+    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_testing::compare::assert_array_equal;
+    use sedona_testing::testers::ScalarUdfTester;
+
+    use super::*;
+
+    #[rstest]
+    fn udf(#[values(WKB_GEOMETRY, WKB_VIEW_GEOMETRY)] sedona_type: SedonaType) 
{
+        let udf = SedonaScalarUDF::from_kernel("st_numpoints", 
st_num_points_impl());
+        let tester = ScalarUdfTester::new(udf.into(), vec![sedona_type]);
+        tester.assert_return_type(DataType::Int32);
+        let result = tester.invoke_scalar("LINESTRING (1 2, 3 4)").unwrap();
+        tester.assert_scalar_result_equals(result, 2_i32);
+
+        let result = tester.invoke_scalar(ScalarValue::Null).unwrap();
+        assert!(result.is_null());
+
+        let input_wkt = vec![
+            None,
+            Some("POINT (1 2)"),
+            Some("LINESTRING (0 0, 1 1, 2 2)"),
+            Some("POLYGON EMPTY"),
+            Some("LINESTRING(0 0, 1 1, 1 2, 2 2)"),
+            Some("MULTILINESTRING ((0 0, 1 1), (2 2, 3 3))"),
+            Some("POLYGON ((0 0, 4 0, 4 4, 0 4, 0 0))"),
+            Some("GEOMETRYCOLLECTION (POINT (1 2), LINESTRING (0 0, 1 1))"),
+        ];
+
+        let expected: ArrayRef = Arc::new(Int32Array::from(vec![
+            None,
+            None,
+            Some(3),
+            None,
+            Some(4),
+            None,
+            None,
+            None,
+        ]));
+
+        let result = tester.invoke_wkb_array(input_wkt).unwrap();
+        assert_array_equal(&result, &expected);
+    }
+}
diff --git a/python/sedonadb/tests/functions/test_functions.py 
b/python/sedonadb/tests/functions/test_functions.py
index 3344ed2f..e956eaff 100644
--- a/python/sedonadb/tests/functions/test_functions.py
+++ b/python/sedonadb/tests/functions/test_functions.py
@@ -2742,3 +2742,35 @@ def test_st_numinteriorrings_basic(eng, geom, expected):
         f"SELECT ST_NumInteriorRings({geom_or_null(geom)})",
         expected,
     )
+
+
[email protected]("eng", [SedonaDB, PostGIS])
[email protected](
+    ("geom", "expected"),
+    [
+        (None, None),
+        ("POINT EMPTY", None),
+        ("LINESTRING EMPTY", 0),
+        ("POLYGON EMPTY", None),
+        ("MULTIPOINT EMPTY", None),
+        ("MULTILINESTRING EMPTY", None),
+        ("MULTIPOLYGON EMPTY", None),
+        ("GEOMETRYCOLLECTION EMPTY", None),
+        ("POINT (1 2)", None),
+        ("LINESTRING (0 0, 1 1, 2 2)", 3),
+        ("LINESTRING (0 0, 1 1, 0 0)", 3),
+        ("LINESTRING Z (0 0 0, 1 1 1, 2 2 2, 3 3 3)", 4),
+        ("LINESTRING M (0 0 0, 1 1 1, 2 2 2, 3 3 3)", 4),
+        ("LINESTRING ZM (0 0 0 2, 1 1 1 4)", 2),
+        ("POLYGON ((0 0, 4 0, 4 4, 0 4, 0 0))", None),
+        ("MULTILINESTRING ((0 0, 0 1, 1 1, 0 0),(0 0, 1 1))", None),
+        ("GEOMETRYCOLLECTION (LINESTRING (0 0, 0 1, 1 1, 0 0))", None),
+        ("POLYGON ((0 0,6 0,6 6,0 6,0 0),(2 2,4 2,4 4,2 4,2 2))", None),
+    ],
+)
+def test_st_numpoints(eng, geom, expected):
+    eng = eng.create_or_skip()
+    eng.assert_query_result(
+        f"SELECT ST_NumPoints({geom_or_null(geom)})",
+        expected,
+    )

Reply via email to