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)'))")

Reply via email to