This is an automated email from the ASF dual-hosted git repository. jiayu pushed a commit to branch SEDONA-566 in repository https://gitbox.apache.org/repos/asf/sedona.git
commit 2bb1e3361a0b0348006c936657a52ba06c107abc Author: Furqaan Khan <[email protected]> AuthorDate: Fri Mar 29 15:16:31 2024 -0400 [TASK-143] Add ST_TriangulatePolygon (#141) * fix: tests of forcePolygonCCW and isPolygonCCW * feat: add ST_TriangulatePolygon * fix: snowflake test * chore: remove print statement * chore: add point test --- .../java/org/apache/sedona/common/Functions.java | 5 +++++ .../org/apache/sedona/common/FunctionsTest.java | 22 +++++++++++++++++++++- docs/api/flink/Function.md | 22 ++++++++++++++++++++++ docs/api/snowflake/vector-data/Function.md | 20 ++++++++++++++++++++ docs/api/sql/Function.md | 22 ++++++++++++++++++++++ .../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 | 6 ++++++ .../sedona/snowflake/snowsql/TestFunctions.java | 9 +++++++++ .../sedona/snowflake/snowsql/TestFunctionsV2.java | 9 +++++++++ .../org/apache/sedona/snowflake/snowsql/UDFs.java | 9 +++++++++ .../apache/sedona/snowflake/snowsql/UDFsV2.java | 9 +++++++++ .../scala/org/apache/sedona/sql/UDF/Catalog.scala | 1 + .../sql/sedona_sql/expressions/Functions.scala | 7 +++++++ .../sql/sedona_sql/expressions/st_functions.scala | 3 +++ .../apache/sedona/sql/dataFrameAPITestScala.scala | 7 +++++++ .../org/apache/sedona/sql/functionTestScala.scala | 7 +++++++ 20 files changed, 186 insertions(+), 1 deletion(-) 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 5cec2a42d..087b6e799 100644 --- a/common/src/main/java/org/apache/sedona/common/Functions.java +++ b/common/src/main/java/org/apache/sedona/common/Functions.java @@ -47,6 +47,7 @@ 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.TopologyPreservingSimplifier; +import org.locationtech.jts.triangulate.polygon.ConstrainedDelaunayTriangulator; import org.wololo.jts2geojson.GeoJSONWriter; import java.util.*; @@ -893,6 +894,10 @@ public class Functions { return isExteriorRingCW && isInteriorRingCW; } + public static Geometry triangulatePolygon(Geometry geom) { + return ConstrainedDelaunayTriangulator.triangulate(geom); + } + public static double lineLocatePoint(Geometry geom, Geometry point) { double length = geom.getLength(); 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 701a24594..5b1a85630 100644 --- a/common/src/test/java/org/apache/sedona/common/FunctionsTest.java +++ b/common/src/test/java/org/apache/sedona/common/FunctionsTest.java @@ -756,6 +756,7 @@ public class FunctionsTest extends TestBase { assertEquals(expected, actual); } + @Test public void testForcePolygonCCW() throws ParseException { Geometry polyCW = Constructors.geomFromWKT("POLYGON ((20 35, 45 20, 30 5, 10 10, 10 30, 20 35), (30 20, 20 25, 20 15, 30 20))", 0); String actual = Functions.asWKT(Functions.forcePolygonCCW(polyCW)); @@ -800,8 +801,9 @@ public class FunctionsTest extends TestBase { Geometry lineClosed = Constructors.geomFromWKT("LINESTRING (30 20, 20 25, 20 15, 30 20)", 0); assertFalse(Functions.isPolygonCW(lineClosed)); - } + } + @Test public void testIsPolygonCCW() throws ParseException { Geometry polyCCW = Constructors.geomFromWKT("POLYGON ((20 35, 10 30, 10 10, 30 5, 45 20, 20 35),(30 20, 20 15, 20 25, 30 20))", 0); assertTrue(Functions.isPolygonCCW(polyCCW)); @@ -819,6 +821,24 @@ public class FunctionsTest extends TestBase { assertFalse(Functions.isPolygonCCW(lineClosed)); } + @Test + public void testTriangulatePolygon() throws ParseException { + Geometry geom = Constructors.geomFromWKT("POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (5 5, 5 8, 8 8, 8 5, 5 5))", 0); + String actual = Functions.asWKT(Functions.triangulatePolygon(geom)); + String expected = "GEOMETRYCOLLECTION (POLYGON ((0 0, 0 10, 5 5, 0 0)), POLYGON ((5 8, 5 5, 0 10, 5 8)), POLYGON ((10 0, 0 0, 5 5, 10 0)), POLYGON ((10 10, 5 8, 0 10, 10 10)), POLYGON ((10 0, 5 5, 8 5, 10 0)), POLYGON ((5 8, 10 10, 8 8, 5 8)), POLYGON ((10 10, 10 0, 8 5, 10 10)), POLYGON ((8 5, 8 8, 10 10, 8 5)))"; + assertEquals(expected, actual); + + geom = Constructors.geomFromWKT("MULTIPOLYGON (((0 0, 10 0, 10 10, 0 10, 0 0), (2 2, 2 8, 8 8, 8 2, 2 2)), ((4 4, 4 6, 6 6, 6 4, 4 4)), ((6 6, 6 8, 8 8, 8 6, 6 6)), ((3 3, 3 4, 4 4, 4 3, 3 3)), ((5 5, 5 6, 6 6, 6 5, 5 5)), ((7 7, 7 8, 8 8, 8 7, 7 7)))", 0); + actual = Functions.asWKT(Functions.triangulatePolygon(geom)); + expected = "GEOMETRYCOLLECTION (POLYGON ((0 0, 0 10, 2 2, 0 0)), POLYGON ((2 8, 2 2, 0 10, 2 8)), POLYGON ((10 0, 0 0, 2 2, 10 0)), POLYGON ((8 8, 2 8, 0 10, 8 8)), POLYGON ((10 0, 2 2, 8 2, 10 0)), POLYGON ((8 8, 0 10, 10 10, 8 8)), POLYGON ((10 10, 10 0, 8 2, 10 10)), POLYGON ((8 2, 8 8, 10 10, 8 2)), POLYGON ((4 4, 4 6, 6 6, 4 4)), POLYGON ((6 6, 6 4, 4 4, 6 6)), POLYGON ((6 6, 6 8, 8 8, 6 6)), POLYGON ((8 8, 8 6, 6 6, 8 8)), POLYGON ((3 3, 3 4, 4 4, 3 3)), POLYGON ((4 4, 4 3, [...] + assertEquals(expected, actual); + + geom = Constructors.geomFromWKT("POINT(0 1)", 0); + actual = Functions.asWKT(Functions.triangulatePolygon(geom)); + expected = "GEOMETRYCOLLECTION EMPTY"; + assertEquals(expected, actual); + } + @Test public void geometricMedianTolerance() throws Exception { MultiPoint multiPoint = GEOMETRY_FACTORY.createMultiPointFromCoords( diff --git a/docs/api/flink/Function.md b/docs/api/flink/Function.md index 5c6b2f6f3..3aec907ea 100644 --- a/docs/api/flink/Function.md +++ b/docs/api/flink/Function.md @@ -2895,6 +2895,28 @@ Output: POINT (-70.01 44.37) ``` +## ST_TriangulatePolygon + +Introduction: Generates the constrained Delaunay triangulation for the input Polygon. The constrained Delaunay triangulation is a set of triangles created from the Polygon's vertices that covers the Polygon area precisely, while maximizing the combined interior angles across all triangles compared to other possible triangulations. This produces the highest quality triangulation representation of the Polygon geometry. The function returns a GeometryCollection of Polygon geometries compris [...] + +Format: `ST_TriangulatePolygon(geom: Geometry)` + +Since: `vTBD` + +SQL Example + +```sql +SELECT ST_TriangulatePolygon( + ST_GeomFromWKT('POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (5 5, 5 8, 8 8, 8 5, 5 5))') + ) +``` + +Output: + +``` +GEOMETRYCOLLECTION (POLYGON ((0 0, 0 10, 5 5, 0 0)), POLYGON ((5 8, 5 5, 0 10, 5 8)), POLYGON ((10 0, 0 0, 5 5, 10 0)), POLYGON ((10 10, 5 8, 0 10, 10 10)), POLYGON ((10 0, 5 5, 8 5, 10 0)), POLYGON ((5 8, 10 10, 8 8, 5 8)), POLYGON ((10 10, 10 0, 8 5, 10 10)), POLYGON ((8 5, 8 8, 10 10, 8 5))) +``` + ## ST_Union Introduction: diff --git a/docs/api/snowflake/vector-data/Function.md b/docs/api/snowflake/vector-data/Function.md index 6f02aaed1..1e7a741b9 100644 --- a/docs/api/snowflake/vector-data/Function.md +++ b/docs/api/snowflake/vector-data/Function.md @@ -2251,6 +2251,26 @@ Input: `ST_Translate(POINT(1, 3, 2), 1, 2)` Output: `POINT(2, 5, 2)` +## ST_TriangulatePolygon + +Introduction: Generates the constrained Delaunay triangulation for the input Polygon. The constrained Delaunay triangulation is a set of triangles created from the Polygon's vertices that covers the Polygon area precisely, while maximizing the combined interior angles across all triangles compared to other possible triangulations. This produces the highest quality triangulation representation of the Polygon geometry. The function returns a GeometryCollection of Polygon geometries compris [...] + +Format: `ST_TriangulatePolygon(geom: Geometry)` + +SQL Example + +```sql +SELECT ST_TriangulatePolygon( + ST_GeomFromWKT('POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (5 5, 5 8, 8 8, 8 5, 5 5))') + ) +``` + +Output: + +``` +GEOMETRYCOLLECTION (POLYGON ((0 0, 0 10, 5 5, 0 0)), POLYGON ((5 8, 5 5, 0 10, 5 8)), POLYGON ((10 0, 0 0, 5 5, 10 0)), POLYGON ((10 10, 5 8, 0 10, 10 10)), POLYGON ((10 0, 5 5, 8 5, 10 0)), POLYGON ((5 8, 10 10, 8 8, 5 8)), POLYGON ((10 10, 10 0, 8 5, 10 10)), POLYGON ((8 5, 8 8, 10 10, 8 5))) +``` + ## ST_Union Introduction: Return the union of geometry A and B diff --git a/docs/api/sql/Function.md b/docs/api/sql/Function.md index 814c9c376..637951ac5 100644 --- a/docs/api/sql/Function.md +++ b/docs/api/sql/Function.md @@ -3005,6 +3005,28 @@ Output: POINT (-70.01 44.37) ``` +## ST_TriangulatePolygon + +Introduction: Generates the constrained Delaunay triangulation for the input Polygon. The constrained Delaunay triangulation is a set of triangles created from the Polygon's vertices that covers the Polygon area precisely, while maximizing the combined interior angles across all triangles compared to other possible triangulations. This produces the highest quality triangulation representation of the Polygon geometry. The function returns a GeometryCollection of Polygon geometries compris [...] + +Format: `ST_TriangulatePolygon(geom: Geometry)` + +Since: `vTBD` + +SQL Example + +```sql +SELECT ST_TriangulatePolygon( + ST_GeomFromWKT('POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (5 5, 5 8, 8 8, 8 5, 5 5))') + ) +``` + +Output: + +``` +GEOMETRYCOLLECTION (POLYGON ((0 0, 0 10, 5 5, 0 0)), POLYGON ((5 8, 5 5, 0 10, 5 8)), POLYGON ((10 0, 0 0, 5 5, 10 0)), POLYGON ((10 10, 5 8, 0 10, 10 10)), POLYGON ((10 0, 5 5, 8 5, 10 0)), POLYGON ((5 8, 10 10, 8 8, 5 8)), POLYGON ((10 10, 10 0, 8 5, 10 10)), POLYGON ((8 5, 8 8, 10 10, 8 5))) +``` + ## ST_Union Introduction: 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..7fc1917de 100644 --- a/flink/src/main/java/org/apache/sedona/flink/Catalog.java +++ b/flink/src/main/java/org/apache/sedona/flink/Catalog.java @@ -146,6 +146,7 @@ public class Catalog { new Functions.ST_IsPolygonCCW(), new Functions.ST_ForcePolygonCCW(), new Functions.ST_Translate(), + new Functions.ST_TriangulatePolygon(), new Functions.ST_Union(), new Functions.ST_VoronoiPolygons(), new Functions.ST_FrechetDistance(), 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..d5d84244d 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 @@ -1062,6 +1062,14 @@ public class Functions { } } + public static class ST_TriangulatePolygon 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) { + Geometry geometry = (Geometry) o; + return org.apache.sedona.common.Functions.triangulatePolygon(geometry); + } + } + public static class ST_Union 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 o1, 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..140ccea89 100644 --- a/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java +++ b/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java @@ -1217,6 +1217,13 @@ public class FunctionTest extends TestBase{ } @Test + public void testTriangulatePolygon() { + Table polyTable = tableEnv.sqlQuery("SELECT ST_TriangulatePolygon(ST_GeomFromWKT('POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (5 5, 5 8, 8 8, 8 5, 5 5))')) as poly"); + String actual = (String) first(polyTable.select(call(Functions.ST_AsText.class.getSimpleName(), $("poly")))).getField(0); + String expected = "GEOMETRYCOLLECTION (POLYGON ((0 0, 0 10, 5 5, 0 0)), POLYGON ((5 8, 5 5, 0 10, 5 8)), POLYGON ((10 0, 0 0, 5 5, 10 0)), POLYGON ((10 10, 5 8, 0 10, 10 10)), POLYGON ((10 0, 5 5, 8 5, 10 0)), POLYGON ((5 8, 10 10, 8 8, 5 8)), POLYGON ((10 10, 10 0, 8 5, 10 10)), POLYGON ((8 5, 8 8, 10 10, 8 5)))"; + assertEquals(expected, actual); + } + public void testForcePolygonCW() { Table polyTable = tableEnv.sqlQuery("SELECT ST_ForcePolygonCW(ST_GeomFromWKT('POLYGON ((20 35, 10 30, 10 10, 30 5, 45 20, 20 35),(30 20, 20 15, 20 25, 30 20))')) AS polyCW"); String actual = (String) first(polyTable.select(call(Functions.ST_AsText.class.getSimpleName(), $("polyCW")))).getField(0); diff --git a/python/sedona/sql/st_functions.py b/python/sedona/sql/st_functions.py index dfd9ae13c..6eb5943d7 100644 --- a/python/sedona/sql/st_functions.py +++ b/python/sedona/sql/st_functions.py @@ -1303,6 +1303,17 @@ def ST_Transform(geometry: ColumnOrName, source_crs: ColumnOrName, target_crs: O args = (geometry, source_crs, target_crs, disable_error) return _call_st_function("ST_Transform", args) +@validate_argument_types +def ST_TriangulatePolygon(geom: ColumnOrName) -> Column: + """Computes the constrained Delaunay triangulation of polygons. Holes and Multipolygons are supported. + + :param geom: (Multi)Polygon to be triangulated. + :type geom: ColumnOrName + :return: Triangulated Polygon as GeometryCollection of Polygons + :rtype: Column + """ + return _call_st_function("ST_TriangulatePolygon", geom) + @validate_argument_types def ST_Union(a: ColumnOrName, b: Optional[ColumnOrName] = None) -> Column: diff --git a/python/tests/sql/test_dataframe_api.py b/python/tests/sql/test_dataframe_api.py index 56ec8212a..056d1e3a4 100644 --- a/python/tests/sql/test_dataframe_api.py +++ b/python/tests/sql/test_dataframe_api.py @@ -176,6 +176,7 @@ test_configurations = [ (stf.ST_SymDifference, ("a", "b"), "overlapping_polys", "", "MULTIPOLYGON (((1 0, 0 0, 0 1, 1 1, 1 0)), ((2 0, 2 1, 3 1, 3 0, 2 0)))"), (stf.ST_Transform, ("point", lambda: f.lit("EPSG:4326"), lambda: f.lit("EPSG:32649")), "point_geom", "ST_ReducePrecision(geom, 2)", "POINT (-34870890.91 1919456.06)"), (stf.ST_Translate, ("geom", 1.0, 1.0,), "square_geom", "", "POLYGON ((2 1, 2 2, 3 2, 3 1, 2 1))"), + (stf.ST_TriangulatePolygon, ("geom",), "square_geom", "", "GEOMETRYCOLLECTION (POLYGON ((1 0, 1 1, 2 1, 1 0)), POLYGON ((2 1, 2 0, 1 0, 2 1)))"), (stf.ST_Union, ("a", "b"), "overlapping_polys", "", "POLYGON ((1 0, 0 0, 0 1, 1 1, 2 1, 3 1, 3 0, 2 0, 1 0))"), (stf.ST_Union, ("polys",), "array_polygons", "", "POLYGON ((2 3, 3 3, 3 -3, -3 -3, -3 3, -2 3, -2 4, 2 4, 2 3))"), (stf.ST_VoronoiPolygons, ("geom",), "multipoint", "", "GEOMETRYCOLLECTION (POLYGON ((-1 -1, -1 2, 2 -1, -1 -1)), POLYGON ((-1 2, 2 2, 2 -1, -1 2)))"), @@ -349,6 +350,7 @@ wrong_type_configurations = [ (stf.ST_SymDifference, ("", None)), (stf.ST_Transform, (None, "", "")), (stf.ST_Transform, ("", None, "")), + (stf.ST_TriangulatePolygon, (None,)), (stf.ST_Union, (None, "")), (stf.ST_Union, (None,)), (stf.ST_X, (None,)), diff --git a/python/tests/sql/test_function.py b/python/tests/sql/test_function.py index ea8497132..ce1ee0bdb 100644 --- a/python/tests/sql/test_function.py +++ b/python/tests/sql/test_function.py @@ -1380,6 +1380,12 @@ class TestPredicateJoin(TestBase): actual = actualDf.selectExpr("ST_NRings(geom)").take(1)[0][0] assert expected == actual + def test_trangulatePolygon(self): + baseDf = self.spark.sql("SELECT ST_GeomFromWKT('POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (5 5, 5 8, 8 8, 8 5, 5 5))') as poly") + actual = baseDf.selectExpr("ST_AsText(ST_TriangulatePolygon(poly))").take(1)[0][0] + expected = "GEOMETRYCOLLECTION (POLYGON ((0 0, 0 10, 5 5, 0 0)), POLYGON ((5 8, 5 5, 0 10, 5 8)), POLYGON ((10 0, 0 0, 5 5, 10 0)), POLYGON ((10 10, 5 8, 0 10, 10 10)), POLYGON ((10 0, 5 5, 8 5, 10 0)), POLYGON ((5 8, 10 10, 8 8, 5 8)), POLYGON ((10 10, 10 0, 8 5, 10 10)), POLYGON ((8 5, 8 8, 10 10, 8 5)))" + assert actual == expected + def test_translate(self): expected = "POLYGON ((3 5, 3 6, 4 6, 4 5, 3 5))" actual_df = self.spark.sql( 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..15875b0a0 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 @@ -1092,6 +1092,15 @@ public class TestFunctions extends TestBase { ); } + @Test + public void test_ST_TriangulatePolygon() { + registerUDF("ST_TriangulatePolygon", byte[].class); + verifySqlSingleRes( + "SELECT sedona.ST_AsText(sedona.ST_TriangulatePolygon(sedona.ST_GeomFromWKT('POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (5 5, 5 8, 8 8, 8 5, 5 5))')))", + "GEOMETRYCOLLECTION (POLYGON ((0 0, 0 10, 5 5, 0 0)), POLYGON ((5 8, 5 5, 0 10, 5 8)), POLYGON ((10 0, 0 0, 5 5, 10 0)), POLYGON ((10 10, 5 8, 0 10, 10 10)), POLYGON ((10 0, 5 5, 8 5, 10 0)), POLYGON ((5 8, 10 10, 8 8, 5 8)), POLYGON ((10 10, 10 0, 8 5, 10 10)), POLYGON ((8 5, 8 8, 10 10, 8 5)))" + ); + } + @Test public void test_ST_Translate() { registerUDF("ST_Translate", byte[].class, double.class, double.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..21e8ede7a 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 @@ -1051,6 +1051,15 @@ public class TestFunctionsV2 ); } + @Test + public void test_ST_TriangulatePolygon() { + registerUDFV2("ST_TriangulatePolygon", String.class); + verifySqlSingleRes( + "SELECT ST_AsText(sedona.ST_TriangulatePolygon(ST_GeomFromWKT('POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (5 5, 5 8, 8 8, 8 5, 5 5))')))", + "GEOMETRYCOLLECTION(POLYGON((0 0,0 10,5 5,0 0)),POLYGON((5 8,5 5,0 10,5 8)),POLYGON((10 0,0 0,5 5,10 0)),POLYGON((10 10,5 8,0 10,10 10)),POLYGON((10 0,5 5,8 5,10 0)),POLYGON((5 8,10 10,8 8,5 8)),POLYGON((10 10,10 0,8 5,10 10)),POLYGON((8 5,8 8,10 10,8 5)))" + ); + } + @Test public void test_ST_Translate() { registerUDFV2("ST_Translate", String.class, double.class, double.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..31581cba0 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 @@ -1411,6 +1411,15 @@ public class UDFs { ); } + @UDFAnnotations.ParamMeta(argNames = {"geom"}) + public static byte[] ST_TriangulatePolygon(byte[] geom) { + return GeometrySerde.serialize( + Functions.triangulatePolygon( + GeometrySerde.deserialize(geom) + ) + ); + } + @UDFAnnotations.ParamMeta(argNames = {"geom", "deltaX", "deltaY"}) public static byte[] ST_Translate(byte[] geom, double deltaX, double deltaY) { 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 2c6ed7907..e6447a61a 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 @@ -1242,6 +1242,15 @@ public class UDFsV2 ); } + @UDFAnnotations.ParamMeta(argNames = {"geom"}, argTypes = {"Geometry"}, returnTypes = "Geometry") + public static String ST_TriangulatePolygon(String geom) { + return GeometrySerde.serGeoJson( + Functions.triangulatePolygon( + GeometrySerde.deserGeoJson(geom) + ) + ); + } + @UDFAnnotations.ParamMeta(argNames = {"geom", "deltaX", "deltaY"}, argTypes = {"Geometry", "double", "double"}, returnTypes = "Geometry") public static String ST_Translate(String geom, double deltaX, double deltaY) { 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 bd2e801ad..35ebfea06 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 @@ -175,6 +175,7 @@ object Catalog { function[ST_Force3D](0.0), function[ST_NRings](), function[ST_Translate](0.0), + function[ST_TriangulatePolygon](), function[ST_VoronoiPolygons](0.0, null), function[ST_FrechetDistance](), function[ST_Affine](), 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..9e565b1f0 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 @@ -1150,6 +1150,13 @@ case class ST_Translate(inputExpressions: Seq[Expression]) } } +case class ST_TriangulatePolygon(inputExpressions: Seq[Expression]) + extends InferredExpression(Functions.triangulatePolygon _) with FoldableExpression { + protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = { + copy(inputExpressions = newChildren) + } +} + case class ST_VoronoiPolygons(inputExpressions: Seq[Expression]) extends InferredExpression(nullTolerantInferrableFunction3(FunctionsGeoTools.voronoiPolygons)) with FoldableExpression { protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = { 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..bc3d0ffe6 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 @@ -409,6 +409,9 @@ object st_functions extends DataFrameAPI { def ST_Translate(geometry: String, deltaX: Double, deltaY: Double): Column = wrapExpression[ST_Translate](geometry, deltaX, deltaY, 0.0) + def ST_TriangulatePolygon(geom: Column): Column = wrapExpression[ST_TriangulatePolygon](geom) + def ST_TriangulatePolygon(geom: String): Column = wrapExpression[ST_TriangulatePolygon](geom) + def ST_VoronoiPolygons(geometry: Column, tolerance : Column, extendTo: Column): Column = wrapExpression[ST_VoronoiPolygons](geometry, tolerance, extendTo) def ST_VoronoiPolygons(geometry: String, tolerance : Double, extendTo: String): Column = wrapExpression[ST_VoronoiPolygons](geometry, tolerance, extendTo) 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 1ff67eeeb..01247f07f 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 @@ -1204,6 +1204,13 @@ class dataFrameAPITestScala extends TestBaseScala { assertEquals(expectedGeomDefaultValue, wktWriter.write(actualGeomDefaultValue)) } + it("Passed ST_TriangulatePolygon") { + val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (5 5, 5 8, 8 8, 8 5, 5 5))') as poly") + val actual = baseDf.select(ST_AsText(ST_TriangulatePolygon("poly"))).first().getString(0) + val expected = "GEOMETRYCOLLECTION (POLYGON ((0 0, 0 10, 5 5, 0 0)), POLYGON ((5 8, 5 5, 0 10, 5 8)), POLYGON ((10 0, 0 0, 5 5, 10 0)), POLYGON ((10 10, 5 8, 0 10, 10 10)), POLYGON ((10 0, 5 5, 8 5, 10 0)), POLYGON ((5 8, 10 10, 8 8, 5 8)), POLYGON ((10 10, 10 0, 8 5, 10 10)), POLYGON ((8 5, 8 8, 10 10, 8 5)))" + assertEquals(expected, actual) + } + it("Passed ST_ForcePolygonCW") { val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('POLYGON ((20 35, 10 30, 10 10, 30 5, 45 20, 20 35),(30 20, 20 15, 20 25, 30 20))') AS poly") val actual = baseDf.select(ST_AsText(ST_ForcePolygonCW("poly"))).take(1)(0).get(0).asInstanceOf[String] 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..8f620cd77 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 @@ -232,6 +232,13 @@ class functionTestScala extends TestBaseScala with Matchers with GeometrySample assert(functionDf.take(1)(0).get(0).asInstanceOf[Double].equals(expected)) } + it("Passed ST_TriangulatePolygon") { + val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (5 5, 5 8, 8 8, 8 5, 5 5))') as poly") + val actual = baseDf.selectExpr("ST_AsText(ST_TriangulatePolygon(poly))").first().getString(0) + val expected = "GEOMETRYCOLLECTION (POLYGON ((0 0, 0 10, 5 5, 0 0)), POLYGON ((5 8, 5 5, 0 10, 5 8)), POLYGON ((10 0, 0 0, 5 5, 10 0)), POLYGON ((10 10, 5 8, 0 10, 10 10)), POLYGON ((10 0, 5 5, 8 5, 10 0)), POLYGON ((5 8, 10 10, 8 8, 5 8)), POLYGON ((10 10, 10 0, 8 5, 10 10)), POLYGON ((8 5, 8 8, 10 10, 8 5)))" + assert(expected.equals(actual)) + } + it("Passed ST_Transform") { var point = "POINT (120 60)" val transformedResult = sparkSession.sql(s"""select ST_Transform(ST_geomFromWKT('$point'),'EPSG:4326', 'EPSG:3857', false)""").rdd.map(row => row.getAs[Geometry](0)).collect()(0)
