This is an automated email from the ASF dual-hosted git repository. jiayu pushed a commit to branch SEDONA-601 in repository https://gitbox.apache.org/repos/asf/sedona.git
commit 91080868f2fb61114dce6f96863844c6ebfbe5a1 Author: Furqaan Khan <[email protected]> AuthorDate: Fri May 24 13:36:37 2024 -0400 [TASK-44] Add ST_DelaunayTriangles (#200) * temp commit * docs: add docs for all 3 engines * chore: port to snowflake * fix: snowflake tests * fix: snowflake tests --- .../java/org/apache/sedona/common/Functions.java | 38 +++++++++++++++++----- .../org/apache/sedona/common/FunctionsTest.java | 19 +++++++++++ docs/api/flink/Function.md | 33 +++++++++++++++++++ docs/api/snowflake/vector-data/Function.md | 31 ++++++++++++++++++ docs/api/sql/Function.md | 33 +++++++++++++++++++ .../main/java/org/apache/sedona/flink/Catalog.java | 1 + .../apache/sedona/flink/expressions/Functions.java | 23 +++++++++++++ .../java/org/apache/sedona/flink/FunctionTest.java | 16 +++++++++ python/sedona/sql/st_functions.py | 24 ++++++++++++++ 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 | 10 ++++++ .../org/apache/sedona/snowflake/snowsql/UDFs.java | 29 +++++++++++++++++ .../apache/sedona/snowflake/snowsql/UDFsV2.java | 29 +++++++++++++++++ .../scala/org/apache/sedona/sql/UDF/Catalog.scala | 1 + .../sql/sedona_sql/expressions/Functions.scala | 9 +++++ .../sql/sedona_sql/expressions/st_functions.scala | 8 +++++ .../apache/sedona/sql/dataFrameAPITestScala.scala | 15 +++++++++ .../org/apache/sedona/sql/functionTestScala.scala | 15 +++++++++ 20 files changed, 343 insertions(+), 8 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..360eff8c9 100644 --- a/common/src/main/java/org/apache/sedona/common/Functions.java +++ b/common/src/main/java/org/apache/sedona/common/Functions.java @@ -46,6 +46,7 @@ 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.DelaunayTriangulationBuilder; import org.locationtech.jts.triangulate.polygon.ConstrainedDelaunayTriangulator; import org.wololo.jts2geojson.GeoJSONWriter; @@ -401,7 +402,7 @@ public class Functions { Coordinate[] points = geometry.getCoordinates(); double min = Double.MAX_VALUE; for(int i=0; i < points.length; i++){ - if(java.lang.Double.isNaN(points[i].getM())) + if(Double.isNaN(points[i].getM())) continue; min = Math.min(points[i].getM(), min); } @@ -412,7 +413,7 @@ public class Functions { Coordinate[] points = geometry.getCoordinates(); double max = - Double.MAX_VALUE; for (int i=0; i < points.length; i++) { - if(java.lang.Double.isNaN(points[i].getM())) + if(Double.isNaN(points[i].getM())) continue; max = Math.max(points[i].getM(), max); } @@ -459,7 +460,7 @@ public class Functions { Coordinate[] points = geometry.getCoordinates(); double max = - Double.MAX_VALUE; for (int i=0; i < points.length; i++) { - if(java.lang.Double.isNaN(points[i].getZ())) + if(Double.isNaN(points[i].getZ())) continue; max = Math.max(points[i].getZ(), max); } @@ -470,7 +471,7 @@ public class Functions { Coordinate[] points = geometry.getCoordinates(); double min = Double.MAX_VALUE; for(int i=0; i < points.length; i++){ - if(java.lang.Double.isNaN(points[i].getZ())) + if(Double.isNaN(points[i].getZ())) continue; min = Math.min(points[i].getZ(), min); } @@ -588,13 +589,13 @@ public class Functions { Double y_cord = geom.getY(); Double z_cord = geom.getZ(); Double m_cord = geom.getM(); - if(!java.lang.Double.isNaN(x_cord)) + if(!Double.isNaN(x_cord)) count_dimension++; - if(!java.lang.Double.isNaN(y_cord)) + if(!Double.isNaN(y_cord)) count_dimension++; - if(!java.lang.Double.isNaN(z_cord)) + if(!Double.isNaN(z_cord)) count_dimension++; - if(!java.lang.Double.isNaN(m_cord)) + if(!Double.isNaN(m_cord)) count_dimension++; return count_dimension; } @@ -760,6 +761,27 @@ public class Functions { } } + public static Geometry delaunayTriangle(Geometry geometry) { + return delaunayTriangle(geometry, 0.0, 0); + } + + public static Geometry delaunayTriangle(Geometry geometry, double tolerance) { + return delaunayTriangle(geometry, tolerance, 0); + } + + public static Geometry delaunayTriangle(Geometry geometry, double tolerance, int flag) { + DelaunayTriangulationBuilder dTBuilder = new DelaunayTriangulationBuilder(); + dTBuilder.setSites(geometry); + dTBuilder.setTolerance(tolerance); + if (flag == 0) { + return dTBuilder.getTriangles(geometry.getFactory()); + } else if (flag == 1) { + return dTBuilder.getEdges(geometry.getFactory()); + } else { + throw new IllegalArgumentException("Select a valid flag option (0 or 1)."); + } + } + public static int zmFlag(Geometry geom) { Coordinate coords = geom.getCoordinate(); boolean hasZ = !Double.isNaN(coords.getZ()); 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..c8e3d8d0e 100644 --- a/common/src/test/java/org/apache/sedona/common/FunctionsTest.java +++ b/common/src/test/java/org/apache/sedona/common/FunctionsTest.java @@ -1270,6 +1270,25 @@ public class FunctionsTest extends TestBase { assertEquals(expected4, result4); } + @Test + public void delaunayTriangles() throws ParseException { + Geometry poly = Constructors.geomFromEWKT("POLYGON((175 150, 20 40, 50 60, 125 100, 175 150))"); + Geometry point = Constructors.geomFromEWKT("POINT (110 170)"); + Geometry combined = Functions.union(poly, point); + String actual = Functions.delaunayTriangle(combined).toText(); + String expected = "GEOMETRYCOLLECTION (POLYGON ((20 40, 125 100, 50 60, 20 40)), POLYGON ((20 40, 50 60, 110 170, 20 40)), POLYGON ((110 170, 50 60, 125 100, 110 170)), POLYGON ((110 170, 125 100, 175 150, 110 170)))"; + assertEquals(expected, actual); + + poly = Constructors.geomFromEWKT("MULTIPOLYGON (((10 10, 10 20, 20 20, 20 10, 10 10)),((25 10, 25 20, 35 20, 35 10, 25 10)))"); + actual = Functions.delaunayTriangle(poly, 20).toText(); + expected = "GEOMETRYCOLLECTION (POLYGON ((10 20, 10 10, 35 10, 10 20)))"; + assertEquals(expected, actual); + + actual = Functions.delaunayTriangle(poly, 0, 1).toText(); + expected = "MULTILINESTRING ((25 20, 35 20), (20 20, 25 20), (10 20, 20 20), (10 10, 10 20), (10 10, 20 10), (20 10, 25 10), (25 10, 35 10), (35 10, 35 20), (25 20, 35 10), (25 10, 25 20), (20 20, 25 10), (20 10, 20 20), (10 20, 20 10))"; + assertEquals(expected, actual); + } + @Test public void spheroidLength() { Point point = GEOMETRY_FACTORY.createPoint(new Coordinate(90, 0)); diff --git a/docs/api/flink/Function.md b/docs/api/flink/Function.md index 660658491..896b0cadc 100644 --- a/docs/api/flink/Function.md +++ b/docs/api/flink/Function.md @@ -1055,6 +1055,39 @@ Output: 11.309932474020195 ``` +## ST_DelaunayTriangles + +Introduction: This function computes the [Delaunay triangulation](https://en.wikipedia.org/wiki/Delaunay_triangulation) for the set of vertices in the input geometry. An optional `tolerance` parameter allows snapping nearby input vertices together prior to triangulation and can improve robustness in certain scenarios by handling near-coincident vertices. The default for `tolerance` is 0. The Delaunay triangulation geometry is bounded by the convex hull of the input vertex set. + +The output geometry representation depends on the provided `flag`: + +- `0` - a GeometryCollection of triangular Polygons (default option) +- `1` - a MultiLinestring of the edges of the triangulation + +Format: + +`ST_DelaunayTriangles(geometry: Geometry)` + +`ST_DelaunayTriangles(geometry: Geometry, tolerance: Double)` + +`ST_DelaunayTriangles(geometry: Geometry, tolerance: Double, flag: Integer)` + +Since: `vTBD` + +SQL Example + +```sql +SELECT ST_DelaunayTriangles( + ST_GeomFromWKT('POLYGON ((10 10, 15 30, 20 25, 25 35, 30 20, 40 30, 50 10, 45 5, 35 15, 30 5, 25 15, 20 10, 15 20, 10 10))') +) +``` + +Output: + +``` +GEOMETRYCOLLECTION (POLYGON ((15 30, 10 10, 15 20, 15 30)), POLYGON ((15 30, 15 20, 20 25, 15 30)), POLYGON ((15 30, 20 25, 25 35, 15 30)), POLYGON ((25 35, 20 25, 30 20, 25 35)), POLYGON ((25 35, 30 20, 40 30, 25 35)), POLYGON ((40 30, 30 20, 35 15, 40 30)), POLYGON ((40 30, 35 15, 50 10, 40 30)), POLYGON ((50 10, 35 15, 45 5, 50 10)), POLYGON ((30 5, 45 5, 35 15, 30 5)), POLYGON ((30 5, 35 15, 25 15, 30 5)), POLYGON ((30 5, 25 15, 20 10, 30 5)), POLYGON ((30 5, 20 10, 10 10, 30 5)), PO [...] +``` + ## ST_Difference Introduction: Return the difference between geometry A and B (return part of geometry A that does not intersect geometry B) diff --git a/docs/api/snowflake/vector-data/Function.md b/docs/api/snowflake/vector-data/Function.md index 5f862e767..977703336 100644 --- a/docs/api/snowflake/vector-data/Function.md +++ b/docs/api/snowflake/vector-data/Function.md @@ -750,6 +750,37 @@ Output: 11.309932474020195 ``` +## ST_DelaunayTriangles + +Introduction: This function computes the [Delaunay triangulation](https://en.wikipedia.org/wiki/Delaunay_triangulation) for the set of vertices in the input geometry. An optional `tolerance` parameter allows snapping nearby input vertices together prior to triangulation and can improve robustness in certain scenarios by handling near-coincident vertices. The default for `tolerance` is 0. The Delaunay triangulation geometry is bounded by the convex hull of the input vertex set. + +The output geometry representation depends on the provided `flag`: + +- `0` - a GeometryCollection of triangular Polygons (default option) +- `1` - a MultiLinestring of the edges of the triangulation + +Format: + +`ST_DelaunayTriangles(geometry: Geometry)` + +`ST_DelaunayTriangles(geometry: Geometry, tolerance: Double)` + +`ST_DelaunayTriangles(geometry: Geometry, tolerance: Double, flag: Integer)` + +SQL Example + +```sql +SELECT ST_DelaunayTriangles( + ST_GeomFromWKT('POLYGON ((10 10, 15 30, 20 25, 25 35, 30 20, 40 30, 50 10, 45 5, 35 15, 30 5, 25 15, 20 10, 15 20, 10 10))') +) +``` + +Output: + +``` +GEOMETRYCOLLECTION (POLYGON ((15 30, 10 10, 15 20, 15 30)), POLYGON ((15 30, 15 20, 20 25, 15 30)), POLYGON ((15 30, 20 25, 25 35, 15 30)), POLYGON ((25 35, 20 25, 30 20, 25 35)), POLYGON ((25 35, 30 20, 40 30, 25 35)), POLYGON ((40 30, 30 20, 35 15, 40 30)), POLYGON ((40 30, 35 15, 50 10, 40 30)), POLYGON ((50 10, 35 15, 45 5, 50 10)), POLYGON ((30 5, 45 5, 35 15, 30 5)), POLYGON ((30 5, 35 15, 25 15, 30 5)), POLYGON ((30 5, 25 15, 20 10, 30 5)), POLYGON ((30 5, 20 10, 10 10, 30 5)), PO [...] +``` + ## ST_Difference Introduction: Return the difference between geometry A and B (return part of geometry A that does not intersect geometry B) diff --git a/docs/api/sql/Function.md b/docs/api/sql/Function.md index ff0a58fbe..db8769376 100644 --- a/docs/api/sql/Function.md +++ b/docs/api/sql/Function.md @@ -958,6 +958,39 @@ Output: 11.309932474020195 ``` +## ST_DelaunayTriangles + +Introduction: This function computes the [Delaunay triangulation](https://en.wikipedia.org/wiki/Delaunay_triangulation) for the set of vertices in the input geometry. An optional `tolerance` parameter allows snapping nearby input vertices together prior to triangulation and can improve robustness in certain scenarios by handling near-coincident vertices. The default for `tolerance` is 0. The Delaunay triangulation geometry is bounded by the convex hull of the input vertex set. + +The output geometry representation depends on the provided `flag`: + +- `0` - a GeometryCollection of triangular Polygons (default option) +- `1` - a MultiLinestring of the edges of the triangulation + +Format: + +`ST_DelaunayTriangles(geometry: Geometry)` + +`ST_DelaunayTriangles(geometry: Geometry, tolerance: Double)` + +`ST_DelaunayTriangles(geometry: Geometry, tolerance: Double, flag: Integer)` + +Since: `vTBD` + +SQL Example + +```sql +SELECT ST_DelaunayTriangles( + ST_GeomFromWKT('POLYGON ((10 10, 15 30, 20 25, 25 35, 30 20, 40 30, 50 10, 45 5, 35 15, 30 5, 25 15, 20 10, 15 20, 10 10))') +) +``` + +Output: + +``` +GEOMETRYCOLLECTION (POLYGON ((15 30, 10 10, 15 20, 15 30)), POLYGON ((15 30, 15 20, 20 25, 15 30)), POLYGON ((15 30, 20 25, 25 35, 15 30)), POLYGON ((25 35, 20 25, 30 20, 25 35)), POLYGON ((25 35, 30 20, 40 30, 25 35)), POLYGON ((40 30, 30 20, 35 15, 40 30)), POLYGON ((40 30, 35 15, 50 10, 40 30)), POLYGON ((50 10, 35 15, 45 5, 50 10)), POLYGON ((30 5, 45 5, 35 15, 30 5)), POLYGON ((30 5, 35 15, 25 15, 30 5)), POLYGON ((30 5, 25 15, 20 10, 30 5)), POLYGON ((30 5, 20 10, 10 10, 30 5)), PO [...] +``` + ## ST_Difference Introduction: Return the difference between geometry A and B (return part of geometry A that does not intersect geometry B) 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 91eebd9c4..c8ccd74c4 100644 --- a/flink/src/main/java/org/apache/sedona/flink/Catalog.java +++ b/flink/src/main/java/org/apache/sedona/flink/Catalog.java @@ -78,6 +78,7 @@ public class Catalog { new Functions.ST_H3ToGeom(), new Functions.ST_Dump(), new Functions.ST_DumpPoints(), + new Functions.ST_DelaunayTriangles(), new Functions.ST_EndPoint(), new Functions.ST_GeometryType(), new Functions.ST_Intersection(), 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..25976e034 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 @@ -1413,6 +1413,29 @@ public class Functions { } } + public static class ST_DelaunayTriangles 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(value = "Double") Double tolerance, + @DataTypeHint(value = "Integer") Integer flag) { + Geometry geometry = (Geometry) o; + return org.apache.sedona.common.Functions.delaunayTriangle(geometry, tolerance, flag); + } + + @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(value = "Double") Double tolerance) { + Geometry geometry = (Geometry) o; + return org.apache.sedona.common.Functions.delaunayTriangle(geometry, tolerance); + } + + @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.delaunayTriangle(geometry); + } + } + public static class ST_IsValidReason extends ScalarFunction { @DataTypeHint("String") public String 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 f128b31d2..74e0cf5dc 100644 --- a/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java +++ b/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java @@ -1555,6 +1555,22 @@ public class FunctionTest extends TestBase{ } + @Test + public void testDelaunayTriangle() { + Table polyTable = tableEnv.sqlQuery("SELECT ST_GeomFromWKT('MULTIPOLYGON (((10 10, 10 20, 20 20, 20 10, 10 10)),((25 10, 25 20, 35 20, 35 10, 25 10)))') AS geom"); + String actual = ((Geometry) first(polyTable.select(call(Functions.ST_DelaunayTriangles.class.getSimpleName(), $("geom")))).getField(0)).toText(); + String expected = "GEOMETRYCOLLECTION (POLYGON ((10 20, 10 10, 20 10, 10 20)), POLYGON ((10 20, 20 10, 20 20, 10 20)), POLYGON ((20 20, 20 10, 25 10, 20 20)), POLYGON ((20 20, 25 10, 25 20, 20 20)), POLYGON ((25 20, 25 10, 35 10, 25 20)), POLYGON ((25 20, 35 10, 35 20, 25 20)))"; + assertEquals(expected, actual); + + actual = ((Geometry) first(polyTable.select(call(Functions.ST_DelaunayTriangles.class.getSimpleName(), $("geom"), 20))).getField(0)).toText(); + expected = "GEOMETRYCOLLECTION (POLYGON ((10 20, 10 10, 35 10, 10 20)))"; + assertEquals(expected, actual); + + actual = ((Geometry) first(polyTable.select(call(Functions.ST_DelaunayTriangles.class.getSimpleName(), $("geom"), 20, 1))).getField(0)).toText(); + expected = "MULTILINESTRING ((10 20, 35 10), (10 10, 10 20), (10 10, 35 10))"; + assertEquals(expected, actual); + } + @Test public void testHausdorffDistance() { Table polyTable = tableEnv.sqlQuery("SELECT ST_GeomFromWKT('POINT (0.0 1.0)') AS g1, ST_GeomFromWKT('LINESTRING (0 0, 1 0, 2 0, 3 0, 4 0, 5 0)') AS g2"); diff --git a/python/sedona/sql/st_functions.py b/python/sedona/sql/st_functions.py index 1a3dd9809..3f107d10b 100644 --- a/python/sedona/sql/st_functions.py +++ b/python/sedona/sql/st_functions.py @@ -1818,6 +1818,30 @@ def ST_Degrees(angleInRadian: Union[ColumnOrName, float]) -> Column: :return: Angle in Degrees """ return _call_st_function("ST_Degrees", angleInRadian) + +@validate_argument_types +def ST_DelaunayTriangles(geometry: ColumnOrName, tolerance: Optional[Union[ColumnOrName, float]] = None, flag: Optional[Union[ColumnOrName, int]] = None) -> Column: + """ + Computes the Delaunay Triangles of the vertices of the input geometry. + + :param geometry: Input geometry + :type geometry: ColumnOrName + :param tolerance: + :type tolerance: ColumnOrName or float + :param flag: Selects the output type + :type flag: ColumnOrName or int + :return: Delaunay triangles of the input geometry + :rtype: ColumnOrName + """ + + if flag is None and tolerance is None: + args = (geometry) + elif flag is None: + args = (geometry, tolerance) + else: + args = (geometry, tolerance, flag) + return _call_st_function("ST_DelaunayTriangles", args) + @validate_argument_types def ST_HausdorffDistance(g1: ColumnOrName, g2: ColumnOrName, densityFrac: Optional[Union[ColumnOrName, float]] = -1) -> Column: """ diff --git a/python/tests/sql/test_dataframe_api.py b/python/tests/sql/test_dataframe_api.py index 2e201e538..997325349 100644 --- a/python/tests/sql/test_dataframe_api.py +++ b/python/tests/sql/test_dataframe_api.py @@ -118,6 +118,7 @@ test_configurations = [ (stf.ST_DistanceSpheroid, ("point", "point"), "point_geom", "", 0.0), (stf.ST_DistanceSphere, ("point", "point"), "point_geom", "", 0.0), (stf.ST_DistanceSphere, ("point", "point", 6378137.0), "point_geom", "", 0.0), + (stf.ST_DelaunayTriangles, ("multipoint", ), "multipoint_geom", "", "GEOMETRYCOLLECTION (POLYGON ((10 40, 20 20, 40 30, 10 40)), POLYGON ((40 30, 20 20, 30 10, 40 30)))"), (stf.ST_Dump, ("geom",), "multipoint", "", ["POINT (0 0)", "POINT (1 1)"]), (stf.ST_DumpPoints, ("line",), "linestring_geom", "", ["POINT (0 0)", "POINT (1 0)", "POINT (2 0)", "POINT (3 0)", "POINT (4 0)", "POINT (5 0)"]), (stf.ST_EndPoint, ("line",), "linestring_geom", "", "POINT (5 0)"), @@ -317,6 +318,7 @@ wrong_type_configurations = [ (stf.ST_Distance, ("", None)), (stf.ST_Dump, (None,)), (stf.ST_DumpPoints, (None,)), + (stf.ST_DelaunayTriangles, (None,)), (stf.ST_EndPoint, (None,)), (stf.ST_Envelope, (None,)), (stf.ST_ExteriorRing, (None,)), diff --git a/python/tests/sql/test_function.py b/python/tests/sql/test_function.py index fcedfb794..91bc38dd3 100644 --- a/python/tests/sql/test_function.py +++ b/python/tests/sql/test_function.py @@ -420,6 +420,12 @@ class TestPredicateJoin(TestBase): diff = self.spark.sql("select ST_Difference(a,b) from test_diff") assert diff.take(1)[0][0].wkt == "POLYGON EMPTY" + def test_st_delaunay_triangles(self): + baseDf = self.spark.sql("SELECT ST_GeomFromWKT('MULTIPOLYGON (((10 10, 10 20, 20 20, 20 10, 10 10)),((25 10, 25 20, 35 20, 35 10, 25 10)))') AS geom") + actual = baseDf.selectExpr("ST_DelaunayTriangles(geom)").take(1)[0][0].wkt + expected = "GEOMETRYCOLLECTION (POLYGON ((10 20, 10 10, 20 10, 10 20)), POLYGON ((10 20, 20 10, 20 20, 10 20)), POLYGON ((20 20, 20 10, 25 10, 20 20)), POLYGON ((20 20, 25 10, 25 20, 20 20)), POLYGON ((25 20, 25 10, 35 10, 25 20)), POLYGON ((25 20, 35 10, 35 20, 25 20)))" + assert expected == actual + def test_st_sym_difference_part_of_right_overlaps_left(self): test_table = self.spark.sql( "select ST_GeomFromWKT('POLYGON ((-1 -1, 1 -1, 1 1, -1 1, -1 -1))') as a,ST_GeomFromWKT('POLYGON ((0 -2, 2 -2, 2 0, 0 0, 0 -2))') as b") 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..6aa152116 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 @@ -315,6 +315,15 @@ public class TestFunctions extends TestBase { ); } + @Test + public void test_ST_DelaunayTriangles() { + registerUDF("ST_DelaunayTriangles", byte[].class); + verifySqlSingleRes( + "select sedona.ST_AsText(sedona.ST_DelaunayTriangles(sedona.ST_GeomFromText('POLYGON ((10 10, 15 30, 20 25, 25 35, 30 20, 40 30, 50 10, 45 5, 35 15, 30 5, 25 15, 20 10, 15 20, 10 10))')))", + "GEOMETRYCOLLECTION (POLYGON ((15 30, 10 10, 15 20, 15 30)), POLYGON ((15 30, 15 20, 20 25, 15 30)), POLYGON ((15 30, 20 25, 25 35, 15 30)), POLYGON ((25 35, 20 25, 30 20, 25 35)), POLYGON ((25 35, 30 20, 40 30, 25 35)), POLYGON ((40 30, 30 20, 35 15, 40 30)), POLYGON ((40 30, 35 15, 50 10, 40 30)), POLYGON ((50 10, 35 15, 45 5, 50 10)), POLYGON ((30 5, 45 5, 35 15, 30 5)), POLYGON ((30 5, 35 15, 25 15, 30 5)), POLYGON ((30 5, 25 15, 20 10, 30 5)), POLYGON ((30 5, 20 10, [...] + ); + } + @Test public void test_ST_Degrees() { registerUDF("ST_Degrees", 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 6f9448783..117a75a2b 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 @@ -320,6 +320,16 @@ public class TestFunctionsV2 "POLYGON((0 -3,-3 -3,-3 3,0 3,0 -3))" ); } + + @Test + public void test_ST_DelaunayTriangles() { + registerUDFV2("ST_DelaunayTriangles", String.class); + verifySqlSingleRes( + "select ST_AsText(sedona.ST_DelaunayTriangles(ST_GeomFromText('POLYGON ((10 10, 15 30, 20 25, 25 35, 30 20, 40 30, 50 10, 45 5, 35 15, 30 5, 25 15, 20 10, 15 20, 10 10))')))", + "GEOMETRYCOLLECTION(POLYGON((15 30,10 10,15 20,15 30)),POLYGON((15 30,15 20,20 25,15 30)),POLYGON((15 30,20 25,25 35,15 30)),POLYGON((25 35,20 25,30 20,25 35)),POLYGON((25 35,30 20,40 30,25 35)),POLYGON((40 30,30 20,35 15,40 30)),POLYGON((40 30,35 15,50 10,40 30)),POLYGON((50 10,35 15,45 5,50 10)),POLYGON((30 5,45 5,35 15,30 5)),POLYGON((30 5,35 15,25 15,30 5)),POLYGON((30 5,25 15,20 10,30 5)),POLYGON((30 5,20 10,10 10,30 5)),POLYGON((10 10,20 10,15 20,10 10)),POLYGON((15 [...] + ); + } + @Test public void test_ST_Dimension() { registerUDFV2("ST_Dimension", 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 db4d394e1..b4e46bca2 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 @@ -355,6 +355,35 @@ public class UDFs { ); } + @UDFAnnotations.ParamMeta(argNames = {"geometry"}) + public static byte[] ST_DelaunayTriangles(byte[] geometry) { + return GeometrySerde.serialize( + Functions.delaunayTriangle( + GeometrySerde.deserialize(geometry) + ) + ); + } + + @UDFAnnotations.ParamMeta(argNames = {"geometry", "tolerance"}) + public static byte[] ST_DelaunayTriangles(byte[] geometry, double tolerance) { + return GeometrySerde.serialize( + Functions.delaunayTriangle( + GeometrySerde.deserialize(geometry), + tolerance + ) + ); + } + + @UDFAnnotations.ParamMeta(argNames = {"geometry", "tolerance", "flag"}) + public static byte[] ST_DelaunayTriangles(byte[] geometry, double tolerance, int flag) { + return GeometrySerde.serialize( + Functions.delaunayTriangle( + GeometrySerde.deserialize(geometry), + tolerance, flag + ) + ); + } + @UDFAnnotations.ParamMeta(argNames = {"geometry"}) public static Integer ST_Dimension(byte[] geometry) { return Functions.dimension( 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..8666b6d9e 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 @@ -354,6 +354,35 @@ public class UDFsV2 ); } + @UDFAnnotations.ParamMeta(argNames = {"geometry"}, argTypes = {"Geometry"}, returnTypes = "Geometry") + public static String ST_DelaunayTriangles(String geometry) { + return GeometrySerde.serGeoJson( + Functions.delaunayTriangle( + GeometrySerde.deserGeoJson(geometry) + ) + ); + } + + @UDFAnnotations.ParamMeta(argNames = {"geometry", "tolerance"}, argTypes = {"Geometry", "double"}, returnTypes = "Geometry") + public static String ST_DelaunayTriangles(String geometry, double tolerance) { + return GeometrySerde.serGeoJson( + Functions.delaunayTriangle( + GeometrySerde.deserGeoJson(geometry), + tolerance + ) + ); + } + + @UDFAnnotations.ParamMeta(argNames = {"geometry", "tolerance", "flag"}, argTypes = {"Geometry", "double", "int"}, returnTypes = "Geometry") + public static String ST_DelaunayTriangles(String geometry, double tolerance, int flag) { + return GeometrySerde.serGeoJson( + Functions.delaunayTriangle( + GeometrySerde.deserGeoJson(geometry), + tolerance, flag + ) + ); + } + @UDFAnnotations.ParamMeta(argNames = {"leftGeometry", "rightGeometry"}, argTypes = {"Geometry", "Geometry"}, returnTypes = "Geometry") public static String ST_Difference(String leftGeometry, String rightGeometry) { 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 6d16bc9be..71d4915eb 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 @@ -214,6 +214,7 @@ object Catalog { function[ST_BoundingDiagonal](), function[ST_Angle](), function[ST_Degrees](), + function[ST_DelaunayTriangles](), function[ST_HausdorffDistance](-1), function[ST_DWithin](), function[ST_IsValidReason](), 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..5c1097026 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 @@ -1376,6 +1376,15 @@ case class ST_Degrees(inputExpressions: Seq[Expression]) } } +case class ST_DelaunayTriangles(inputExpressions: Seq[Expression]) + extends InferredExpression(inferrableFunction3(Functions.delaunayTriangle), + inferrableFunction2(Functions.delaunayTriangle), + inferrableFunction1(Functions.delaunayTriangle)) with FoldableExpression { + protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = { + copy(inputExpressions = newChildren) + } +} + /** * Return the number of ddimensions in geometry. * 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..df0c489ce 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 @@ -530,6 +530,14 @@ object st_functions extends DataFrameAPI { def ST_Degrees(angleInRadian: Column): Column = wrapExpression[ST_Degrees](angleInRadian) def ST_Degrees(angleInRadian: Double): Column = wrapExpression[ST_Degrees](angleInRadian) + + def ST_DelaunayTriangles(geometry: Column, tolerance: Column, flags: Column): Column = wrapExpression[ST_DelaunayTriangles](geometry, tolerance, flags) + def ST_DelaunayTriangles(geometry: String, tolerance: Double, flags: Integer): Column = wrapExpression[ST_DelaunayTriangles](geometry, tolerance, flags) + def ST_DelaunayTriangles(geometry: Column, tolerance: Column): Column = wrapExpression[ST_DelaunayTriangles](geometry, tolerance) + def ST_DelaunayTriangles(geometry: String, tolerance: Double): Column = wrapExpression[ST_DelaunayTriangles](geometry, tolerance) + def ST_DelaunayTriangles(geometry: Column): Column = wrapExpression[ST_DelaunayTriangles](geometry) + def ST_DelaunayTriangles(geometry: String): Column = wrapExpression[ST_DelaunayTriangles](geometry) + def ST_HausdorffDistance(g1: Column, g2: Column) = wrapExpression[ST_HausdorffDistance](g1, g2, -1) def ST_HausdorffDistance(g1: String, g2: String) = wrapExpression[ST_HausdorffDistance](g1, g2, -1); 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 4a1bf9f98..dcc404dbb 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 @@ -1678,6 +1678,21 @@ class dataFrameAPITestScala extends TestBaseScala { assertEquals(expectedDegrees, actualDegrees, 1e-9) } + it("Should pass ST_DelaunayTriangles") { + val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('MULTIPOLYGON (((10 10, 10 20, 20 20, 20 10, 10 10)),((25 10, 25 20, 35 20, 35 10, 25 10)))') AS geom") + var actual = baseDf.select(ST_DelaunayTriangles("geom")).first().get(0).asInstanceOf[Geometry].toText + var expected = "GEOMETRYCOLLECTION (POLYGON ((10 20, 10 10, 20 10, 10 20)), POLYGON ((10 20, 20 10, 20 20, 10 20)), POLYGON ((20 20, 20 10, 25 10, 20 20)), POLYGON ((20 20, 25 10, 25 20, 20 20)), POLYGON ((25 20, 25 10, 35 10, 25 20)), POLYGON ((25 20, 35 10, 35 20, 25 20)))" + assertEquals(expected, actual) + + actual = baseDf.select(ST_DelaunayTriangles("geom", 20)).first().get(0).asInstanceOf[Geometry].toText + expected = "GEOMETRYCOLLECTION (POLYGON ((10 20, 10 10, 35 10, 10 20)))" + assertEquals(expected, actual) + + actual = baseDf.select(ST_DelaunayTriangles("geom", 20, 1)).first().get(0).asInstanceOf[Geometry].toText + expected = "MULTILINESTRING ((10 20, 35 10), (10 10, 10 20), (10 10, 35 10))" + assertEquals(expected, actual) + } + it("Passed ST_HausdorffDistance") { val polyDf = sparkSession.sql("SELECT ST_GeomFromWKT('POLYGON ((1 2, 2 1, 2 0, 4 1, 1 2))') AS g1, " + "ST_GeomFromWKT('MULTILINESTRING ((1 1, 2 1, 4 4, 5 5), (10 10, 11 11, 12 12, 14 14), (-11 -20, -11 -21, -15 -19))') AS g2") 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..3e801db0f 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 @@ -2524,6 +2524,21 @@ class functionTestScala extends TestBaseScala with Matchers with GeometrySample } } + it("Should pass ST_DelaunayTriangles") { + val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('MULTIPOLYGON (((10 10, 10 20, 20 20, 20 10, 10 10)),((25 10, 25 20, 35 20, 35 10, 25 10)))') AS geom") + var actual = baseDf.selectExpr("ST_DelaunayTriangles(geom)").first().get(0).asInstanceOf[Geometry].toText + var expected = "GEOMETRYCOLLECTION (POLYGON ((10 20, 10 10, 20 10, 10 20)), POLYGON ((10 20, 20 10, 20 20, 10 20)), POLYGON ((20 20, 20 10, 25 10, 20 20)), POLYGON ((20 20, 25 10, 25 20, 20 20)), POLYGON ((25 20, 25 10, 35 10, 25 20)), POLYGON ((25 20, 35 10, 35 20, 25 20)))" + assertEquals(expected, actual) + + actual = baseDf.selectExpr("ST_DelaunayTriangles(geom, 20)").first().get(0).asInstanceOf[Geometry].toText + expected = "GEOMETRYCOLLECTION (POLYGON ((10 20, 10 10, 35 10, 10 20)))" + assertEquals(expected, actual) + + actual = baseDf.selectExpr("ST_DelaunayTriangles(geom, 20, 1)").first().get(0).asInstanceOf[Geometry].toText + expected = "MULTILINESTRING ((10 20, 35 10), (10 10, 10 20), (10 10, 35 10))" + assertEquals(expected, actual) + } + it ("should pass ST_Angle - 2 lines") { val geomTestCases = Map( ("'LINESTRING (0 0, 1 1)'", "'LINESTRING (0 0, 3 2)'") -> (0.19739555984988044, 11.309932474020195)
