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

jiayu pushed a commit to branch SEDONA-564
in repository https://gitbox.apache.org/repos/asf/sedona.git

commit 24a992a4bf59e2dface731f866b632bb7d8a0f90
Author: Furqaan Khan <[email protected]>
AuthorDate: Thu Mar 28 16:13:46 2024 -0400

    [189] Add ST_NumInteriorRing (#138)
    
    * feat: add ST_NumInteriorRing
    
    * feat: add python tests
    
    * docs: remove version number
---
 docs/api/flink/Function.md                         | 20 +++++++++++++++
 docs/api/snowflake/vector-data/Function.md         | 18 ++++++++++++++
 docs/api/sql/Function.md                           | 20 +++++++++++++++
 .../main/java/org/apache/sedona/flink/Catalog.java |  1 +
 .../apache/sedona/flink/expressions/Functions.java |  8 ++++++
 .../java/org/apache/sedona/flink/FunctionTest.java |  7 ++++++
 python/sedona/sql/st_functions.py                  | 11 ++++++++
 python/tests/sql/test_dataframe_api.py             |  2 ++
 python/tests/sql/test_function.py                  | 23 ++++++++++++++++-
 .../sedona/snowflake/snowsql/TestFunctions.java    | 13 ++++++++++
 .../sedona/snowflake/snowsql/TestFunctionsV2.java  | 13 ++++++++++
 .../org/apache/sedona/snowflake/snowsql/UDFs.java  |  7 ++++++
 .../apache/sedona/snowflake/snowsql/UDFsV2.java    |  7 ++++++
 .../scala/org/apache/sedona/sql/UDF/Catalog.scala  |  1 +
 .../sql/sedona_sql/expressions/Functions.scala     |  8 ++++++
 .../sql/sedona_sql/expressions/st_functions.scala  |  3 +++
 .../org/apache/sedona/sql/functionTestScala.scala  | 29 ++++++++++++++++++++++
 17 files changed, 190 insertions(+), 1 deletion(-)

diff --git a/docs/api/flink/Function.md b/docs/api/flink/Function.md
index 5c6b2f6f3..51023b75e 100644
--- a/docs/api/flink/Function.md
+++ b/docs/api/flink/Function.md
@@ -2283,6 +2283,26 @@ Output:
 1
 ```
 
+## ST_NumInteriorRing
+
+Introduction: Returns number of interior rings of polygon geometries. It is an 
alias of [ST_NumInteriorRings](#st_numinteriorrings).
+
+Format: `ST_NumInteriorRing(geom: Geometry)`
+
+Since: `vTBD`
+
+SQL Example
+
+```sql
+SELECT ST_NumInteriorRing(ST_GeomFromText('POLYGON ((0 0, 0 5, 5 5, 5 0, 0 0), 
(1 1, 2 1, 2 2, 1 2, 1 1))'))
+```
+
+Output:
+
+```
+1
+```
+
 ## ST_NumInteriorRings
 
 Introduction: Returns number of interior rings of polygon geometries.
diff --git a/docs/api/snowflake/vector-data/Function.md 
b/docs/api/snowflake/vector-data/Function.md
index 6f02aaed1..eedcc50a2 100644
--- a/docs/api/snowflake/vector-data/Function.md
+++ b/docs/api/snowflake/vector-data/Function.md
@@ -1759,6 +1759,24 @@ SELECT ST_NumGeometries(df.geometry)
 FROM df
 ```
 
+## ST_NumInteriorRing
+
+Introduction: Returns number of interior rings of polygon geometries. It is an 
alias of [ST_NumInteriorRings](#st_numinteriorrings).
+
+Format: `ST_NumInteriorRing(geom: Geometry)`
+
+SQL Example
+
+```sql
+SELECT ST_NumInteriorRing(ST_GeomFromText('POLYGON ((0 0, 0 5, 5 5, 5 0, 0 0), 
(1 1, 2 1, 2 2, 1 2, 1 1))'))
+```
+
+Output:
+
+```
+1
+```
+
 ## ST_NumInteriorRings
 
 Introduction: RETURNS number of interior rings of polygon geometries.
diff --git a/docs/api/sql/Function.md b/docs/api/sql/Function.md
index 814c9c376..8f25489b4 100644
--- a/docs/api/sql/Function.md
+++ b/docs/api/sql/Function.md
@@ -2297,6 +2297,26 @@ Output:
 1
 ```
 
+## ST_NumInteriorRing
+
+Introduction: Returns number of interior rings of polygon geometries. It is an 
alias of [ST_NumInteriorRings](#st_numinteriorrings).
+
+Format: `ST_NumInteriorRing(geom: Geometry)`
+
+Since: `vTBD`
+
+SQL Example
+
+```sql
+SELECT ST_NumInteriorRing(ST_GeomFromText('POLYGON ((0 0, 0 5, 5 5, 5 0, 0 0), 
(1 1, 2 1, 2 2, 1 2, 1 1))'))
+```
+
+Output:
+
+```
+1
+```
+
 ## ST_NumInteriorRings
 
 Introduction: RETURNS number of interior rings of polygon geometries.
diff --git a/flink/src/main/java/org/apache/sedona/flink/Catalog.java 
b/flink/src/main/java/org/apache/sedona/flink/Catalog.java
index efd07c3ca..69e6ce0b3 100644
--- a/flink/src/main/java/org/apache/sedona/flink/Catalog.java
+++ b/flink/src/main/java/org/apache/sedona/flink/Catalog.java
@@ -86,6 +86,7 @@ public class Catalog {
                 new Functions.ST_NPoints(),
                 new Functions.ST_NumGeometries(),
                 new Functions.ST_NumInteriorRings(),
+                new Functions.ST_NumInteriorRing(),
                 new Functions.ST_ExteriorRing(),
                 new Functions.ST_AsEWKT(),
                 new Functions.ST_AsEWKB(),
diff --git 
a/flink/src/main/java/org/apache/sedona/flink/expressions/Functions.java 
b/flink/src/main/java/org/apache/sedona/flink/expressions/Functions.java
index 150c2048a..fe11db120 100644
--- a/flink/src/main/java/org/apache/sedona/flink/expressions/Functions.java
+++ b/flink/src/main/java/org/apache/sedona/flink/expressions/Functions.java
@@ -475,6 +475,14 @@ public class Functions {
         }
     }
 
+    public static class ST_NumInteriorRing extends ScalarFunction {
+        @DataTypeHint("Integer")
+        public Integer eval(@DataTypeHint(value = "RAW", bridgedTo = 
org.locationtech.jts.geom.Geometry.class) Object o) {
+            Geometry geom = (Geometry) o;
+            return org.apache.sedona.common.Functions.numInteriorRings(geom);
+        }
+    }
+
     public static class ST_ExteriorRing extends ScalarFunction {
         @DataTypeHint(value = "RAW", bridgedTo = 
org.locationtech.jts.geom.Geometry.class)
         public Geometry eval(@DataTypeHint(value = "RAW", bridgedTo = 
org.locationtech.jts.geom.Geometry.class) Object o) {
diff --git a/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java 
b/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java
index c8d1a2f2e..7649e0040 100644
--- a/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java
+++ b/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java
@@ -572,6 +572,13 @@ public class FunctionTest extends TestBase{
         assertEquals(2, first(resultTable).getField(0));
     }
 
+    @Test
+    public void testNumInteriorRing() {
+        Table polygonTable = tableEnv.sqlQuery("SELECT 
ST_GeomFromText('POLYGON((7 9,8 7,11 6,15 8,16 6,17 7,17 10,18 12,17 14,15 
15,11 15,10 13,9 12,7 9),(9 9,10 10,11 11,11 10,10 8,9 9),(12 14,15 14,13 11,12 
14))') AS polygon");
+        Table resultTable = 
polygonTable.select(call(Functions.ST_NumInteriorRing.class.getSimpleName(), 
$("polygon")));
+        assertEquals(2, first(resultTable).getField(0));
+    }
+
     @Test
     public void testExteriorRing() {
         Table polygonTable = createPolygonTable(1);
diff --git a/python/sedona/sql/st_functions.py 
b/python/sedona/sql/st_functions.py
index dfd9ae13c..c22b85490 100644
--- a/python/sedona/sql/st_functions.py
+++ b/python/sedona/sql/st_functions.py
@@ -1024,6 +1024,17 @@ def ST_NumInteriorRings(geometry: ColumnOrName) -> 
Column:
     """
     return _call_st_function("ST_NumInteriorRings", geometry)
 
+@validate_argument_types
+def ST_NumInteriorRing(geometry: ColumnOrName) -> Column:
+    """Return the number of interior rings contained in a polygon geometry.
+
+    :param geometry: Polygon geometry column to return for.
+    :type geometry: ColumnOrName
+    :return: Number of interior rings polygons contain as an integer column.
+    :rtype: Column
+    """
+    return _call_st_function("ST_NumInteriorRing", geometry)
+
 
 @validate_argument_types
 def ST_PointN(geometry: ColumnOrName, n: Union[ColumnOrName, int]) -> Column:
diff --git a/python/tests/sql/test_dataframe_api.py 
b/python/tests/sql/test_dataframe_api.py
index 56ec8212a..44265ca6f 100644
--- a/python/tests/sql/test_dataframe_api.py
+++ b/python/tests/sql/test_dataframe_api.py
@@ -155,6 +155,7 @@ test_configurations = [
     (stf.ST_NRings, ("geom",), "square_geom", "", 1),
     (stf.ST_NumGeometries, ("geom",), "multipoint", "", 2),
     (stf.ST_NumInteriorRings, ("geom",), "geom_with_hole", "", 1),
+    (stf.ST_NumInteriorRing, ("geom",), "geom_with_hole", "", 1),
     (stf.ST_NumPoints, ("line",), "linestring_geom", "", 6),
     (stf.ST_PointN, ("line", 2), "linestring_geom", "", "POINT (1 0)"),
     (stf.ST_PointOnSurface, ("line",), "linestring_geom", "", "POINT (2 0)"),
@@ -314,6 +315,7 @@ wrong_type_configurations = [
     (stf.ST_NPoints, (None,)),
     (stf.ST_NumGeometries, (None,)),
     (stf.ST_NumInteriorRings, (None,)),
+    (stf.ST_NumInteriorRing, (None,)),
     (stf.ST_PointN, (None, 2)),
     (stf.ST_PointN, ("", None)),
     (stf.ST_PointN, ("", 2.0)),
diff --git a/python/tests/sql/test_function.py 
b/python/tests/sql/test_function.py
index ea8497132..791a3073b 100644
--- a/python/tests/sql/test_function.py
+++ b/python/tests/sql/test_function.py
@@ -781,7 +781,7 @@ class TestPredicateJoin(TestBase):
         is_closed_collected = [[*row] for row in is_closed]
         assert (is_closed_collected == expected_result)
 
-    def test_num_interior_ring(self):
+    def test_num_interior_rings(self):
         geometries = [
             (1, "Point(21 52)"),
             (2, "Polygon((0 0, 0 1, 1 1, 1 0, 0 0))"),
@@ -802,6 +802,27 @@ class TestPredicateJoin(TestBase):
         collected_interior_rings = [[*row] for row in 
number_of_interior_rings.filter("num is not null").collect()]
         assert (collected_interior_rings == [[2, 0], [11, 1]])
 
+    def test_num_interior_ring(self):
+        geometries = [
+            (1, "Point(21 52)"),
+            (2, "Polygon((0 0, 0 1, 1 1, 1 0, 0 0))"),
+            (3, "Linestring(0 0, 1 1, 1 0)"),
+            (4, "Linestring(0 0, 1 1, 1 0, 0 0)"),
+            (5, "MULTIPOINT ((10 40), (40 30), (20 20), (30 10))"),
+            (6, "MULTIPOLYGON (((30 20, 45 40, 10 40, 30 20)), ((15 5, 40 10, 
10 20, 5 10, 15 5)))"),
+            (7, "MULTILINESTRING ((10 10, 20 20, 10 40, 10 10), (40 40, 30 30, 
40 20, 30 10, 40 40))"),
+            (8, "MULTILINESTRING ((10 10, 20 20, 10 40, 10 10), (40 40, 30 30, 
40 20, 30 10))"),
+            (9, "MULTILINESTRING ((10 10, 20 20, 10 40), (40 40, 30 30, 40 20, 
30 10))"),
+            (10,
+             "GEOMETRYCOLLECTION (POINT (40 10), LINESTRING (10 10, 20 20, 10 
40), POLYGON ((40 40, 20 45, 45 30, 40 40)))"),
+            (11, "POLYGON ((0 0, 0 5, 5 5, 5 0, 0 0), (1 1, 2 1, 2 2, 1 2, 1 
1))")]
+
+        geometry_df = self.__wkt_pair_list_with_index_to_data_frame(geometries)
+
+        number_of_interior_rings = geometry_df.selectExpr("index", 
"ST_NumInteriorRing(geom) as num")
+        collected_interior_rings = [[*row] for row in 
number_of_interior_rings.filter("num is not null").collect()]
+        assert (collected_interior_rings == [[2, 0], [11, 1]])
+
     def test_st_add_point(self):
         geometry = [
             ("Point(21 52)", "Point(21 52)"),
diff --git 
a/snowflake-tester/src/test/java/org/apache/sedona/snowflake/snowsql/TestFunctions.java
 
b/snowflake-tester/src/test/java/org/apache/sedona/snowflake/snowsql/TestFunctions.java
index a0b283156..8c26cc404 100644
--- 
a/snowflake-tester/src/test/java/org/apache/sedona/snowflake/snowsql/TestFunctions.java
+++ 
b/snowflake-tester/src/test/java/org/apache/sedona/snowflake/snowsql/TestFunctions.java
@@ -687,6 +687,19 @@ public class TestFunctions extends TestBase {
         );
 
     }
+
+    @Test
+    public void test_ST_NumInteriorRing() {
+        registerUDF("ST_NumInteriorRing", byte[].class);
+        verifySqlSingleRes(
+                "select 
sedona.ST_NumInteriorRing(sedona.ST_GeomFromText('POLYGON((0 0 0,0 5 0,5 0 0,0 
0 5),(1 1 0,3 1 0,1 3 0,1 1 0))'))",
+                1
+        );
+        verifySqlSingleRes(
+                "select 
sedona.ST_NumInteriorRing(sedona.ST_GeomFromText('POLYGON((0 0, 0 1, 1 1, 1 0, 
0 0))'))",
+                0
+        );
+    }
     @Test
     public void test_ST_PointN() {
         registerUDF("ST_PointN", byte[].class, int.class);
diff --git 
a/snowflake-tester/src/test/java/org/apache/sedona/snowflake/snowsql/TestFunctionsV2.java
 
b/snowflake-tester/src/test/java/org/apache/sedona/snowflake/snowsql/TestFunctionsV2.java
index 3921b4f09..289d40ee3 100644
--- 
a/snowflake-tester/src/test/java/org/apache/sedona/snowflake/snowsql/TestFunctionsV2.java
+++ 
b/snowflake-tester/src/test/java/org/apache/sedona/snowflake/snowsql/TestFunctionsV2.java
@@ -634,6 +634,19 @@ public class TestFunctionsV2
         );
 
     }
+
+    @Test
+    public void test_ST_NumInteriorRing() {
+        registerUDFV2("ST_NumInteriorRing", String.class);
+        verifySqlSingleRes(
+                "select 
sedona.ST_NumInteriorRing(ST_GeometryFromWKT('POLYGON((0 0,0 5,5 0,0 0),(1 1,3 
1,1 3,1 1))'))",
+                1
+        );
+        verifySqlSingleRes(
+                "select 
sedona.ST_NumInteriorRing(ST_GeometryFromWKT('POLYGON((0 0, 0 1, 1 1, 1 0, 0 
0))'))",
+                0
+        );
+    }
     @Test
     public void test_ST_PointN() {
         registerUDFV2("ST_PointN", String.class, int.class);
diff --git 
a/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/UDFs.java 
b/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/UDFs.java
index bad3f0c89..fa4de5603 100644
--- a/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/UDFs.java
+++ b/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/UDFs.java
@@ -875,6 +875,13 @@ public class UDFs {
         );
     }
 
+    @UDFAnnotations.ParamMeta(argNames = {"geometry"})
+    public static Integer ST_NumInteriorRing(byte[] geometry) {
+        return Functions.numInteriorRings(
+                GeometrySerde.deserialize(geometry)
+        );
+    }
+
     @UDFAnnotations.ParamMeta(argNames = {"leftGeometry", "rightGeometry"})
     public static boolean ST_OrderingEquals(byte[] leftGeometry, byte[] 
rightGeometry) {
         return Predicates.orderingEquals(
diff --git 
a/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/UDFsV2.java 
b/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/UDFsV2.java
index 2c6ed7907..da0e56027 100644
--- a/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/UDFsV2.java
+++ b/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/UDFsV2.java
@@ -766,6 +766,13 @@ public class UDFsV2
         );
     }
 
+    @UDFAnnotations.ParamMeta(argNames = {"geometry"}, argTypes = {"Geometry"})
+    public static Integer ST_NumInteriorRing(String geometry) {
+        return Functions.numInteriorRings(
+                GeometrySerde.deserGeoJson(geometry)
+        );
+    }
+
     @UDFAnnotations.ParamMeta(argNames = {"leftGeometry", "rightGeometry"}, 
argTypes = {"Geometry", "Geometry"})
     public static boolean ST_OrderingEquals(String leftGeometry, String 
rightGeometry) {
         return Predicates.orderingEquals(
diff --git 
a/spark/common/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala 
b/spark/common/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala
index bd2e801ad..0384d96d5 100644
--- a/spark/common/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala
+++ b/spark/common/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala
@@ -123,6 +123,7 @@ object Catalog {
     function[ST_IsClosed](),
     function[ST_IsCollection](),
     function[ST_NumInteriorRings](),
+    function[ST_NumInteriorRing](),
     function[ST_AddPoint](-1),
     function[ST_RemovePoint](-1),
     function[ST_SetPoint](),
diff --git 
a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala
 
b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala
index 4b9c09c03..f734c99de 100644
--- 
a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala
+++ 
b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala
@@ -621,6 +621,14 @@ case class ST_NumInteriorRings(inputExpressions: 
Seq[Expression])
   }
 }
 
+case class ST_NumInteriorRing(inputExpressions: Seq[Expression])
+  extends InferredExpression(Functions.numInteriorRings _) {
+
+  protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = 
{
+    copy(inputExpressions = newChildren)
+  }
+}
+
 case class ST_AddPoint(inputExpressions: Seq[Expression])
   extends InferredExpression(inferrableFunction3(Functions.addPoint)) {
 
diff --git 
a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_functions.scala
 
b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_functions.scala
index b78917303..4ec1885e8 100644
--- 
a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_functions.scala
+++ 
b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_functions.scala
@@ -265,6 +265,9 @@ object st_functions extends DataFrameAPI {
   def ST_NumInteriorRings(geometry: Column): Column = 
wrapExpression[ST_NumInteriorRings](geometry)
   def ST_NumInteriorRings(geometry: String): Column = 
wrapExpression[ST_NumInteriorRings](geometry)
 
+  def ST_NumInteriorRing(geometry: Column): Column = 
wrapExpression[ST_NumInteriorRing](geometry)
+  def ST_NumInteriorRing(geometry: String): Column = 
wrapExpression[ST_NumInteriorRing](geometry)
+
   def ST_PointN(geometry: Column, n: Column): Column = 
wrapExpression[ST_PointN](geometry, n)
   def ST_PointN(geometry: String, n: Int): Column = 
wrapExpression[ST_PointN](geometry, n)
 
diff --git 
a/spark/common/src/test/scala/org/apache/sedona/sql/functionTestScala.scala 
b/spark/common/src/test/scala/org/apache/sedona/sql/functionTestScala.scala
index 3fee1e53c..807513951 100644
--- a/spark/common/src/test/scala/org/apache/sedona/sql/functionTestScala.scala
+++ b/spark/common/src/test/scala/org/apache/sedona/sql/functionTestScala.scala
@@ -1191,6 +1191,35 @@ class functionTestScala extends TestBaseScala with 
Matchers with GeometrySample
       .collect().toList should contain theSameElementsAs List((2, 0), (11, 1))
   }
 
+  it("Should pass ST_NumInteriorRing") {
+    Given("Geometry DataFrame")
+    val geometryDf = Seq(
+      (1, "Point(21 52)"),
+      (2, "Polygon((0 0, 0 1, 1 1, 1 0, 0 0))"),
+      (3, "Linestring(0 0, 1 1, 1 0)"),
+      (4, "Linestring(0 0, 1 1, 1 0, 0 0)"),
+      (5, "MULTIPOINT ((10 40), (40 30), (20 20), (30 10))"),
+      (6, "MULTIPOLYGON (((30 20, 45 40, 10 40, 30 20)), ((15 5, 40 10, 10 20, 
5 10, 15 5)))"),
+      (7, "MULTILINESTRING ((10 10, 20 20, 10 40, 10 10), (40 40, 30 30, 40 
20, 30 10, 40 40))"),
+      (8, "MULTILINESTRING ((10 10, 20 20, 10 40, 10 10), (40 40, 30 30, 40 
20, 30 10))"),
+      (9, "MULTILINESTRING ((10 10, 20 20, 10 40), (40 40, 30 30, 40 20, 30 
10))"),
+      (10, "GEOMETRYCOLLECTION (POINT (40 10), LINESTRING (10 10, 20 20, 10 
40), POLYGON ((40 40, 20 45, 45 30, 40 40)))"),
+      (11, "POLYGON ((0 0, 0 5, 5 5, 5 0, 0 0), (1 1, 2 1, 2 2, 1 2, 1 1))")
+    ).map({ case (index, wkt) => Tuple2(index, wktReader.read(wkt)) 
}).toDF("id", "geom")
+
+    When("Using ST_NumInteriorRing")
+    val numberOfInteriorRings = geometryDf.selectExpr(
+      "id", "ST_NumInteriorRing(geom) as num"
+    )
+
+    Then("Result should match with expected values")
+
+    numberOfInteriorRings
+      .filter("num is not null")
+      .as[(Int, Int)]
+      .collect().toList should contain theSameElementsAs List((2, 0), (11, 1))
+  }
+
   it("Should pass ST_AddPoint") {
     Given("Geometry df")
     val geometryDf = Seq(

Reply via email to