You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sedona.apache.org by ji...@apache.org on 2024/02/29 00:59:14 UTC
(sedona) branch master updated: [SEDONA-508] Add ST_CrossesDateLine (#1259)
This is an automated email from the ASF dual-hosted git repository.
jiayu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sedona.git
The following commit(s) were added to refs/heads/master by this push:
new 7d293df83 [SEDONA-508] Add ST_CrossesDateLine (#1259)
7d293df83 is described below
commit 7d293df833fca76cec8357761f1a851ff0385dd8
Author: Pranav Toggi <pr...@gmail.com>
AuthorDate: Wed Feb 28 19:59:08 2024 -0500
[SEDONA-508] Add ST_CrossesDateLine (#1259)
* Implement ST_CrossesDateLine
* Add docs
* Refactor crossesDateLine(); Fix python test
* Port ST_CrossesDateLine from Predicates to Functions
* Fix typo
---
.../java/org/apache/sedona/common/Functions.java | 45 ++++++++++++++++++++++
.../java/org/apache/sedona/common/Predicates.java | 5 ++-
.../org/apache/sedona/common/PredicatesTest.java | 32 +++++++++++++++
docs/api/flink/Function.md | 29 ++++++++++++++
docs/api/snowflake/vector-data/Function.md | 29 ++++++++++++++
docs/api/sql/Function.md | 29 ++++++++++++++
.../main/java/org/apache/sedona/flink/Catalog.java | 1 +
.../apache/sedona/flink/expressions/Functions.java | 15 ++++++++
.../java/org/apache/sedona/flink/FunctionTest.java | 27 +++++++++++++
python/sedona/sql/st_functions.py | 12 ++++++
python/sedona/sql/st_predicates.py | 1 -
python/tests/sql/test_dataframe_api.py | 4 ++
python/tests/sql/test_function.py | 14 +++++++
.../sedona/snowflake/snowsql/TestFunctions.java | 13 +++++++
.../sedona/snowflake/snowsql/TestFunctionsV2.java | 13 +++++++
.../org/apache/sedona/snowflake/snowsql/UDFs.java | 7 ++++
.../apache/sedona/snowflake/snowsql/UDFsV2.java | 7 ++++
.../scala/org/apache/sedona/sql/UDF/Catalog.scala | 1 +
.../sql/sedona_sql/expressions/Functions.scala | 8 ++++
.../sql/sedona_sql/expressions/Predicates.scala | 1 -
.../sql/sedona_sql/expressions/st_functions.scala | 3 ++
.../apache/sedona/sql/dataFrameAPITestScala.scala | 7 ++++
.../org/apache/sedona/sql/functionTestScala.scala | 13 +++++++
23 files changed, 313 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 4e4cf5910..3e1880b3b 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 class Functions {
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 df94a0c5d..03a195187 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 class Predicates {
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 ba4194b9d..4e6ea7155 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.junit.Test;
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 class PredicatesTest extends TestBase {
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 1565cdbfc..eebd3f943 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 36a67eea9..ffb49685d 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 e0a02fb23..745ed7351 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 67c0b5aa6..7b32e269c 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 class Catalog {
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 d83e708a3..e690ea178 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 class Functions {
}
}
+ 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 451d750a8..daa6459b1 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 class FunctionTest extends TestBase{
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 91a787faa..456b4dc03 100644
--- a/python/sedona/sql/st_functions.py
+++ b/python/sedona/sql/st_functions.py
@@ -47,6 +47,7 @@ __all__ = [
"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 4197d45a5..ee8d08678 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 67415563d..69802c13d 100644
--- a/python/tests/sql/test_dataframe_api.py
+++ b/python/tests/sql/test_dataframe_api.py
@@ -86,6 +86,7 @@ test_configurations = [
(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 @@ wrong_type_configurations = [
(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 @@ class TestDataFrameAPI(TestBase):
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 8e678595b..9f6035f01 100644
--- a/python/tests/sql/test_function.py
+++ b/python/tests/sql/test_function.py
@@ -159,6 +159,20 @@ class TestPredicateJoin(TestBase):
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 b5ee84e47..901e7db00 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 class TestFunctions extends TestBase {
"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 bb8fd0fb6..64104bd01 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 class TestFunctionsV2
"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 d2c89e047..d7b853306 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 class UDFs {
);
}
+ @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 aad3bdbd5..a13deef46 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 class UDFsV2
);
}
+ @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 982d10062..db9fe9d09 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 f95a1ffbe..73006d5a0 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 c262d1579..746cb8c1c 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 743fbe346..bf9aebc8f 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 71b965996..bf19c2005 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 8863fd4cb..87fea2a25 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")