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