This is an automated email from the ASF dual-hosted git repository. jiayu pushed a commit to branch SEDONA-596 in repository https://gitbox.apache.org/repos/asf/sedona.git
commit 4a5e1047ff2f40459e55c54632264b4817c763a8 Author: Furqaan Khan <[email protected]> AuthorDate: Thu May 9 00:58:46 2024 -0400 [TASK-140] Add ST_SimplifyPolygonHull (#187) --- .../java/org/apache/sedona/common/Functions.java | 9 ++++ .../org/apache/sedona/common/FunctionsTest.java | 34 +++++++++++++++ docs/api/flink/Function.md | 50 ++++++++++++++++++++++ docs/api/snowflake/vector-data/Function.md | 48 +++++++++++++++++++++ docs/api/sql/Function.md | 50 ++++++++++++++++++++++ .../main/java/org/apache/sedona/flink/Catalog.java | 1 + .../apache/sedona/flink/expressions/Functions.java | 17 ++++++++ .../java/org/apache/sedona/flink/FunctionTest.java | 12 ++++++ python/sedona/sql/st_functions.py | 15 +++++++ python/tests/sql/test_dataframe_api.py | 6 +++ python/tests/sql/test_function.py | 10 +++++ .../sedona/snowflake/snowsql/TestFunctions.java | 14 ++++++ .../sedona/snowflake/snowsql/TestFunctionsV2.java | 14 ++++++ .../org/apache/sedona/snowflake/snowsql/UDFs.java | 21 +++++++++ .../apache/sedona/snowflake/snowsql/UDFsV2.java | 21 +++++++++ .../scala/org/apache/sedona/sql/UDF/Catalog.scala | 1 + .../sql/sedona_sql/expressions/Functions.scala | 8 ++++ .../sql/sedona_sql/expressions/st_functions.scala | 4 ++ .../apache/sedona/sql/dataFrameAPITestScala.scala | 11 +++++ .../org/apache/sedona/sql/functionTestScala.scala | 10 +++++ 20 files changed, 356 insertions(+) diff --git a/common/src/main/java/org/apache/sedona/common/Functions.java b/common/src/main/java/org/apache/sedona/common/Functions.java index c8895442e..161d862db 100644 --- a/common/src/main/java/org/apache/sedona/common/Functions.java +++ b/common/src/main/java/org/apache/sedona/common/Functions.java @@ -43,6 +43,7 @@ import org.locationtech.jts.operation.valid.IsSimpleOp; import org.locationtech.jts.operation.valid.IsValidOp; import org.locationtech.jts.operation.valid.TopologyValidationError; import org.locationtech.jts.precision.GeometryPrecisionReducer; +import org.locationtech.jts.simplify.PolygonHullSimplifier; import org.locationtech.jts.simplify.TopologyPreservingSimplifier; import org.locationtech.jts.simplify.VWSimplifier; import org.locationtech.jts.triangulate.polygon.ConstrainedDelaunayTriangulator; @@ -1251,6 +1252,14 @@ public class Functions { return VWSimplifier.simplify(geometry, tolerance); } + public static Geometry simplifyPolygonHull(Geometry geometry, double vertexFactor, boolean isOuter) { + return PolygonHullSimplifier.hull(geometry, isOuter, vertexFactor); + } + + public static Geometry simplifyPolygonHull(Geometry geometry, double vertexFactor) { + return simplifyPolygonHull(geometry, vertexFactor, true); + } + public static String geometryType(Geometry geometry) { return "ST_" + geometry.getGeometryType(); } diff --git a/common/src/test/java/org/apache/sedona/common/FunctionsTest.java b/common/src/test/java/org/apache/sedona/common/FunctionsTest.java index 99e93f151..8915c2cab 100644 --- a/common/src/test/java/org/apache/sedona/common/FunctionsTest.java +++ b/common/src/test/java/org/apache/sedona/common/FunctionsTest.java @@ -1331,6 +1331,40 @@ public class FunctionsTest extends TestBase { assertEquals(expected, actual); } + @Test + public void simplifyPolygonHull() throws ParseException { + Geometry geom = Constructors.geomFromEWKT("POLYGON ((131 158, 136 163, 161 165, 173 156, 179 148, 169 140, 186 144, 190 137, 185 131, 174 128, 174 124, 166 119, 158 121, 158 115, 165 107, 161 97, 166 88, 166 79, 158 57, 145 57, 112 53, 111 47, 93 43, 90 48, 88 40, 80 39, 68 32, 51 33, 40 31, 39 34, 49 38, 34 38, 25 34, 28 39, 36 40, 44 46, 24 41, 17 41, 14 46, 19 50, 33 54, 21 55, 13 52, 11 57, 22 60, 34 59, 41 68, 75 72, 62 77, 56 70, 46 72, 31 69, 46 76, 52 82, 47 84, 56 90, 66 [...] + String actual = Functions.asWKT(Functions.simplifyPolygonHull(geom, 0.3, true)); + String expected = "POLYGON ((161 165, 173 156, 186 144, 190 137, 185 131, 174 124, 166 119, 166 79, 158 57, 68 32, 40 31, 25 34, 17 41, 14 46, 11 57, 56 91, 33 97, 23 100, 22 107, 28 131, 80 135, 73 145, 85 157, 99 162, 122 170, 161 165))"; + assertEquals(expected, actual); + + actual = Functions.asWKT(Functions.simplifyPolygonHull(geom, 0.3)); + assertEquals(expected, actual); + + actual = Functions.asWKT(Functions.simplifyPolygonHull(geom, 0.3, false)); + expected = "POLYGON ((131 158, 116 158, 99 162, 89 137, 76 130, 59 127, 28 131, 46 116, 36 100, 64 94, 75 72, 41 68, 33 54, 68 32, 90 48, 112 53, 145 57, 158 57, 161 97, 158 115, 158 121, 190 137, 169 140, 179 148, 161 165, 131 158))"; + assertEquals(expected, actual); + + actual = Functions.asWKT(Functions.simplifyPolygonHull(geom, 0.1, false)); + expected = "POLYGON ((89 137, 36 100, 64 94, 75 72, 33 54, 112 53, 145 57, 161 165, 89 137))"; + assertEquals(expected, actual); + + actual = Functions.asWKT(Functions.simplifyPolygonHull(geom, 0.1)); + expected = "POLYGON ((161 165, 173 156, 186 144, 190 137, 158 57, 68 32, 40 31, 25 34, 17 41, 14 46, 11 57, 22 107, 28 131, 85 157, 99 162, 122 170, 161 165))"; + assertEquals(expected, actual); + + geom = Constructors.geomFromEWKT("MULTIPOLYGON (((131 158, 136 163, 161 165, 173 156, 179 148, 169 140, 186 144, 190 137, 185 131, 174 128, 174 124, 166 119, 158 121, 158 115, 165 107, 161 97, 166 88, 166 79, 158 57, 145 57, 112 53, 111 47, 93 43, 90 48, 88 40, 80 39, 68 32, 51 33, 40 31, 39 34, 49 38, 34 38, 25 34, 28 39, 36 40, 44 46, 24 41, 17 41, 14 46, 19 50, 33 54, 21 55, 13 52, 11 57, 22 60, 34 59, 41 68, 75 72, 62 77, 56 70, 46 72, 31 69, 46 76, 52 82, 47 84, 56 90, 66 90 [...] + actual = Functions.asWKT(Functions.simplifyPolygonHull(geom, 0.3, true)); + expected = "MULTIPOLYGON (((161 165, 173 156, 186 144, 190 137, 185 131, 174 124, 166 119, 166 79, 158 57, 68 32, 40 31, 25 34, 17 41, 14 46, 11 57, 56 91, 33 97, 23 100, 22 107, 28 131, 80 135, 73 145, 85 157, 99 162, 122 170, 161 165)))"; + assertEquals(expected, actual); + + geom = Constructors.geomFromEWKT("LINESTRING (10 10, 20 20, 30 30)"); + Geometry finalGeom = geom; + assertThrows("Input geometry must be polygonal", IllegalArgumentException.class, () -> { + Functions.simplifyPolygonHull(finalGeom, 0.1); + }); + } + @Test public void force3DObject2D() { int expectedDims = 3; diff --git a/docs/api/flink/Function.md b/docs/api/flink/Function.md index 3f0a2e13d..835045aaa 100644 --- a/docs/api/flink/Function.md +++ b/docs/api/flink/Function.md @@ -3042,6 +3042,56 @@ Output: 3021 ``` +## ST_SimplifyPolygonHull + +Introduction: This function computes a topology-preserving simplified hull, either outer or inner, for a polygonal geometry input. An outer hull fully encloses the original geometry, while an inner hull lies entirely within. The result maintains the same structure as the input, including handling of MultiPolygons and holes, represented as a polygonal geometry formed from a subset of vertices. + +Vertex reduction is governed by the `vertexFactor` parameter ranging from 0 to 1, with lower values yielding simpler outputs with fewer vertices and reduced concavity. For both hull types, a `vertexFactor` of 1.0 returns the original geometry. Specifically, for outer hulls, 0.0 computes the convex hull; for inner hulls, 0.0 produces a triangular geometry. + +The simplification algorithm iteratively removes concave corners containing the least area until reaching the target vertex count. It preserves topology by preventing edge crossings, ensuring the output is a valid polygonal geometry in all cases. + +Format: + +``` +ST_SimplifyPolygonHull(geom: Geometry, vertexFactor: Double, isOuter: Boolean = true) +``` + +``` +ST_SimplifyPolygonHull(geom: Geometry, vertexFactor: Double) +``` + +Since: `vTBD` + +SQL Example + +```sql +SELECT ST_SimplifyPolygonHull( + ST_GeomFromText('POLYGON ((30 10, 40 40, 45 45, 50 30, 55 25, 60 50, 65 45, 70 30, 75 20, 80 25, 70 10, 30 10))'), + 0.4 +) +``` + +Output: + +``` +POLYGON ((30 10, 40 40, 45 45, 60 50, 65 45, 80 25, 70 10, 30 10)) +``` + +SQL Example + +```sql +SELECT ST_SimplifyPolygonHull( + ST_GeomFromText('POLYGON ((30 10, 40 40, 45 45, 50 30, 55 25, 60 50, 65 45, 70 30, 75 20, 80 25, 70 10, 30 10))'), + 0.4, false +) +``` + +Output: + +``` +POLYGON ((30 10, 70 10, 60 50, 55 25, 30 10)) +``` + ## ST_SimplifyPreserveTopology Introduction: Simplifies a geometry and ensures that the result is a valid geometry having the same dimension and number of components as the input, diff --git a/docs/api/snowflake/vector-data/Function.md b/docs/api/snowflake/vector-data/Function.md index 419a1a907..5f862e767 100644 --- a/docs/api/snowflake/vector-data/Function.md +++ b/docs/api/snowflake/vector-data/Function.md @@ -2254,6 +2254,54 @@ Output: LINESTRING(177 10, 179 10, 181 10, 183 10) ``` +## ST_SimplifyPolygonHull + +Introduction: This function computes a topology-preserving simplified hull, either outer or inner, for a polygonal geometry input. An outer hull fully encloses the original geometry, while an inner hull lies entirely within. The result maintains the same structure as the input, including handling of MultiPolygons and holes, represented as a polygonal geometry formed from a subset of vertices. + +Vertex reduction is governed by the `vertexFactor` parameter ranging from 0 to 1, with lower values yielding simpler outputs with fewer vertices and reduced concavity. For both hull types, a `vertexFactor` of 1.0 returns the original geometry. Specifically, for outer hulls, 0.0 computes the convex hull; for inner hulls, 0.0 produces a triangular geometry. + +The simplification algorithm iteratively removes concave corners containing the least area until reaching the target vertex count. It preserves topology by preventing edge crossings, ensuring the output is a valid polygonal geometry in all cases. + +Format: + +``` +ST_SimplifyPolygonHull(geom: Geometry, vertexFactor: Double, isOuter: Boolean = true) +``` + +``` +ST_SimplifyPolygonHull(geom: Geometry, vertexFactor: Double) +``` + +SQL Example + +```sql +SELECT ST_SimplifyPolygonHull( + ST_GeomFromText('POLYGON ((30 10, 40 40, 45 45, 50 30, 55 25, 60 50, 65 45, 70 30, 75 20, 80 25, 70 10, 30 10))'), + 0.4 +) +``` + +Output: + +``` +POLYGON ((30 10, 40 40, 45 45, 60 50, 65 45, 80 25, 70 10, 30 10)) +``` + +SQL Example + +```sql +SELECT ST_SimplifyPolygonHull( + ST_GeomFromText('POLYGON ((30 10, 40 40, 45 45, 50 30, 55 25, 60 50, 65 45, 70 30, 75 20, 80 25, 70 10, 30 10))'), + 0.4, false +) +``` + +Output: + +``` +POLYGON ((30 10, 70 10, 60 50, 55 25, 30 10)) +``` + ## ST_SimplifyPreserveTopology Introduction: Simplifies a geometry and ensures that the result is a valid geometry having the same dimension and number of components as the input, diff --git a/docs/api/sql/Function.md b/docs/api/sql/Function.md index 01893a2c8..8a5558ff5 100644 --- a/docs/api/sql/Function.md +++ b/docs/api/sql/Function.md @@ -3031,6 +3031,56 @@ Output: LINESTRING(177 10, 179 10, 181 10, 183 10) ``` +## ST_SimplifyPolygonHull + +Introduction: This function computes a topology-preserving simplified hull, either outer or inner, for a polygonal geometry input. An outer hull fully encloses the original geometry, while an inner hull lies entirely within. The result maintains the same structure as the input, including handling of MultiPolygons and holes, represented as a polygonal geometry formed from a subset of vertices. + +Vertex reduction is governed by the `vertexFactor` parameter ranging from 0 to 1, with lower values yielding simpler outputs with fewer vertices and reduced concavity. For both hull types, a `vertexFactor` of 1.0 returns the original geometry. Specifically, for outer hulls, 0.0 computes the convex hull; for inner hulls, 0.0 produces a triangular geometry. + +The simplification algorithm iteratively removes concave corners containing the least area until reaching the target vertex count. It preserves topology by preventing edge crossings, ensuring the output is a valid polygonal geometry in all cases. + +Format: + +``` +ST_SimplifyPolygonHull(geom: Geometry, vertexFactor: Double, isOuter: Boolean = true) +``` + +``` +ST_SimplifyPolygonHull(geom: Geometry, vertexFactor: Double) +``` + +Since: `vTBD` + +SQL Example + +```sql +SELECT ST_SimplifyPolygonHull( + ST_GeomFromText('POLYGON ((30 10, 40 40, 45 45, 50 30, 55 25, 60 50, 65 45, 70 30, 75 20, 80 25, 70 10, 30 10))'), + 0.4 +) +``` + +Output: + +``` +POLYGON ((30 10, 40 40, 45 45, 60 50, 65 45, 80 25, 70 10, 30 10)) +``` + +SQL Example + +```sql +SELECT ST_SimplifyPolygonHull( + ST_GeomFromText('POLYGON ((30 10, 40 40, 45 45, 50 30, 55 25, 60 50, 65 45, 70 30, 75 20, 80 25, 70 10, 30 10))'), + 0.4, false +) +``` + +Output: + +``` +POLYGON ((30 10, 70 10, 60 50, 55 25, 30 10)) +``` + ## ST_SimplifyPreserveTopology Introduction: Simplifies a geometry and ensures that the result is a valid geometry having the same dimension and number of components as the input, 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 d9f05ef1f..772aa27cf 100644 --- a/flink/src/main/java/org/apache/sedona/flink/Catalog.java +++ b/flink/src/main/java/org/apache/sedona/flink/Catalog.java @@ -154,6 +154,7 @@ public class Catalog { new Functions.ST_ShiftLongitude(), new Functions.ST_SimplifyPreserveTopology(), new Functions.ST_SimplifyVW(), + new Functions.ST_SimplifyPolygonHull(), new Functions.ST_Split(), new Functions.ST_Subdivide(), new Functions.ST_SymDifference(), 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 86feb05f5..79d1a8b26 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 @@ -1046,6 +1046,23 @@ public class Functions { } } + public static class ST_SimplifyPolygonHull 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, + @DataTypeHint("Double") Double vertexFactor, + @DataTypeHint("Boolean") Boolean isOuter) { + Geometry geom = (Geometry) o; + return org.apache.sedona.common.Functions.simplifyPolygonHull(geom, vertexFactor, isOuter); + } + + @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, + @DataTypeHint("Double") Double vertexFactor) { + Geometry geom = (Geometry) o; + return org.apache.sedona.common.Functions.simplifyPolygonHull(geom, vertexFactor); + } + } + public static class ST_Subdivide 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 c19e8177e..f128b31d2 100644 --- a/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java +++ b/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java @@ -1160,6 +1160,18 @@ public class FunctionTest extends TestBase{ assertEquals("POLYGON ((0 0, 1 0, 1 1, 0 0))", result.toString()); } + @Test + public void testSimplifyPolygonHull() { + Table table = tableEnv.sqlQuery("SELECT ST_GeomFromWKT('POLYGON ((30 10, 40 40, 45 45, 20 40, 25 35, 10 20, 15 15, 30 10))') AS geom"); + String actual = first(table.select(call(Functions.ST_SimplifyPolygonHull.class.getSimpleName(), $("geom"), 0.3, false))).getField(0).toString(); + String expected = "POLYGON ((30 10, 40 40, 10 20, 30 10))"; + assertEquals(expected, actual); + + actual = first(table.select(call(Functions.ST_SimplifyPolygonHull.class.getSimpleName(), $("geom"), 0.3))).getField(0).toString(); + expected = "POLYGON ((30 10, 15 15, 10 20, 20 40, 45 45, 30 10))"; + assertEquals(expected, actual); + } + @Test public void testSplit() { Table pointTable = tableEnv.sqlQuery("SELECT ST_Split(ST_GeomFromWKT('LINESTRING (0 0, 1.5 1.5, 2 2)'), ST_GeomFromWKT('MULTIPOINT (0.5 0.5, 1 1)'))"); diff --git a/python/sedona/sql/st_functions.py b/python/sedona/sql/st_functions.py index 33470ef41..1a3dd9809 100644 --- a/python/sedona/sql/st_functions.py +++ b/python/sedona/sql/st_functions.py @@ -1387,6 +1387,21 @@ def ST_SimplifyVW(geometry: ColumnOrName, distance_tolerance: ColumnOrNameOrNumb return _call_st_function("ST_SimplifyVW", (geometry, distance_tolerance)) +@validate_argument_types +def ST_SimplifyPolygonHull(geometry: ColumnOrName, vertexFactor: ColumnOrNameOrNumber, isOuter: Optional[Union[ColumnOrName, bool]] = None) -> Column: + """Simplify a geometry using Visvalingam-Whyatt algorithm within a specified tolerance while preserving topological relationships. + + :param geometry: Geometry column to simplify. + :type geometry: ColumnOrName + :param vertexFactor: Tolerance for merging points together to simplify the geometry as either a number or numeric column. + :type vertexFactor: ColumnOrNameOrNumber + :return: Simplified geometry as a geometry column. + :rtype: Column + """ + args = (geometry, vertexFactor) if isOuter is None else (geometry, vertexFactor, isOuter) + + return _call_st_function("ST_SimplifyPolygonHull", args) + @validate_argument_types def ST_Split(input: ColumnOrName, blade: ColumnOrName) -> Column: """Split input geometry by the blade geometry. diff --git a/python/tests/sql/test_dataframe_api.py b/python/tests/sql/test_dataframe_api.py index a76cd3885..b6fd404d4 100644 --- a/python/tests/sql/test_dataframe_api.py +++ b/python/tests/sql/test_dataframe_api.py @@ -192,6 +192,8 @@ test_configurations = [ (stf.ST_ShiftLongitude, ("geom",), "triangle_geom", "", "POLYGON ((0 0, 1 0, 1 1, 0 0))"), (stf.ST_SimplifyPreserveTopology, ("geom", 0.2), "0.9_poly", "", "POLYGON ((0 0, 1 0, 1 1, 0 0))"), (stf.ST_SimplifyVW, ("geom", 0.1), "0.9_poly", "", "POLYGON ((0 0, 1 0, 1 1, 0 0))"), + (stf.ST_SimplifyPolygonHull, ("geom", 0.3, False), "polygon_unsimplified", "", "POLYGON ((30 10, 40 40, 10 20, 30 10))"), + (stf.ST_SimplifyPolygonHull, ("geom", 0.3), "polygon_unsimplified", "", "POLYGON ((30 10, 15 15, 10 20, 20 40, 45 45, 30 10))"), (stf.ST_Snap, ("poly", "line", 2.525), "poly_and_line", "" ,"POLYGON ((2.6 12.5, 2.6 20, 12.6 20, 12.6 12.5, 10.1 10, 2.6 12.5))"), (stf.ST_Split, ("line", "points"), "multipoint_splitting_line", "", "MULTILINESTRING ((0 0, 0.5 0.5), (0.5 0.5, 1 1), (1 1, 1.5 1.5, 2 2))"), (stf.ST_SRID, ("point",), "point_geom", "", 0), @@ -395,6 +397,8 @@ wrong_type_configurations = [ (stf.ST_SimplifyPreserveTopology, ("", None)), (stf.ST_SimplifyVW, (None, 2)), (stf.ST_SimplifyVW, ("", None)), + (stf.ST_SimplifyPolygonHull, ("", None)), + (stf.ST_SimplifyPolygonHull, (None, None)), (stf.ST_Snap, (None, None, 12)), (stf.ST_SRID, (None,)), (stf.ST_StartPoint, (None,)), @@ -518,6 +522,8 @@ class TestDataFrameAPI(TestBase): return TestDataFrameAPI.spark.sql("SELECT ST_GeomFromWKT('POLYGON ((0 0, 3 0, 3 3, 0 0), (1 1, 2 2, 2 1, 1 1))') AS geom") elif request.param == "0.9_poly": return TestDataFrameAPI.spark.sql("SELECT ST_GeomFromWKT('POLYGON ((0 0, 1 0, 1 0.9, 1 1, 0 0))') AS geom") + elif request.param == "polygon_unsimplified": + return TestDataFrameAPI.spark.sql("SELECT ST_GeomFromWKT('POLYGON ((30 10, 40 40, 45 45, 20 40, 25 35, 10 20, 15 15, 30 10))') AS geom") elif request.param == "precision_reduce_point": return TestDataFrameAPI.spark.sql("SELECT ST_Point(0.12, 0.23) AS geom") elif request.param == "closed_linestring_geom": diff --git a/python/tests/sql/test_function.py b/python/tests/sql/test_function.py index bbc7d19cc..fcedfb794 100644 --- a/python/tests/sql/test_function.py +++ b/python/tests/sql/test_function.py @@ -902,6 +902,16 @@ class TestPredicateJoin(TestBase): expected = "LINESTRING (5 2, 7 25, 10 10)" assert expected == actual + def test_st_simplify_polygon_hull(self): + basedf = self.spark.sql("SELECT ST_GeomFromWKT('POLYGON ((30 10, 40 40, 45 45, 20 40, 25 35, 10 20, 15 15, 30 10))') as geom") + actual = basedf.selectExpr("ST_SimplifyPolygonHull(geom, 0.3, false)").take(1)[0][0].wkt + expected = "POLYGON ((30 10, 40 40, 10 20, 30 10))" + assert expected == actual + + actual = basedf.selectExpr("ST_SimplifyPolygonHull(geom, 0.3)").take(1)[0][0].wkt + expected = "POLYGON ((30 10, 15 15, 10 20, 20 40, 45 45, 30 10))" + assert expected == actual + def test_st_is_ring(self): result_and_expected = [ 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 04dea7d12..8a39b3f01 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 @@ -874,6 +874,20 @@ public class TestFunctions extends TestBase { ); } + @Test + public void test_ST_SimplifyPolygonHull() { + registerUDF("ST_SimplifyPolygonHull", byte[].class, double.class, boolean.class); + verifySqlSingleRes( + "select sedona.ST_AsText(sedona.ST_SimplifyPolygonHull(sedona.ST_GeomFromText('POLYGON ((30 10, 40 40, 45 45, 20 40, 25 35, 10 20, 15 15, 30 10))'), 0.3, false))", + "POLYGON ((30 10, 40 40, 10 20, 30 10))" + ); + registerUDF("ST_SimplifyPolygonHull", byte[].class, double.class); + verifySqlSingleRes( + "select sedona.ST_AsText(sedona.ST_SimplifyPolygonHull(sedona.ST_GeomFromText('POLYGON ((30 10, 40 40, 45 45, 20 40, 25 35, 10 20, 15 15, 30 10))'), 0.3))", + "POLYGON ((30 10, 15 15, 10 20, 20 40, 45 45, 30 10))" + ); + } + @Test public void test_ST_Split() { registerUDF("ST_Split", byte[].class, byte[].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 bb58dc103..6f9448783 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 @@ -837,6 +837,20 @@ public class TestFunctionsV2 ); } + @Test + public void test_ST_SimplifyPolygonHull() { + registerUDFV2("ST_SimplifyPolygonHull", String.class, double.class, boolean.class); + verifySqlSingleRes( + "select ST_AsText(sedona.ST_SimplifyPolygonHull(ST_GeomFromText('POLYGON ((30 10, 40 40, 45 45, 20 40, 25 35, 10 20, 15 15, 30 10))'), 0.3, false))", + "POLYGON((30 10,40 40,10 20,30 10))" + ); + registerUDFV2("ST_SimplifyPolygonHull", String.class, double.class); + verifySqlSingleRes( + "select ST_AsText(sedona.ST_SimplifyPolygonHull(ST_GeomFromText('POLYGON ((30 10, 40 40, 45 45, 20 40, 25 35, 10 20, 15 15, 30 10))'), 0.3))", + "POLYGON((30 10,15 15,10 20,20 40,45 45,30 10))" + ); + } + @Test public void test_ST_Split() { registerUDFV2("ST_Split", String.class, String.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 f8e8194b5..66b07e703 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 @@ -1236,6 +1236,27 @@ public class UDFs { ); } + @UDFAnnotations.ParamMeta(argNames = {"geometry", "vertexFactor", "isOuter"}) + public static byte[] ST_SimplifyPolygonHull(byte[] geometry, double vertexFactor, boolean isOuter) { + return GeometrySerde.serialize( + Functions.simplifyPolygonHull( + GeometrySerde.deserialize(geometry), + vertexFactor, + isOuter + ) + ); + } + + @UDFAnnotations.ParamMeta(argNames = {"geometry", "vertexFactor"}) + public static byte[] ST_SimplifyPolygonHull(byte[] geometry, double vertexFactor) { + return GeometrySerde.serialize( + Functions.simplifyPolygonHull( + GeometrySerde.deserialize(geometry), + vertexFactor + ) + ); + } + @UDFAnnotations.ParamMeta(argNames = {"input", "blade"}) public static byte[] ST_Split(byte[] input, byte[] blade) { return GeometrySerde.serialize( 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 faa075f95..e2e0acdc5 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 @@ -994,6 +994,27 @@ public class UDFsV2 ); } + @UDFAnnotations.ParamMeta(argNames = {"geometry", "vertexFactor", "isOuter"}, argTypes = {"Geometry", "double", "boolean"}, returnTypes = "Geometry") + public static String ST_SimplifyPolygonHull(String geometry, double vertexFactor, boolean isOuter) { + return GeometrySerde.serGeoJson( + Functions.simplifyPolygonHull( + GeometrySerde.deserGeoJson(geometry), + vertexFactor, + isOuter + ) + ); + } + + @UDFAnnotations.ParamMeta(argNames = {"geometry", "vertexFactor"}, argTypes = {"Geometry", "double"}, returnTypes = "Geometry") + public static String ST_SimplifyPolygonHull(String geometry, double vertexFactor) { + return GeometrySerde.serGeoJson( + Functions.simplifyPolygonHull( + GeometrySerde.deserGeoJson(geometry), + vertexFactor + ) + ); + } + @UDFAnnotations.ParamMeta(argNames = {"input", "blade"}, argTypes = {"Geometry", "Geometry"}, returnTypes = "Geometry") public static String ST_Split(String input, String blade) { return GeometrySerde.serGeoJson( 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 8ef7ea486..c6a0d5d41 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 @@ -107,6 +107,7 @@ object Catalog { function[ST_AsGML](), function[ST_AsKML](), function[ST_SimplifyVW](), + function[ST_SimplifyPolygonHull](), function[ST_SRID](), function[ST_SetSRID](), function[ST_GeometryType](), 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 708edd538..ded1e31f7 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 @@ -354,6 +354,14 @@ case class ST_SimplifyVW(inputExpressions: Seq[Expression]) } } +case class ST_SimplifyPolygonHull(inputExpressions: Seq[Expression]) + extends InferredExpression(inferrableFunction2(Functions.simplifyPolygonHull), inferrableFunction3(Functions.simplifyPolygonHull)) { + + protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = { + copy(inputExpressions = newChildren) + } +} + case class ST_AsText(inputExpressions: Seq[Expression]) extends InferredExpression(Functions.asWKT _) { 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 67b342162..9bb23391d 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 @@ -363,6 +363,10 @@ object st_functions extends DataFrameAPI { def ST_SimplifyVW(geometry: Column, distanceTolerance: Column): Column = wrapExpression[ST_SimplifyVW](geometry, distanceTolerance) def ST_SimplifyVW(geometry: String, distanceTolerance: Double): Column = wrapExpression[ST_SimplifyVW](geometry, distanceTolerance) + def ST_SimplifyPolygonHull(geometry: Column, vertexFactor: Column): Column = wrapExpression[ST_SimplifyPolygonHull](geometry, vertexFactor) + def ST_SimplifyPolygonHull(geometry: String, vertexFactor: Double): Column = wrapExpression[ST_SimplifyPolygonHull](geometry, vertexFactor) + def ST_SimplifyPolygonHull(geometry: Column, vertexFactor: Column, isOuter: Column): Column = wrapExpression[ST_SimplifyPolygonHull](geometry, vertexFactor, isOuter) + def ST_SimplifyPolygonHull(geometry: String, vertexFactor: Double, isOuter: Boolean): Column = wrapExpression[ST_SimplifyPolygonHull](geometry, vertexFactor, isOuter) def ST_Union(a: Column, b: Column): Column = wrapExpression[ST_Union](a, b) def ST_Union(a: String, b: String): Column = wrapExpression[ST_Union](a, b) diff --git a/spark/common/src/test/scala/org/apache/sedona/sql/dataFrameAPITestScala.scala b/spark/common/src/test/scala/org/apache/sedona/sql/dataFrameAPITestScala.scala index a62b13ea7..b1ac7ee26 100644 --- a/spark/common/src/test/scala/org/apache/sedona/sql/dataFrameAPITestScala.scala +++ b/spark/common/src/test/scala/org/apache/sedona/sql/dataFrameAPITestScala.scala @@ -703,6 +703,17 @@ class dataFrameAPITestScala extends TestBaseScala { assertEquals(expected, actual) } + it("Passed ST_SimplifyPolygonHull") { + val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('POLYGON ((30 10, 40 40, 45 45, 20 40, 25 35, 10 20, 15 15, 30 10))') AS geom") + var actual = baseDf.select(ST_SimplifyPolygonHull("geom", 0.3, false)).first().get(0).asInstanceOf[Geometry].toText + var expected = "POLYGON ((30 10, 40 40, 10 20, 30 10))" + assertEquals(expected, actual) + + actual = baseDf.select(ST_SimplifyPolygonHull("geom", 0.3)).first().get(0).asInstanceOf[Geometry].toText + expected = "POLYGON ((30 10, 15 15, 10 20, 20 40, 45 45, 30 10))" + assertEquals(expected, actual) + } + it("Passed ST_GeometryType") { val pointDf = sparkSession.sql("SELECT ST_Point(0.0, 0.0) AS geom") val df = pointDf.select(ST_GeometryType("geom")) 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 cb1082b02..1b81fb909 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 @@ -662,6 +662,16 @@ class functionTestScala extends TestBaseScala with Matchers with GeometrySample assertEquals(expected, actual) } + it("Passed ST_SimplifyPolygonHull") { + val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('POLYGON ((30 10, 40 40, 45 45, 20 40, 25 35, 10 20, 15 15, 30 10))') AS geom") + var actual = baseDf.selectExpr("ST_SimplifyPolygonHull(geom, 0.3, false)").first().get(0).asInstanceOf[Geometry].toText + var expected = "POLYGON ((30 10, 40 40, 10 20, 30 10))" + assertEquals(expected, actual) + + actual = baseDf.selectExpr("ST_SimplifyPolygonHull(geom, 0.3)").first().get(0).asInstanceOf[Geometry].toText + expected = "POLYGON ((30 10, 15 15, 10 20, 20 40, 45 45, 30 10))" + assertEquals(expected, actual) + } it("Passed ST_NPoints") { var test = sparkSession.sql("SELECT ST_NPoints(ST_GeomFromText('LINESTRING(77.29 29.07,77.42 29.26,77.27 29.31,77.29 29.07)'))")
