This is an automated email from the ASF dual-hosted git repository. jiayu pushed a commit to branch SEDONA-598 in repository https://gitbox.apache.org/repos/asf/sedona.git
commit 6da51e9925c492b7cff8dd942601e88111c97018 Author: Furqaan Khan <[email protected]> AuthorDate: Thu May 9 22:11:54 2024 -0400 [TASK-134] Add ST_UnaryUnion (#189) * feat: Add ST_UnaryUnion * fix: sedona tests * fix: snowflake tests --- .../java/org/apache/sedona/common/Functions.java | 5 +++++ .../org/apache/sedona/common/FunctionsTest.java | 21 ++++++++++++++++++--- docs/api/flink/Function.md | 20 ++++++++++++++++++++ docs/api/snowflake/vector-data/Function.md | 18 ++++++++++++++++++ docs/api/sql/Function.md | 20 ++++++++++++++++++++ .../main/java/org/apache/sedona/flink/Catalog.java | 1 + .../apache/sedona/flink/expressions/Functions.java | 8 ++++++++ .../java/org/apache/sedona/flink/FunctionTest.java | 8 ++++++++ python/sedona/sql/st_functions.py | 10 ++++++++++ python/tests/sql/test_dataframe_api.py | 4 ++++ 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 +++++++++ .../org/apache/sedona/snowflake/snowsql/UDFsV2.java | 9 +++++++++ .../scala/org/apache/sedona/sql/UDF/Catalog.scala | 1 + .../sql/sedona_sql/expressions/Functions.scala | 8 ++++++++ .../sql/sedona_sql/expressions/st_functions.scala | 3 +++ .../apache/sedona/sql/dataFrameAPITestScala.scala | 7 +++++++ .../org/apache/sedona/sql/functionTestScala.scala | 7 +++++++ 20 files changed, 180 insertions(+), 3 deletions(-) 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 161d862db..e2bf5c6ea 100644 --- a/common/src/main/java/org/apache/sedona/common/Functions.java +++ b/common/src/main/java/org/apache/sedona/common/Functions.java @@ -39,6 +39,7 @@ import org.locationtech.jts.operation.distance3d.Distance3DOp; import org.locationtech.jts.operation.linemerge.LineMerger; import org.locationtech.jts.operation.overlay.snap.GeometrySnapper; import org.locationtech.jts.operation.polygonize.Polygonizer; +import org.locationtech.jts.operation.union.UnaryUnionOp; import org.locationtech.jts.operation.valid.IsSimpleOp; import org.locationtech.jts.operation.valid.IsValidOp; import org.locationtech.jts.operation.valid.TopologyValidationError; @@ -1317,6 +1318,10 @@ public class Functions { return GEOMETRY_FACTORY.createGeometryCollection(geoms).union(); } + public static Geometry unaryUnion(Geometry geom) { + return UnaryUnionOp.union(geom); + } + public static Geometry createMultiGeometryFromOneElement(Geometry geometry) { if (geometry instanceof Circle) { return GEOMETRY_FACTORY.createGeometryCollection(new Circle[] {(Circle) geometry}); 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 8915c2cab..b7a79d1af 100644 --- a/common/src/test/java/org/apache/sedona/common/FunctionsTest.java +++ b/common/src/test/java/org/apache/sedona/common/FunctionsTest.java @@ -725,7 +725,6 @@ public class FunctionsTest extends TestBase { long[] cellIds = new long[]{1152991873351024640L, 1153132610839379968L, 1153273348327735296L, 1153414085816090624L}; Geometry[] polygons = Functions.s2ToGeom(cellIds); String actual = Functions.asWKT(Functions.union(polygons)); - System.out.println(Arrays.toString(polygons)); String expected = "POLYGON ((0.6014716838554667 -0.0000000000000254, 0.6014716838554158 -0.0000000000000254, -0.0000000000000254 -0.0000000000000254, -0.0000000000000254 0.6014385452363985, -0.0000000000000254 0.6014716838554667, -0.0000000000000254 1.2121321753162642, 0.6014716838554667 1.2121321753162642, 0.6014716838554667 1.2120654068310366, 1.2121321753162642 1.2120654068310366, 1.2121321753162642 0.6014385452364494, 1.2121321753162642 0.6013371003640015, 1.2121321753162642 [...] assertEquals(expected, actual); @@ -743,7 +742,24 @@ public class FunctionsTest extends TestBase { actual = Functions.asWKT(Functions.union(mPoly)); expected = "POLYGON ((0.6014716838554667 -0.0000000000000254, 0.6014716838554158 -0.0000000000000254, -0.0000000000000254 -0.0000000000000254, -0.0000000000000254 0.6014385452363985, -0.0000000000000254 0.6014716838554667, -0.0000000000000254 1.2121321753162642, 0.6014716838554667 1.2121321753162642, 0.6014716838554667 1.2120654068310366, 1.2121321753162642 1.2120654068310366, 1.2121321753162642 0.6014385452364494, 1.2121321753162642 0.6013371003640015, 1.2121321753162642 -0.0000 [...] assertEquals(expected, actual); - System.out.println(actual); + } + + @Test + public void testUnaryUnion() throws ParseException { + Geometry geometry = Constructors.geomFromEWKT("MULTIPOLYGON(((0 10,0 30,20 30,20 10,0 10)),((10 0,10 20,30 20,30 0,10 0)))"); + String actual = Functions.unaryUnion(geometry).toString(); + String expected = "POLYGON ((10 0, 10 10, 0 10, 0 30, 20 30, 20 20, 30 20, 30 0, 10 0))"; + assertEquals(expected, actual); + + geometry = Constructors.geomFromEWKT("MULTILINESTRING ((10 10, 20 20, 30 30),(25 25, 35 35, 45 45),(40 40, 50 50, 60 60),(55 55, 65 65, 75 75))"); + actual = Functions.unaryUnion(geometry).toString(); + expected = "MULTILINESTRING ((10 10, 20 20, 25 25), (25 25, 30 30), (30 30, 35 35, 40 40), (40 40, 45 45), (45 45, 50 50, 55 55), (55 55, 60 60), (60 60, 65 65, 75 75))"; + assertEquals(expected, actual); + + geometry = Constructors.geomFromEWKT("GEOMETRYCOLLECTION (POINT (10 10),LINESTRING (20 20, 30 30),POLYGON ((25 25, 35 35, 35 35, 25 25)),MULTIPOINT (30 30, 40 40),MULTILINESTRING ((40 40, 50 50), (45 45, 55 55)),MULTIPOLYGON (((50 50, 60 60, 60 60, 50 50)), ((55 55, 65 65, 65 65, 55 55))))"); + actual = Functions.unaryUnion(geometry).toString(); + expected = "GEOMETRYCOLLECTION (POINT (10 10), LINESTRING (20 20, 30 30), LINESTRING (40 40, 45 45), LINESTRING (45 45, 50 50), LINESTRING (50 50, 55 55))"; + assertEquals(expected, actual); } @Test @@ -1940,7 +1956,6 @@ public class FunctionsTest extends TestBase { for (int[] testCase : testCases_special) { Geometry geom = GEOMETRY_FACTORY.createPoint(new Coordinate(testCase[0], testCase[1])); int actualEPSG = Functions.bestSRID(geom); - System.out.println("actualEPSG: "+actualEPSG); int expectedEPSG = testCase[2]; assertEquals("Failed at coordinates (" + testCase[0] + ", " + testCase[1] + ")", expectedEPSG, actualEPSG); } diff --git a/docs/api/flink/Function.md b/docs/api/flink/Function.md index 660658491..9a0047ff6 100644 --- a/docs/api/flink/Function.md +++ b/docs/api/flink/Function.md @@ -3404,6 +3404,26 @@ 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_UnaryUnion + +Introduction: This variant of [ST_Union](#st_union) operates on a single geometry input. The input geometry can be a simple Geometry type, a MultiGeometry, or a GeometryCollection. The function calculates the geometric union across all components and elements within the provided geometry object. + +Format: `ST_UnaryUnion(geometry: Geometry)` + +Since: `vTBD` + +SQL Example + +```sql +SELECT ST_UnaryUnion(ST_GeomFromWKT('MULTIPOLYGON(((0 10,0 30,20 30,20 10,0 10)),((10 0,10 20,30 20,30 0,10 0)))')) +``` + +Output: + +``` +POLYGON ((10 0, 10 10, 0 10, 0 30, 20 30, 20 20, 30 20, 30 0, 10 0)) +``` + ## ST_Union Introduction: diff --git a/docs/api/snowflake/vector-data/Function.md b/docs/api/snowflake/vector-data/Function.md index 5f862e767..43e208727 100644 --- a/docs/api/snowflake/vector-data/Function.md +++ b/docs/api/snowflake/vector-data/Function.md @@ -2557,6 +2557,24 @@ 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_UnaryUnion + +Introduction: This variant of [ST_Union](#st_union) operates on a single geometry input. The input geometry can be a simple Geometry type, a MultiGeometry, or a GeometryCollection. The function calculates the geometric union across all components and elements within the provided geometry object. + +Format: `ST_UnaryUnion(geometry: Geometry)` + +SQL Example + +```sql +SELECT ST_UnaryUnion(ST_GeomFromWKT('MULTIPOLYGON(((0 10,0 30,20 30,20 10,0 10)),((10 0,10 20,30 20,30 0,10 0)))')) +``` + +Output: + +``` +POLYGON ((10 0, 10 10, 0 10, 0 30, 20 30, 20 20, 30 20, 30 0, 10 0)) +``` + ## 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 ff0a58fbe..af394655d 100644 --- a/docs/api/sql/Function.md +++ b/docs/api/sql/Function.md @@ -3514,6 +3514,26 @@ 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_UnaryUnion + +Introduction: This variant of [ST_Union](#st_union) operates on a single geometry input. The input geometry can be a simple Geometry type, a MultiGeometry, or a GeometryCollection. The function calculates the geometric union across all components and elements within the provided geometry object. + +Format: `ST_UnaryUnion(geometry: Geometry)` + +Since: `vTBD` + +SQL Example + +```sql +SELECT ST_UnaryUnion(ST_GeomFromWKT('MULTIPOLYGON(((0 10,0 30,20 30,20 10,0 10)),((10 0,10 20,30 20,30 0,10 0)))')) +``` + +Output: + +``` +POLYGON ((10 0, 10 10, 0 10, 0 30, 20 30, 20 20, 30 20, 30 0, 10 0)) +``` + ## 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 772aa27cf..8d723fa80 100644 --- a/flink/src/main/java/org/apache/sedona/flink/Catalog.java +++ b/flink/src/main/java/org/apache/sedona/flink/Catalog.java @@ -175,6 +175,7 @@ public class Catalog { new Functions.ST_ForcePolygonCCW(), new Functions.ST_Translate(), new Functions.ST_TriangulatePolygon(), + new Functions.ST_UnaryUnion(), 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 79d1a8b26..72d7d2c95 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 @@ -1267,6 +1267,14 @@ public class Functions { } } + public static class ST_UnaryUnion 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.unaryUnion(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 f128b31d2..9e53a7495 100644 --- a/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java +++ b/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java @@ -316,6 +316,14 @@ public class FunctionTest extends TestBase{ assertEquals(expected, actual); } + @Test + public void testUnaryUnion() { + Table table = tableEnv.sqlQuery("SELECT ST_GeomFromWKT('MULTIPOLYGON(((0 10,0 30,20 30,20 10,0 10)),((10 0,10 20,30 20,30 0,10 0)))') AS geom"); + String actual = first(table.select(call(Functions.ST_UnaryUnion.class.getSimpleName(), $("geom")))).getField(0).toString(); + String expected = "POLYGON ((10 0, 10 10, 0 10, 0 30, 20 30, 20 20, 30 20, 30 0, 10 0))"; + assertEquals(expected, actual); + } + @Test public void testUnionArrayVariant() { Table polyTable = tableEnv.sqlQuery("SELECT ARRAY[ST_GeomFromWKT('POLYGON ((-3 -3, 3 -3, 3 3, -3 3, -3 -3))'), ST_GeomFromWKT('POLYGON ((-2 1, 2 1, 2 4, -2 4, -2 1))')] as polys"); diff --git a/python/sedona/sql/st_functions.py b/python/sedona/sql/st_functions.py index 1a3dd9809..75b95c332 100644 --- a/python/sedona/sql/st_functions.py +++ b/python/sedona/sql/st_functions.py @@ -1470,6 +1470,16 @@ def ST_TriangulatePolygon(geom: ColumnOrName) -> Column: """ return _call_st_function("ST_TriangulatePolygon", geom) +@validate_argument_types +def ST_UnaryUnion(geom: ColumnOrName) -> Column: + """Calculate the unary union of a geometry + + :param geom: Geometry to do union + :type geom: ColumnOrName + :return: Geometry representing the unary union of geom as a geometry column. + :rtype: Column + """ + return _call_st_function("ST_UnaryUnion", 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 b6fd404d4..9ba7bfb1d 100644 --- a/python/tests/sql/test_dataframe_api.py +++ b/python/tests/sql/test_dataframe_api.py @@ -206,6 +206,7 @@ test_configurations = [ (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_UnaryUnion, ("geom",), "overlapping_mPolys", "", "POLYGON ((10 0, 10 10, 0 10, 0 30, 20 30, 20 20, 30 20, 30 0, 10 0))"), (stf.ST_VoronoiPolygons, ("geom",), "multipoint", "", "GEOMETRYCOLLECTION (POLYGON ((-1 -1, -1 2, 2 -1, -1 -1)), POLYGON ((-1 2, 2 2, 2 -1, -1 2)))"), (stf.ST_X, ("b",), "two_points", "", 3.0), (stf.ST_XMax, ("line",), "linestring_geom", "", 5.0), @@ -415,6 +416,7 @@ wrong_type_configurations = [ (stf.ST_TriangulatePolygon, (None,)), (stf.ST_Union, (None, "")), (stf.ST_Union, (None,)), + (stf.ST_UnaryUnion, (None,)), (stf.ST_X, (None,)), (stf.ST_XMax, (None,)), (stf.ST_XMin, (None,)), @@ -516,6 +518,8 @@ class TestDataFrameAPI(TestBase): return TestDataFrameAPI.spark.sql("SELECT ST_GeomFromWKT('POLYGON ((1 5, 1 1, 3 3, 5 3, 7 1, 7 5, 5 3, 3 3, 1 5))') AS geom") elif request.param == "overlapping_polys": return TestDataFrameAPI.spark.sql("SELECT ST_GeomFromWKT('POLYGON((0 0, 2 0, 2 1, 0 1, 0 0))') AS a, ST_GeomFromWKT('POLYGON((1 0, 3 0, 3 1, 1 1, 1 0))') AS b") + elif request.param == "overlapping_mPolys": + return TestDataFrameAPI.spark.sql("SELECT ST_GeomFromWKT('MULTIPOLYGON(((0 10,0 30,20 30,20 10,0 10)),((10 0,10 20,30 20,30 0,10 0)))') AS geom") elif request.param == "multipoint": return TestDataFrameAPI.spark.sql("SELECT ST_GeomFromWKT('MULTIPOINT ((0 0), (1 1))') AS geom") elif request.param == "geom_with_hole": diff --git a/python/tests/sql/test_function.py b/python/tests/sql/test_function.py index fcedfb794..37da2fbfa 100644 --- a/python/tests/sql/test_function.py +++ b/python/tests/sql/test_function.py @@ -464,6 +464,12 @@ class TestPredicateJoin(TestBase): expected = "MULTIPOLYGON (((5 -3, 5 -1, 7 -1, 7 -3, 5 -3)), ((-3 -3, -3 3, 3 3, 3 -3, -3 -3)), ((4 4, 4 6, 6 6, 6 4, 4 4)))" assert expected == actual + def test_st_unary_union(self): + baseDf = self.spark.sql("SELECT ST_GeomFromWKT('MULTIPOLYGON(((0 10,0 30,20 30,20 10,0 10)),((10 0,10 20,30 20,30 0,10 0)))') AS geom") + actual = baseDf.selectExpr("ST_UnaryUnion(geom)").take(1)[0][0].wkt + expected = "POLYGON ((10 0, 10 10, 0 10, 0 30, 20 30, 20 20, 30 20, 30 0, 10 0))" + assert expected == actual + def test_st_azimuth(self): sample_points = create_sample_points(20) sample_pair_points = [[el, sample_points[1]] for el in sample_points] 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 8a39b3f01..edfe2b9b4 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 @@ -954,6 +954,15 @@ public class TestFunctions extends TestBase { ); } + @Test + public void test_ST_UnaryUnion() { + registerUDF("ST_UnaryUnion", byte[].class); + verifySqlSingleRes( + "select sedona.ST_AsText(sedona.ST_UnaryUnion(sedona.ST_GeomFromText('MULTIPOLYGON(((0 10,0 30,20 30,20 10,0 10)),((10 0,10 20,30 20,30 0,10 0)))')))", + "POLYGON ((10 0, 10 10, 0 10, 0 30, 20 30, 20 20, 30 20, 30 0, 10 0))" + ); + } + @Test public void test_ST_VoronoiPolygons() { registerUDF("ST_VoronoiPolygons", 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 6f9448783..1927789a6 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 @@ -916,6 +916,15 @@ public class TestFunctionsV2 ); } + @Test + public void test_ST_UnaryUnion() { + registerUDFV2("ST_UnaryUnion", String.class); + verifySqlSingleRes( + "select ST_AsText(sedona.ST_UnaryUnion(ST_GeometryFromWKT('MULTILINESTRING ((10 10, 20 20, 30 30),(25 25, 35 35, 45 45),(40 40, 50 50, 60 60),(55 55, 65 65, 75 75))')))", + "MULTILINESTRING((10 10,20 20,25 25),(25 25,30 30),(30 30,35 35,40 40),(40 40,45 45),(45 45,50 50,55 55),(55 55,60 60),(60 60,65 65,75 75))" + ); + } + @Test public void test_ST_VoronoiPolygons() { registerUDFV2("ST_VoronoiPolygons", 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 66b07e703..32402968c 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 @@ -1370,6 +1370,15 @@ public class UDFs { ); } + @UDFAnnotations.ParamMeta(argNames = {"geometry"}) + public static byte[] ST_UnaryUnion(byte[] geometry) { + return GeometrySerde.serialize( + Functions.unaryUnion( + GeometrySerde.deserialize(geometry) + ) + ); + } + @UDFAnnotations.ParamMeta(argNames = {"geometry"}) public static byte[] ST_VoronoiPolygons(byte[] geometry) { 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 e2e0acdc5..a30f775f5 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 @@ -1128,6 +1128,15 @@ public class UDFsV2 ); } + @UDFAnnotations.ParamMeta(argNames = {"geometry"}, argTypes = {"Geometry"}, returnTypes = "Geometry") + public static String ST_UnaryUnion(String geometry) { + return GeometrySerde.serGeoJson( + Functions.unaryUnion( + GeometrySerde.deserGeoJson(geometry) + ) + ); + } + @UDFAnnotations.ParamMeta(argNames = {"geometry"}, argTypes = {"Geometry"}, returnTypes = "Geometry") public static String ST_VoronoiPolygons(String geometry) { 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 c6a0d5d41..ff522f9f6 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 @@ -85,6 +85,7 @@ object Catalog { function[ST_Intersection](), function[ST_Difference](), function[ST_SymDifference](), + function[ST_UnaryUnion](), function[ST_Union](), function[ST_IsValid](), function[ST_IsEmpty](), 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 ded1e31f7..2e1711e70 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 @@ -913,6 +913,14 @@ case class ST_SymDifference(inputExpressions: Seq[Expression]) } } +case class ST_UnaryUnion(inputExpressions: Seq[Expression]) + extends InferredExpression(inferrableFunction1(Functions.unaryUnion)) { + + protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = { + copy(inputExpressions = newChildren) + } +} + /** * Return the union of geometry A and B * 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 9bb23391d..e7b441486 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 @@ -368,6 +368,9 @@ object st_functions extends DataFrameAPI { 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_UnaryUnion(geometry: Column): Column = wrapExpression[ST_UnaryUnion](geometry) + def ST_UnaryUnion(geometry: String): Column = wrapExpression[ST_UnaryUnion](geometry) + 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) def ST_Union(geoms: Column): Column = wrapExpression[ST_Union](geoms) 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 b1ac7ee26..434a938ab 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 @@ -754,6 +754,13 @@ class dataFrameAPITestScala extends TestBaseScala { assert(expected.equals(actual)) } + it("Passed ST_UnaryUnion") { + val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('MULTIPOLYGON(((0 10,0 30,20 30,20 10,0 10)),((10 0,10 20,30 20,30 0,10 0)))') AS geom") + val actual = baseDf.select(ST_UnaryUnion("geom")).first().get(0).asInstanceOf[Geometry].toText + val expected = "POLYGON ((10 0, 10 10, 0 10, 0 30, 20 30, 20 20, 30 20, 30 0, 10 0))" + assertEquals(expected, actual) + } + it("Passed ST_Azimuth") { val baseDf = sparkSession.sql("SELECT ST_Point(0.0, 0.0) AS a, ST_Point(1.0, 1.0) AS b") val df = baseDf.select(ST_Azimuth("a", "b")) 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 1b81fb909..38af32109 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 @@ -781,6 +781,13 @@ class functionTestScala extends TestBaseScala with Matchers with GeometrySample assert(symDiff.first().get(0) == null) } + it("Passed ST_UnaryUnion") { + val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('MULTIPOLYGON(((0 10,0 30,20 30,20 10,0 10)),((10 0,10 20,30 20,30 0,10 0)))') AS geom") + val actual = baseDf.selectExpr("ST_UnaryUnion(geom)").first().get(0).asInstanceOf[Geometry].toText + val expected = "POLYGON ((10 0, 10 10, 0 10, 0 30, 20 30, 20 20, 30 20, 30 0, 10 0))" + assertEquals(expected, actual) + } + it("Passed ST_Union - part of right overlaps left") { val testtable = sparkSession.sql("select ST_GeomFromWKT('POLYGON ((-3 -3, 3 -3, 3 3, -3 3, -3 -3))') as a, ST_GeomFromWKT('POLYGON ((-2 1, 2 1, 2 4, -2 4, -2 1))') as b")
