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)

Reply via email to