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(
