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 4e4cf5910b..3e1880b3be 100644 --- a/common/src/main/java/org/apache/sedona/common/Functions.java +++ b/common/src/main/java/org/apache/sedona/common/Functions.java @@ -218,6 +218,51 @@ public static int bestSRID(Geometry geometry) { return Spheroid.EPSG_WORLD_MERCATOR; } + /** + * Checks if a geometry crosses the International Date Line. + * + * @param geometry The geometry to check. + * @return True if the geometry crosses the Date Line, false otherwise. + */ + public static boolean crossesDateLine(Geometry geometry) { + if (geometry == null || geometry.isEmpty()) { + return false; + } + + CoordinateSequenceFilter filter = new CoordinateSequenceFilter() { + private Coordinate previous = null; + private boolean crossesDateLine = false; + + @Override + public void filter(CoordinateSequence seq, int i) { + if (i == 0) { + previous = seq.getCoordinateCopy(i); + return; + } + + Coordinate current = seq.getCoordinateCopy(i); + if (Math.abs(current.x - previous.x) > 180) { + crossesDateLine = true; + } + + previous = current; + } + + @Override + public boolean isDone() { + return crossesDateLine; + } + + @Override + public boolean isGeometryChanged() { + return false; + } + }; + + geometry.apply(filter); + return filter.isDone(); + } + public static Geometry envelope(Geometry geometry) { return geometry.getEnvelope(); } diff --git a/common/src/main/java/org/apache/sedona/common/Predicates.java b/common/src/main/java/org/apache/sedona/common/Predicates.java index df94a0c5d0..03a1951876 100644 --- a/common/src/main/java/org/apache/sedona/common/Predicates.java +++ b/common/src/main/java/org/apache/sedona/common/Predicates.java @@ -13,9 +13,11 @@ */ package org.apache.sedona.common; -import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.*; import org.apache.sedona.common.sphere.Spheroid; +import java.util.concurrent.atomic.AtomicBoolean; + public class Predicates { public static boolean contains(Geometry leftGeometry, Geometry rightGeometry) { return leftGeometry.contains(rightGeometry); @@ -62,4 +64,5 @@ public static boolean dWithin(Geometry leftGeometry, Geometry rightGeometry, dou return leftGeometry.isWithinDistance(rightGeometry, distance); } } + } diff --git a/common/src/test/java/org/apache/sedona/common/PredicatesTest.java b/common/src/test/java/org/apache/sedona/common/PredicatesTest.java index ba4194b9d4..4e6ea7155c 100644 --- a/common/src/test/java/org/apache/sedona/common/PredicatesTest.java +++ b/common/src/test/java/org/apache/sedona/common/PredicatesTest.java @@ -22,7 +22,11 @@ import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.GeometryFactory; +import org.locationtech.jts.io.ParseException; +import static org.apache.sedona.common.Constructors.geomFromEWKT; +import static org.apache.sedona.common.Constructors.geomFromWKT; +import static org.apache.sedona.common.Functions.crossesDateLine; import static org.junit.Assert.*; public class PredicatesTest extends TestBase { @@ -72,5 +76,33 @@ public void testDWithinSpheroid() { assertTrue(actual); } + @Test + public void testCrossesDateLine() throws ParseException { + Geometry geom1 = geomFromEWKT("LINESTRING(170 30, -170 30)"); + Geometry geom2 = geomFromEWKT("LINESTRING(-120 30, -130 40)"); + Geometry geom3 = geomFromEWKT("POLYGON((175 10, -175 10, -175 -10, 175 -10, 175 10))"); + Geometry geom4 = geomFromEWKT("POLYGON((-120 10, -130 10, -130 -10, -120 -10, -120 10))"); + Geometry geom5 = geomFromEWKT("POINT(180 30)"); + Geometry geom6 = geomFromEWKT("POLYGON((160 20, 180 20, 180 -20, 160 -20, 160 20), (165 15, 175 15, 175 -15, 165 -15, 165 15))"); + Geometry geom8 = geomFromEWKT("POLYGON((170 -10, -170 -10, -170 10, 170 10, 170 -10), (175 -5, -175 -5, -175 5, 175 5, 175 -5))"); + + // Multi-geometry test cases + Geometry multiGeom1 = geomFromEWKT("MULTILINESTRING((170 30, -170 30), (-120 30, -130 40))"); + Geometry multiGeom2 = geomFromEWKT("MULTIPOLYGON(((175 10, -175 10, -175 -10, 175 -10, 175 10)), ((-120 10, -130 10, -130 -10, -120 -10, -120 10)))"); + Geometry multiGeom3 = geomFromEWKT("MULTIPOINT((180 30), (170 -20))"); + Geometry multiGeom4 = geomFromEWKT("MULTIPOLYGON(((160 20, 180 20, 180 -20, 160 -20, 160 20)), ((-120 10, -130 10, -130 -10, -120 -10, -120 10)))"); + + assertEquals(true, crossesDateLine(geom1)); + assertEquals(false, crossesDateLine(geom2)); + assertEquals(true, crossesDateLine(geom3)); + assertEquals(false, crossesDateLine(geom4)); + assertEquals(false, crossesDateLine(geom5)); + assertEquals(false, crossesDateLine(geom6)); + assertEquals(true, crossesDateLine(geom8)); + assertEquals(true, crossesDateLine(multiGeom1)); + assertEquals(true, crossesDateLine(multiGeom2)); + assertEquals(false, crossesDateLine(multiGeom3)); + assertEquals(false, crossesDateLine(multiGeom4)); + } } diff --git a/docs/api/flink/Function.md b/docs/api/flink/Function.md index 1565cdbfce..eebd3f9430 100644 --- a/docs/api/flink/Function.md +++ b/docs/api/flink/Function.md @@ -835,6 +835,35 @@ Output: 2 ``` +## ST_CrossesDateLine + +Introduction: This function determines if a given geometry crosses the International Date Line. It operates by checking if the difference in longitude between any pair of consecutive points in the geometry exceeds 180 degrees. If such a difference is found, it is assumed that the geometry crosses the Date Line. It returns true if the geometry crosses the Date Line, and false otherwise. + +!!!note + The function assumes that the provided geometry is in lon/lat coordinate reference system where longitude values range from -180 to 180 degrees. + +!!!note + For multi-geometries (e.g., MultiPolygon, MultiLineString), this function will return true if any one of the geometries within the multi-geometry crosses the International Date Line. + +Format: `ST_CrossesDateLine(geometry: Geometry)` + +Since: `v1.6.0` + +SQL Example: + +```sql +SELECT ST_CrossesDateLine(ST_GeomFromWKT('LINESTRING(170 30, -170 30)')) +``` + +Output: + +```sql +true +``` + +!!!Warning + For geometries that span more than 180 degrees in longitude without actually crossing the Date Line, this function may still return true, indicating a crossing. + ## ST_Dimension Introduction: Return the topological dimension of this Geometry object, which must be less than or equal to the coordinate dimension. OGC SPEC s2.1.1.1 - returns 0 for POINT, 1 for LINESTRING, 2 for POLYGON, and the largest dimension of the components of a GEOMETRYCOLLECTION. If the dimension is unknown (e.g. for an empty GEOMETRYCOLLECTION) 0 is returned. diff --git a/docs/api/snowflake/vector-data/Function.md b/docs/api/snowflake/vector-data/Function.md index 36a67eea90..ffb49685d0 100644 --- a/docs/api/snowflake/vector-data/Function.md +++ b/docs/api/snowflake/vector-data/Function.md @@ -600,6 +600,35 @@ Output: 2 ``` +## ST_CrossesDateLine + +Introduction: This function determines if a given geometry crosses the International Date Line. It operates by checking if the difference in longitude between any pair of consecutive points in the geometry exceeds 180 degrees. If such a difference is found, it is assumed that the geometry crosses the Date Line. It returns true if the geometry crosses the Date Line, and false otherwise. + +!!!note + The function assumes that the provided geometry is in lon/lat coordinate reference system where longitude values range from -180 to 180 degrees. + +!!!note + For multi-geometries (e.g., MultiPolygon, MultiLineString), this function will return true if any one of the geometries within the multi-geometry crosses the International Date Line. + +Format: `ST_CrossesDateLine(geometry: Geometry)` + +Since: `v1.6.0` + +SQL Example: + +```sql +SELECT ST_CrossesDateLine(ST_GeomFromWKT('LINESTRING(170 30, -170 30)')) +``` + +Output: + +```sql +true +``` + +!!!Warning + For geometries that span more than 180 degrees in longitude without actually crossing the Date Line, this function may still return true, indicating a crossing. + ## ST_Degrees Introduction: Convert an angle in radian to degrees. diff --git a/docs/api/sql/Function.md b/docs/api/sql/Function.md index e0a02fb234..745ed73517 100644 --- a/docs/api/sql/Function.md +++ b/docs/api/sql/Function.md @@ -842,6 +842,35 @@ Output: 2 ``` +## ST_CrossesDateLine + +Introduction: This function determines if a given geometry crosses the International Date Line. It operates by checking if the difference in longitude between any pair of consecutive points in the geometry exceeds 180 degrees. If such a difference is found, it is assumed that the geometry crosses the Date Line. It returns true if the geometry crosses the Date Line, and false otherwise. + +!!!note + The function assumes that the provided geometry is in lon/lat coordinate reference system where longitude values range from -180 to 180 degrees. + +!!!note + For multi-geometries (e.g., MultiPolygon, MultiLineString), this function will return true if any one of the geometries within the multi-geometry crosses the International Date Line. + +Format: `ST_CrossesDateLine(geometry: Geometry)` + +Since: `v1.6.0` + +SQL Example: + +```sql +SELECT ST_CrossesDateLine(ST_GeomFromWKT('LINESTRING(170 30, -170 30)')) +``` + +Output: + +```sql +true +``` + +!!!Warning + For geometries that span more than 180 degrees in longitude without actually crossing the Date Line, this function may still return true, indicating a crossing. + ## ST_Degrees Introduction: Convert an angle in radian to degrees. 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 67c0b5aa61..7b32e269c1 100644 --- a/flink/src/main/java/org/apache/sedona/flink/Catalog.java +++ b/flink/src/main/java/org/apache/sedona/flink/Catalog.java @@ -53,6 +53,7 @@ public static UserDefinedFunction[] getFuncs() { new Functions.ST_CollectionExtract(), new Functions.ST_ConcaveHull(), new Functions.ST_ConvexHull(), + new Functions.ST_CrossesDateLine(), new Functions.ST_Envelope(), new Functions.ST_Difference(), new Functions.ST_Dimension(), 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 d83e708a33..e690ea1780 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 @@ -172,6 +172,21 @@ public Geometry eval(@DataTypeHint(value = "RAW", bridgedTo = org.locationtech.j } } + public static class ST_CrossesDateLine extends ScalarFunction + { + /** + * Constructor for relation checking without duplicate removal + */ + public ST_CrossesDateLine() {} + + @DataTypeHint("Boolean") + public Boolean eval(@DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class) Object o) + { + Geometry geom = (Geometry) o; + return org.apache.sedona.common.Functions.crossesDateLine(geom); + } + } + public static class ST_Envelope 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 451d750a8c..daa6459b1d 100644 --- a/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java +++ b/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java @@ -199,6 +199,33 @@ public void testConvexHull() { assertEquals("POLYGON ((0 0, 1 2, 3 2, 5 0, 0 0))", result.toString()); } + @Test + public void testCrossesDateLine() { + // Test line crossing the Date Line + Table table1 = tableEnv.sqlQuery("SELECT ST_GeomFromWKT('LINESTRING(170 30, -170 30)') AS geom"); + table1 = table1.select(call("ST_CrossesDateLine", $("geom"))); + Boolean actual1 = (Boolean) first(table1).getField(0); + assertEquals(true, actual1); + + // Test line not crossing the Date Line + Table table2 = tableEnv.sqlQuery("SELECT ST_GeomFromWKT('LINESTRING(-120 30, -130 40)') AS geom"); + table2 = table2.select(call("ST_CrossesDateLine", $("geom"))); + Boolean actual2 = (Boolean) first(table2).getField(0); + assertEquals(false, actual2); + + // Test polygon crossing the Date Line + Table table3 = tableEnv.sqlQuery("SELECT ST_GeomFromWKT('POLYGON((175 10, -175 10, -175 -10, 175 -10, 175 10))') AS geom"); + table3 = table3.select(call("ST_CrossesDateLine", $("geom"))); + Boolean actual3 = (Boolean) first(table3).getField(0); + assertEquals(true, actual3); + + // Test polygon not crossing the Date Line + Table table4 = tableEnv.sqlQuery("SELECT ST_GeomFromWKT('POLYGON((-120 10, -130 10, -130 -10, -120 -10, -120 10))') AS geom"); + table4 = table4.select(call("ST_CrossesDateLine", $("geom"))); + Boolean actual4 = (Boolean) first(table4).getField(0); + assertEquals(false, actual4); + } + @Test public void testDifference() { Table lineTable = tableEnv.sqlQuery("SELECT ST_GeomFromWKT('LINESTRING(50 100, 50 200)') AS g1, ST_GeomFromWKT('LINESTRING(50 50, 50 150)') as g2"); diff --git a/python/sedona/sql/st_functions.py b/python/sedona/sql/st_functions.py index 91a787faa0..456b4dc03f 100644 --- a/python/sedona/sql/st_functions.py +++ b/python/sedona/sql/st_functions.py @@ -47,6 +47,7 @@ "ST_ClosestPoint", "ST_ConcaveHull", "ST_ConvexHull", + "ST_CrossesDateLine", "ST_Difference", "ST_Dimension", "ST_Distance", @@ -457,6 +458,17 @@ def ST_ConvexHull(geometry: ColumnOrName) -> Column: """ return _call_st_function("ST_ConvexHull", geometry) +@validate_argument_types +def ST_CrossesDateLine(a: ColumnOrName) -> Column: + """Check whether geometry a crosses the International Date Line. + + :param a: Geometry to check crossing with. + :type a: ColumnOrName + :return: True if geometry a cross the dateline. + :rtype: Column + """ + return _call_st_function("ST_CrossesDateLine", (a)) + @validate_argument_types def ST_Dimension(geometry: ColumnOrName): """Calculate the inherent dimension of a geometry column. diff --git a/python/sedona/sql/st_predicates.py b/python/sedona/sql/st_predicates.py index 4197d45a5e..ee8d086781 100644 --- a/python/sedona/sql/st_predicates.py +++ b/python/sedona/sql/st_predicates.py @@ -67,7 +67,6 @@ def ST_Crosses(a: ColumnOrName, b: ColumnOrName) -> Column: """ return _call_predicate_function("ST_Crosses", (a, b)) - @validate_argument_types def ST_Disjoint(a: ColumnOrName, b: ColumnOrName) -> Column: """Check whether two geometries are disjoint. diff --git a/python/tests/sql/test_dataframe_api.py b/python/tests/sql/test_dataframe_api.py index 67415563d6..69802c13df 100644 --- a/python/tests/sql/test_dataframe_api.py +++ b/python/tests/sql/test_dataframe_api.py @@ -86,6 +86,7 @@ (stf.ST_ConcaveHull, ("geom", 1.0, True), "triangle_geom", "", "POLYGON ((1 1, 1 0, 0 0, 1 1))"), (stf.ST_ConvexHull, ("geom",), "triangle_geom", "", "POLYGON ((0 0, 1 1, 1 0, 0 0))"), (stf.ST_CoordDim, ("point",), "point_geom", "", 2), + (stf.ST_CrossesDateLine, ("line",), "line_crossing_dateline", "", True), (stf.ST_Difference, ("a", "b"), "overlapping_polys", "", "POLYGON ((1 0, 0 0, 0 1, 1 1, 1 0))"), (stf.ST_Dimension, ("geom",), "geometry_geom_collection", "", 1), (stf.ST_Distance, ("a", "b"), "two_points", "", 3.0), @@ -241,6 +242,7 @@ (stf.ST_CollectionExtract, (None,)), (stf.ST_ConcaveHull, (None, 1.0)), (stf.ST_ConvexHull, (None,)), + (stf.ST_CrossesDateLine, (None,)), (stf.ST_Difference, (None, "b")), (stf.ST_Difference, ("", None)), (stf.ST_Distance, (None, "")), @@ -445,6 +447,8 @@ def base_df(self, request): return TestDataFrameAPI.spark.sql("SELECT ST_GeomFromWKT('POINT (0 0)') AS origin, ST_GeomFromWKT('POINT (1 0)') as point") elif request.param == "ny_seattle": return TestDataFrameAPI.spark.sql("SELECT ST_GeomFromWKT('POINT (-122.335167 47.608013)') AS seattle, ST_GeomFromWKT('POINT (-73.935242 40.730610)') as ny") + elif request.param == "line_crossing_dateline": + return TestDataFrameAPI.spark.sql("SELECT ST_GeomFromWKT('LINESTRING (179.95 30, -179.95 30)') AS line") raise ValueError(f"Invalid base_df name passed: {request.param}") def _id_test_configuration(val): diff --git a/python/tests/sql/test_function.py b/python/tests/sql/test_function.py index 8e678595b2..9f6035f012 100644 --- a/python/tests/sql/test_function.py +++ b/python/tests/sql/test_function.py @@ -159,6 +159,20 @@ def test_st_centroid(self): function_df = self.spark.sql("select ST_Centroid(polygondf.countyshape) from polygondf") function_df.show() + def test_st_crossesdateline(self): + crosses_test_table = self.spark.sql( + "select ST_GeomFromWKT('POLYGON((175 10, -175 10, -175 -10, 175 -10, 175 10))') as geom") + crosses_test_table.createOrReplaceTempView("crossesTesttable") + crosses = self.spark.sql("select(ST_CrossesDateLine(geom)) from crossesTesttable") + + not_crosses_test_table = self.spark.sql( + "select ST_GeomFromWKT('POLYGON((1 1, 4 1, 4 4, 1 4, 1 1))') as geom") + not_crosses_test_table.createOrReplaceTempView("notCrossesTesttable") + not_crosses = self.spark.sql("select(ST_CrossesDateLine(geom)) from notCrossesTesttable") + + assert crosses.take(1)[0][0] + assert not not_crosses.take(1)[0][0] + def test_st_length(self): polygon_wkt_df = self.spark.read.format("csv"). \ option("delimiter", "\t"). \ 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 b5ee84e47f..901e7db00c 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 @@ -271,6 +271,19 @@ public void test_ST_ConvexHull() { "POLYGON ((10 8, 20 30, 100 190, 150 10, 10 8))" ); } + + @Test + public void test_ST_CrossesDateLine() { + registerUDF("ST_CrossesDateLine", byte[].class); + verifySqlSingleRes( + "SELECT SEDONA.ST_CrossesDateLine(sedona.ST_GeomFromWKT('POLYGON((175 10, -175 10, -175 -10, 175 -10, 175 10))'))", + true + ); + verifySqlSingleRes( + "SELECT SEDONA.ST_CrossesDateLine(sedona.ST_GeomFromWKT('POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))'))", + false + ); + } @Test public void test_ST_Difference() { registerUDF("ST_Difference", 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 bb8fd0fb66..64104bd014 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 @@ -277,6 +277,19 @@ public void test_ST_ConvexHull() { "POLYGON((10 8,20 30,100 190,150 10,10 8))" ); } + + @Test + public void test_ST_CrossesDateLine() { + registerUDFV2("ST_CrossesDateLine", String.class); + verifySqlSingleRes( + "SELECT SEDONA.ST_CrossesDateLine( ST_GeometryFromWKT('POLYGON((175 10, -175 10, -175 -10, 175 -10, 175 10))'))", + true + ); + verifySqlSingleRes( + "SELECT SEDONA.ST_CrossesDateLine( ST_GeometryFromWKT('POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))'))", + false + ); + } @Test public void test_ST_Difference() { registerUDFV2("ST_Difference", 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 d2c89e0471..d7b853306b 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 @@ -296,6 +296,13 @@ public static boolean ST_Crosses(byte[] leftGeometry, byte[] rightGeometry) { ); } + @UDFAnnotations.ParamMeta(argNames = {"geometry"}) + public static boolean ST_CrossesDateLine(byte[] geometry) { + return Functions.crossesDateLine( + GeometrySerde.deserialize(geometry) + ); + } + @UDFAnnotations.ParamMeta(argNames = {"angleInRadian"}) public static double ST_Degrees(double angleInRadian) { return Functions.degrees(angleInRadian); 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 aad3bdbd5a..a13deef46e 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 @@ -310,6 +310,13 @@ public static boolean ST_Crosses(String leftGeometry, String rightGeometry) { ); } + @UDFAnnotations.ParamMeta(argNames = {"geometry"}, argTypes = {"Geometry"}) + public static boolean ST_CrossesDateLine(String geometry) { + return Functions.crossesDateLine( + GeometrySerde.deserGeoJson(geometry) + ); + } + @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 982d100621..db9fe9d099 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 @@ -87,6 +87,7 @@ object Catalog { function[ST_Touches](), function[ST_Overlaps](), function[ST_Crosses](), + function[ST_CrossesDateLine](), function[ST_IsSimple](), function[ST_MakeValid](false), function[ST_SimplifyPreserveTopology](), 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 f95a1ffbe7..73006d5a05 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 @@ -121,6 +121,14 @@ case class ST_ConvexHull(inputExpressions: Seq[Expression]) } } +case class ST_CrossesDateLine(inputExpressions: Seq[Expression]) + extends InferredExpression(Functions.crossesDateLine _) { + + protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = { + copy(inputExpressions = newChildren) + } +} + /** * Return the number of Points in geometry. * diff --git a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Predicates.scala b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Predicates.scala index c262d1579f..746cb8c1c8 100644 --- a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Predicates.scala +++ b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Predicates.scala @@ -166,7 +166,6 @@ case class ST_Crosses(inputExpressions: Seq[Expression]) } } - /** * Test if leftGeometry overlaps rightGeometry * 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 743fbe3463..bf9aebc8f3 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 @@ -101,6 +101,9 @@ object st_functions extends DataFrameAPI { def ST_ConvexHull(geometry: Column): Column = wrapExpression[ST_ConvexHull](geometry) def ST_ConvexHull(geometry: String): Column = wrapExpression[ST_ConvexHull](geometry) + def ST_CrossesDateLine(a: Column): Column = wrapExpression[ST_CrossesDateLine](a) + def ST_CrossesDateLine(a: String): Column = wrapExpression[ST_CrossesDateLine](a) + def ST_Difference(a: Column, b: Column): Column = wrapExpression[ST_Difference](a, b) def ST_Difference(a: String, b: String): Column = wrapExpression[ST_Difference](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 71b9659964..bf19c20059 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 @@ -599,6 +599,13 @@ class dataFrameAPITestScala extends TestBaseScala { assert(actualResult == expectedResult) } + it("Passed ST_CrossesDateLine") { + val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('POLYGON((175 10, -175 10, -175 -10, 175 -10, 175 10))') AS geom") + val df = baseDf.select(ST_CrossesDateLine("geom")) + val actualResult = df.take(1)(0).getBoolean(0) + assert(actualResult) + } + it("Passed ST_EndPoint") { val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('LINESTRING (0 0, 1 0)') AS geom") val df = baseDf.select(ST_EndPoint("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 8863fd4cb1..87fea2a25f 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 @@ -64,6 +64,19 @@ class functionTestScala extends TestBaseScala with Matchers with GeometrySample assert(functionDf.count() > 0); } + it("Passed ST_CrossesDateLine") { + var crossesTesttable = sparkSession.sql("select ST_GeomFromWKT('POLYGON((170 -10, -170 -10, -170 10, 170 10, 170 -10), (175 -5, -175 -5, -175 5, 175 5, 175 -5))') as geom") + crossesTesttable.createOrReplaceTempView("crossesTesttable") + var crosses = sparkSession.sql("select(ST_CrossesDateLine(geom)) from crossesTesttable") + + var notCrossesTesttable = sparkSession.sql("select ST_GeomFromWKT('POLYGON((1 1, 4 1, 4 4, 1 4, 1 1))') as geom") + notCrossesTesttable.createOrReplaceTempView("notCrossesTesttable") + var notCrosses = sparkSession.sql("select(ST_CrossesDateLine(geom)) from notCrossesTesttable") + + assert(crosses.take(1)(0).get(0).asInstanceOf[Boolean]) + assert(!notCrosses.take(1)(0).get(0).asInstanceOf[Boolean]) + } + it("Passed ST_Buffer") { val polygonWktDf = sparkSession.read.format("csv").option("delimiter", "\t").option("header", "false").load(mixedWktGeometryInputLocation) polygonWktDf.createOrReplaceTempView("polygontable")