This is an automated email from the ASF dual-hosted git repository.

jiayu pushed a commit to branch SEDONA-593
in repository https://gitbox.apache.org/repos/asf/sedona.git

commit 28b65f16d0132aff1682324bba04887da64d55bc
Author: Furqaan Khan <[email protected]>
AuthorDate: Thu May 2 01:40:22 2024 -0400

    [TASK-131] Add ST_Relate (#180)
    
    * add: ST_Relate
    
    * fix: snowflake udf registration
---
 .../java/org/apache/sedona/common/Predicates.java  |  9 ++++
 .../org/apache/sedona/common/PredicatesTest.java   | 42 +++++++++++++++++++
 docs/api/flink/Predicate.md                        | 48 ++++++++++++++++++++++
 docs/api/snowflake/vector-data/Predicate.md        | 46 +++++++++++++++++++++
 docs/api/sql/Predicate.md                          | 48 ++++++++++++++++++++++
 .../main/java/org/apache/sedona/flink/Catalog.java |  1 +
 .../sedona/flink/expressions/Predicates.java       | 26 ++++++++++++
 .../org/apache/sedona/flink/PredicateTest.java     | 10 +++++
 python/sedona/sql/st_predicates.py                 | 17 ++++++++
 python/tests/sql/test_dataframe_api.py             |  4 ++
 python/tests/sql/test_predicate.py                 |  8 ++++
 .../sedona/snowflake/snowsql/TestPredicates.java   | 14 +++++++
 .../sedona/snowflake/snowsql/TestPredicatesV2.java | 16 ++++++++
 .../org/apache/sedona/snowflake/snowsql/UDFs.java  | 17 ++++++++
 .../apache/sedona/snowflake/snowsql/UDFsV2.java    | 17 ++++++++
 .../scala/org/apache/sedona/sql/UDF/Catalog.scala  |  1 +
 .../sql/sedona_sql/expressions/Predicates.scala    |  8 ++++
 .../sql/sedona_sql/expressions/st_predicates.scala |  5 +++
 .../apache/sedona/sql/dataFrameAPITestScala.scala  |  9 ++++
 .../org/apache/sedona/sql/predicateTestScala.scala |  9 ++++
 20 files changed, 355 insertions(+)

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 1027fe4be..2994731d6 100644
--- a/common/src/main/java/org/apache/sedona/common/Predicates.java
+++ b/common/src/main/java/org/apache/sedona/common/Predicates.java
@@ -15,6 +15,7 @@ package org.apache.sedona.common;
 
 import org.locationtech.jts.geom.*;
 import org.apache.sedona.common.sphere.Spheroid;
+import org.locationtech.jts.operation.relate.RelateOp;
 
 public class Predicates {
     public static boolean contains(Geometry leftGeometry, Geometry 
rightGeometry) {
@@ -63,4 +64,12 @@ public class Predicates {
         }
     }
 
+    public static String relate(Geometry leftGeometry, Geometry rightGeometry) 
{
+        return RelateOp.relate(leftGeometry, rightGeometry).toString();
+    }
+
+    public static boolean relate(Geometry leftGeometry, Geometry 
rightGeometry, String intersectionMatrix) {
+        String matrixFromGeom = relate(leftGeometry, rightGeometry);
+        return IntersectionMatrix.matches(matrixFromGeom, intersectionMatrix);
+    }
 }
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 4e6ea7155..87e16bd81 100644
--- a/common/src/test/java/org/apache/sedona/common/PredicatesTest.java
+++ b/common/src/test/java/org/apache/sedona/common/PredicatesTest.java
@@ -76,6 +76,48 @@ public class PredicatesTest extends TestBase {
         assertTrue(actual);
     }
 
+    @Test
+    public void testRelateString() throws ParseException {
+        Geometry geom1 = geomFromEWKT("POINT(1 2)");
+        Geometry geom2 = Functions.buffer(geomFromEWKT("POINT(1 2)"), 2);
+        String actual = Predicates.relate(geom1, geom2);
+        assertEquals("0FFFFF212", actual);
+
+        geom1 = geomFromEWKT("LINESTRING(1 2, 3 4)");
+        geom2 = geomFromEWKT("LINESTRING(5 6, 7 8)");
+        actual = Predicates.relate(geom1, geom2);
+        assertEquals("FF1FF0102", actual);
+
+        geom1 = geomFromEWKT("LINESTRING (1 1, 5 5)");
+        geom2 = geomFromEWKT("POLYGON ((3 3, 3 7, 7 7, 7 3, 3 3))");
+        actual = Predicates.relate(geom1, geom2);
+        assertEquals("1010F0212", actual);
+    }
+
+    @Test
+    public void testRelateBoolean() throws ParseException {
+        Geometry geom1 = geomFromEWKT("POINT(1 2)");
+        Geometry geom2 = Functions.buffer(geomFromEWKT("POINT(1 2)"), 2);
+        boolean actual = Predicates.relate(geom1, geom2, "0FFFFF212");
+        assertTrue(actual);
+
+        actual = Predicates.relate(geom1, geom2, "0F0FFF212");
+        assertFalse(actual);
+
+        geom1 = geomFromEWKT("LINESTRING(1 2, 3 4)");
+        geom2 = geomFromEWKT("LINESTRING(5 6, 7 8)");
+        actual = Predicates.relate(geom1, geom2, "FF1F***02");
+        assertTrue(actual);
+
+        actual = Predicates.relate(geom1, geom2, "FF10***02");
+        assertFalse(actual);
+
+        geom1 = geomFromEWKT("LINESTRING (1 1, 5 5)");
+        geom2 = geomFromEWKT("POLYGON ((3 3, 3 7, 7 7, 7 3, 3 3))");
+        actual = Predicates.relate(geom1, geom2, "1010F0212");
+        assertTrue(actual);
+    }
+
     @Test
     public void testCrossesDateLine() throws ParseException {
         Geometry geom1 = geomFromEWKT("LINESTRING(170 30, -170 30)");
diff --git a/docs/api/flink/Predicate.md b/docs/api/flink/Predicate.md
index c5780ba6c..5c5bc0343 100644
--- a/docs/api/flink/Predicate.md
+++ b/docs/api/flink/Predicate.md
@@ -158,6 +158,54 @@ Output:
 true
 ```
 
+## ST_Relate
+
+Introduction: The first variant of the function computes and returns the 
[Dimensionally Extended 9-Intersection Model 
(DE-9IM)](https://en.wikipedia.org/wiki/DE-9IM) matrix string representing the 
spatial relationship between the two input geometry objects.
+
+The second variant of the function evaluates whether the two input geometries 
satisfy a specific spatial relationship defined by the provided 
`intersectionMatrix` pattern.
+
+!!!Note
+    It is important to note that this function is not optimized for use in 
spatial join operations. Certain DE-9IM relationships can hold true for 
geometries that do not intersect or are disjoint. As a result, it is 
recommended to utilize other dedicated spatial functions specifically optimized 
for spatial join processing.
+
+Format:
+
+`ST_Relate(geom1: Geometry, geom2: Geometry)`
+
+`ST_Relate(geom1: Geometry, geom2: Geometry, intersectionMatrix: String)`
+
+Since: `vTBD`
+
+SQL Example
+
+```sql
+SELECT ST_Relate(
+        ST_GeomFromWKT('LINESTRING (1 1, 5 5)'),
+        ST_GeomFromWKT('POLYGON ((3 3, 3 7, 7 7, 7 3, 3 3))')
+)
+```
+
+Output:
+
+```
+1010F0212
+```
+
+SQL Example
+
+```sql
+SELECT ST_Relate(
+        ST_GeomFromWKT('LINESTRING (1 1, 5 5)'),
+        ST_GeomFromWKT('POLYGON ((3 3, 3 7, 7 7, 7 3, 3 3))'),
+       "1010F0212"
+)
+```
+
+Output:
+
+```
+true
+```
+
 ## ST_Touches
 
 Introduction: Return true if A touches B
diff --git a/docs/api/snowflake/vector-data/Predicate.md 
b/docs/api/snowflake/vector-data/Predicate.md
index 5a7724698..d6473a350 100644
--- a/docs/api/snowflake/vector-data/Predicate.md
+++ b/docs/api/snowflake/vector-data/Predicate.md
@@ -125,6 +125,52 @@ FROM geom
 WHERE ST_Overlaps(geom.geom_a, geom.geom_b)
 ```
 
+## ST_Relate
+
+Introduction: The first variant of the function computes and returns the 
[Dimensionally Extended 9-Intersection Model 
(DE-9IM)](https://en.wikipedia.org/wiki/DE-9IM) matrix string representing the 
spatial relationship between the two input geometry objects.
+
+The second variant of the function evaluates whether the two input geometries 
satisfy a specific spatial relationship defined by the provided 
`intersectionMatrix` pattern.
+
+!!!Note
+    It is important to note that this function is not optimized for use in 
spatial join operations. Certain DE-9IM relationships can hold true for 
geometries that do not intersect or are disjoint. As a result, it is 
recommended to utilize other dedicated spatial functions specifically optimized 
for spatial join processing.
+
+Format:
+
+`ST_Relate(geom1: Geometry, geom2: Geometry)`
+
+`ST_Relate(geom1: Geometry, geom2: Geometry, intersectionMatrix: String)`
+
+SQL Example
+
+```sql
+SELECT ST_Relate(
+        ST_GeomFromWKT('LINESTRING (1 1, 5 5)'),
+        ST_GeomFromWKT('POLYGON ((3 3, 3 7, 7 7, 7 3, 3 3))')
+)
+```
+
+Output:
+
+```
+1010F0212
+```
+
+SQL Example
+
+```sql
+SELECT ST_Relate(
+        ST_GeomFromWKT('LINESTRING (1 1, 5 5)'),
+        ST_GeomFromWKT('POLYGON ((3 3, 3 7, 7 7, 7 3, 3 3))'),
+       "1010F0212"
+)
+```
+
+Output:
+
+```
+true
+```
+
 ## ST_Touches
 
 Introduction: Return true if A touches B
diff --git a/docs/api/sql/Predicate.md b/docs/api/sql/Predicate.md
index 417f0114d..db3fbc888 100644
--- a/docs/api/sql/Predicate.md
+++ b/docs/api/sql/Predicate.md
@@ -190,6 +190,54 @@ Output:
 true
 ```
 
+## ST_Relate
+
+Introduction: The first variant of the function computes and returns the 
[Dimensionally Extended 9-Intersection Model 
(DE-9IM)](https://en.wikipedia.org/wiki/DE-9IM) matrix string representing the 
spatial relationship between the two input geometry objects.
+
+The second variant of the function evaluates whether the two input geometries 
satisfy a specific spatial relationship defined by the provided 
`intersectionMatrix` pattern.
+
+!!!Note
+    It is important to note that this function is not optimized for use in 
spatial join operations. Certain DE-9IM relationships can hold true for 
geometries that do not intersect or are disjoint. As a result, it is 
recommended to utilize other dedicated spatial functions specifically optimized 
for spatial join processing.
+
+Format:
+
+`ST_Relate(geom1: Geometry, geom2: Geometry)`
+
+`ST_Relate(geom1: Geometry, geom2: Geometry, intersectionMatrix: String)`
+
+Since: `vTBD`
+
+SQL Example
+
+```sql
+SELECT ST_Relate(
+        ST_GeomFromWKT('LINESTRING (1 1, 5 5)'),
+        ST_GeomFromWKT('POLYGON ((3 3, 3 7, 7 7, 7 3, 3 3))')
+)
+```
+
+Output:
+
+```
+1010F0212
+```
+
+SQL Example
+
+```sql
+SELECT ST_Relate(
+        ST_GeomFromWKT('LINESTRING (1 1, 5 5)'),
+        ST_GeomFromWKT('POLYGON ((3 3, 3 7, 7 7, 7 3, 3 3))'),
+       "1010F0212"
+)
+```
+
+Output:
+
+```
+true
+```
+
 ## ST_Touches
 
 Introduction: Return true if A touches 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 591b783d0..bdea8749e 100644
--- a/flink/src/main/java/org/apache/sedona/flink/Catalog.java
+++ b/flink/src/main/java/org/apache/sedona/flink/Catalog.java
@@ -197,6 +197,7 @@ public class Catalog {
                 new Predicates.ST_OrderingEquals(),
                 new Predicates.ST_Overlaps(),
                 new Predicates.ST_Touches(),
+                new Predicates.ST_Relate(),
                 new Predicates.ST_DWithin()
         };
     }
diff --git 
a/flink/src/main/java/org/apache/sedona/flink/expressions/Predicates.java 
b/flink/src/main/java/org/apache/sedona/flink/expressions/Predicates.java
index 0963999bc..d353cdfd1 100644
--- a/flink/src/main/java/org/apache/sedona/flink/expressions/Predicates.java
+++ b/flink/src/main/java/org/apache/sedona/flink/expressions/Predicates.java
@@ -214,6 +214,32 @@ public class Predicates {
         }
     }
 
+    public static class ST_Relate extends ScalarFunction {
+        /**
+         * Constructor for relation checking without duplicate removal
+         */
+        public ST_Relate() {
+        }
+
+        @DataTypeHint("String")
+        public String eval(@DataTypeHint(value = "RAW", bridgedTo = 
org.locationtech.jts.geom.Geometry.class) Object o1, @DataTypeHint(value = 
"RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class) Object o2)
+        {
+            Geometry geom1 = (Geometry) o1;
+            Geometry geom2 = (Geometry) o2;
+            return org.apache.sedona.common.Predicates.relate(geom1, geom2);
+        }
+
+        @DataTypeHint("Boolean")
+        public Boolean eval(@DataTypeHint(value = "RAW", bridgedTo = 
org.locationtech.jts.geom.Geometry.class) Object o1,
+                           @DataTypeHint(value = "RAW", bridgedTo = 
org.locationtech.jts.geom.Geometry.class) Object o2,
+                           @DataTypeHint("String") String IM)
+        {
+            Geometry geom1 = (Geometry) o1;
+            Geometry geom2 = (Geometry) o2;
+            return org.apache.sedona.common.Predicates.relate(geom1, geom2, 
IM);
+        }
+    }
+
     public static class ST_Touches
             extends ScalarFunction
     {
diff --git a/flink/src/test/java/org/apache/sedona/flink/PredicateTest.java 
b/flink/src/test/java/org/apache/sedona/flink/PredicateTest.java
index f4acafb56..561a86ec9 100644
--- a/flink/src/test/java/org/apache/sedona/flink/PredicateTest.java
+++ b/flink/src/test/java/org/apache/sedona/flink/PredicateTest.java
@@ -131,6 +131,16 @@ public class PredicateTest extends TestBase{
         assertEquals(true, actual);
     }
 
+    @Test
+    public void testRelate() {
+        Table table = tableEnv.sqlQuery("SELECT ST_GeomFromWKT('LINESTRING (1 
1, 5 5)') AS g1, ST_GeomFromWKT('POLYGON ((3 3, 3 7, 7 7, 7 3, 3 3))') as g2, 
'1010F0212' as im");
+        String actual = (String) 
first(table.select(call(Predicates.ST_Relate.class.getSimpleName(), $("g1"), 
$("g2")))).getField(0);
+        assertEquals("1010F0212", actual);
+
+        Boolean actualBoolean = (Boolean) 
first(table.select(call(Predicates.ST_Relate.class.getSimpleName(), $("g1"), 
$("g2"), $("im")))).getField(0);
+        assertEquals(true, actualBoolean);
+    }
+
     @Test
     public void testDWithin() {
         Table table = tableEnv.sqlQuery("SELECT ST_GeomFromWKT('POINT (0 0)') 
as origin, ST_GeomFromWKT('POINT (1 0)') as p1");
diff --git a/python/sedona/sql/st_predicates.py 
b/python/sedona/sql/st_predicates.py
index 5106e0473..4ffd721f6 100644
--- a/python/sedona/sql/st_predicates.py
+++ b/python/sedona/sql/st_predicates.py
@@ -143,6 +143,23 @@ def ST_Touches(a: ColumnOrName, b: ColumnOrName) -> Column:
     """
     return _call_predicate_function("ST_Touches", (a, b))
 
+@validate_argument_types
+def ST_Relate(a: ColumnOrName, b: ColumnOrName, intersectionMatrix: 
Optional[ColumnOrName] = None) -> Column:
+    """Check whether two geometries are related to each other.
+
+    :param a: One geometry column to check.
+    :type a: ColumnOrName
+    :param b: Other geometry column to check.
+    :type b: ColumnOrName
+    :param intersectionMatrix: intersectionMatrix column to check
+    :type intersectionMatrix: ColumnOrName
+    :return: True if a and b touch and False otherwise, as a boolean column.
+    :rtype: Column
+    """
+    args = (a, b) if intersectionMatrix is None else (a, b, intersectionMatrix)
+
+    return _call_predicate_function("ST_Relate", args)
+
 
 @validate_argument_types
 def ST_Within(a: ColumnOrName, b: ColumnOrName) -> Column:
diff --git a/python/tests/sql/test_dataframe_api.py 
b/python/tests/sql/test_dataframe_api.py
index 0426c7414..fe176e654 100644
--- a/python/tests/sql/test_dataframe_api.py
+++ b/python/tests/sql/test_dataframe_api.py
@@ -223,6 +223,8 @@ test_configurations = [
     (stp.ST_OrderingEquals, ("line", lambda: f.expr("ST_Reverse(line)")), 
"linestring_geom", "", False),
     (stp.ST_Overlaps, ("a", "b"), "overlapping_polys", "", True),
     (stp.ST_Touches, ("a", "b"), "touching_polys", "", True),
+    (stp.ST_Relate, ("a", "b"), "touching_polys", "", "FF2F11212"),
+    (stp.ST_Relate, ("a", "b", lambda: f.lit("FF2F11212")), "touching_polys", 
"", True),
     (stp.ST_Within, (lambda: f.expr("ST_Point(0.5, 0.25)"), "geom"), 
"triangle_geom", "", True),
     (stp.ST_Covers, ("geom", lambda: f.expr("ST_Point(0.5, 0.25)")), 
"triangle_geom", "", True),
     (stp.ST_CoveredBy, (lambda: f.expr("ST_Point(0.5, 0.25)"), "geom"), 
"triangle_geom", "", True),
@@ -424,6 +426,8 @@ wrong_type_configurations = [
     (stp.ST_Overlaps, ("", None)),
     (stp.ST_Touches, (None, "")),
     (stp.ST_Touches, ("", None)),
+    (stp.ST_Relate, (None, "")),
+    (stp.ST_Relate, ("", None)),
     (stp.ST_Within, (None, "")),
     (stp.ST_Within, ("", None)),
 
diff --git a/python/tests/sql/test_predicate.py 
b/python/tests/sql/test_predicate.py
index 3f977caa6..93cc43b76 100644
--- a/python/tests/sql/test_predicate.py
+++ b/python/tests/sql/test_predicate.py
@@ -199,6 +199,14 @@ class TestPredicate(TestBase):
         result_df.show()
         assert result_df.count() == 1
 
+    def test_st_relate(self):
+        baseDf = self.spark.sql("SELECT ST_GeomFromWKT('LINESTRING (1 1, 5 
5)') AS g1, ST_GeomFromWKT('POLYGON ((3 3, 3 7, 7 7, 7 3, 3 3))') as g2, 
'1010F0212' as im")
+        actual = baseDf.selectExpr("ST_Relate(g1, g2)").take(1)[0][0]
+        assert actual == "1010F0212"
+
+        actual = baseDf.selectExpr("ST_Relate(g1, g2, im)").take(1)[0][0]
+        assert actual
+
     def test_st_overlaps(self):
         test_table = self.spark.sql(
             "select ST_GeomFromWKT('POLYGON((2.5 2.5, 2.5 4.5, 4.5 4.5, 4.5 
2.5, 2.5 2.5))') as a,ST_GeomFromWKT('POLYGON((4 4, 4 6, 6 6, 6 4, 4 4))') as 
b, ST_GeomFromWKT('POLYGON((5 5, 4 6, 6 6, 6 4, 5 5))') as c, 
ST_GeomFromWKT('POLYGON((5 5, 4 6, 6 6, 6 4, 5 5))') as d")
diff --git 
a/snowflake-tester/src/test/java/org/apache/sedona/snowflake/snowsql/TestPredicates.java
 
b/snowflake-tester/src/test/java/org/apache/sedona/snowflake/snowsql/TestPredicates.java
index 5f3b54596..161c6a208 100644
--- 
a/snowflake-tester/src/test/java/org/apache/sedona/snowflake/snowsql/TestPredicates.java
+++ 
b/snowflake-tester/src/test/java/org/apache/sedona/snowflake/snowsql/TestPredicates.java
@@ -116,6 +116,20 @@ public class TestPredicates extends TestBase{
                 true
         );
     }
+
+    @Test
+    public void test_ST_Relate() {
+        registerUDF("ST_Relate", byte[].class, byte[].class);
+        verifySqlSingleRes(
+                "SELECT SEDONA.ST_Relate(SEDONA.ST_GeomFromWKT('LINESTRING (1 
1, 5 5)'), SEDONA.ST_GeomFromWKT('POLYGON ((3 3, 3 7, 7 7, 7 3, 3 3))'))",
+                "1010F0212"
+        );
+        registerUDF("ST_Relate", byte[].class, byte[].class, String.class);
+        verifySqlSingleRes(
+                "SELECT SEDONA.ST_Relate(SEDONA.ST_GeomFromWKT('LINESTRING (1 
1, 5 5)'), SEDONA.ST_GeomFromWKT('POLYGON ((3 3, 3 7, 7 7, 7 3, 3 3))'), 
'1010F0212')",
+                true
+        );
+    }
     @Test
     public void test_ST_Within() {
         registerUDF("ST_Within", byte[].class, byte[].class);
diff --git 
a/snowflake-tester/src/test/java/org/apache/sedona/snowflake/snowsql/TestPredicatesV2.java
 
b/snowflake-tester/src/test/java/org/apache/sedona/snowflake/snowsql/TestPredicatesV2.java
index 3a3a4fa8b..2f3c23490 100644
--- 
a/snowflake-tester/src/test/java/org/apache/sedona/snowflake/snowsql/TestPredicatesV2.java
+++ 
b/snowflake-tester/src/test/java/org/apache/sedona/snowflake/snowsql/TestPredicatesV2.java
@@ -117,6 +117,22 @@ public class TestPredicatesV2
                 true
         );
     }
+
+    @Test
+    public void test_ST_Relate() {
+        registerUDFV2("ST_Relate",  String.class,  String.class);
+        verifySqlSingleRes(
+                "SELECT SEDONA.ST_Relate( ST_GeometryFromWKT('LINESTRING (1 1, 
5 5)'),  ST_GeometryFromWKT('POLYGON ((3 3, 3 7, 7 7, 7 3, 3 3))'))",
+                "1010F0212"
+        );
+
+        registerUDFV2("ST_Relate",  String.class,  String.class, String.class);
+        verifySqlSingleRes(
+                "SELECT SEDONA.ST_Relate( ST_GeometryFromWKT('LINESTRING (1 1, 
5 5)'),  ST_GeometryFromWKT('POLYGON ((3 3, 3 7, 7 7, 7 3, 3 3))'), 
'1010F0212')",
+                true
+        );
+    }
+
     @Test
     public void test_ST_Within() {
          registerUDFV2("ST_Within",  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 a184cc6ec..e58020f17 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
@@ -1247,6 +1247,23 @@ public class UDFs {
         );
     }
 
+    @UDFAnnotations.ParamMeta(argNames = {"leftGeometry", "rightGeometry"})
+    public static String ST_Relate(byte[] leftGeometry, byte[] rightGeometry) {
+        return Predicates.relate(
+                GeometrySerde.deserialize(leftGeometry),
+                GeometrySerde.deserialize(rightGeometry)
+        );
+    }
+
+    @UDFAnnotations.ParamMeta(argNames = {"geom1", "geom2", 
"intersectionMatrix"})
+    public static Boolean ST_Relate(byte[] geom1, byte[] geom2, String 
intersectionMatrix) {
+        return Predicates.relate(
+                GeometrySerde.deserialize(geom1),
+                GeometrySerde.deserialize(geom2),
+                intersectionMatrix
+        );
+    }
+
     @UDFAnnotations.ParamMeta(argNames = {"geometry", "sourceCRS", 
"targetCRS"})
     public static byte[] ST_Transform(byte[] geometry, String sourceCRS, 
String targetCRS) {
         return GeometrySerde.serialize(
diff --git 
a/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/UDFsV2.java 
b/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/UDFsV2.java
index 720f106b3..1345d10c2 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
@@ -1034,6 +1034,23 @@ public class UDFsV2
         );
     }
 
+    @UDFAnnotations.ParamMeta(argNames = {"leftGeometry", "rightGeometry"}, 
argTypes = {"Geometry", "Geometry"}, returnTypes = "String")
+    public static String ST_Relate(String leftGeometry, String rightGeometry) {
+        return Predicates.relate(
+                GeometrySerde.deserGeoJson(leftGeometry),
+                GeometrySerde.deserGeoJson(rightGeometry)
+        );
+    }
+
+    @UDFAnnotations.ParamMeta(argNames = {"geom1", "geom2", 
"intersectionMatrix"}, argTypes = {"Geometry", "Geometry", "String"})
+    public static boolean ST_Relate(String geom1, String geom2, String 
intersectionMatrix) {
+        return Predicates.relate(
+                GeometrySerde.deserGeoJson(geom1),
+                GeometrySerde.deserGeoJson(geom2),
+                intersectionMatrix
+        );
+    }
+
     @UDFAnnotations.ParamMeta(argNames = {"geometry", "sourceCRS", 
"targetCRS"}, argTypes = {"Geometry", "String", "String"}, returnTypes = 
"Geometry")
     public static String ST_Transform(String geometry, String sourceCRS, 
String targetCRS) {
         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 f3b46011d..298bb3cf1 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
@@ -91,6 +91,7 @@ object Catalog {
     function[ST_ReducePrecision](),
     function[ST_Equals](),
     function[ST_Touches](),
+    function[ST_Relate](),
     function[ST_Overlaps](),
     function[ST_Crosses](),
     function[ST_CrossesDateLine](),
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 746cb8c1c..9d601a28e 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
@@ -200,6 +200,14 @@ case class ST_Touches(inputExpressions: Seq[Expression])
   }
 }
 
+case class ST_Relate(inputExpressions: Seq[Expression])
+  extends InferredExpression(inferrableFunction3(Predicates.relate), 
inferrableFunction2(Predicates.relate)) {
+
+  protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = 
{
+    copy(inputExpressions = newChildren)
+  }
+}
+
 /**
   * Test if leftGeometry is equal to rightGeometry
   *
diff --git 
a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_predicates.scala
 
b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_predicates.scala
index 32f8b0a29..61ed641dd 100644
--- 
a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_predicates.scala
+++ 
b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_predicates.scala
@@ -46,6 +46,11 @@ object st_predicates extends DataFrameAPI {
   def ST_Touches(a: Column, b: Column): Column = wrapExpression[ST_Touches](a, 
b)
   def ST_Touches(a: String, b: String): Column = wrapExpression[ST_Touches](a, 
b)
 
+  def ST_Relate(a: Column, b: Column): Column = wrapExpression[ST_Relate](a, b)
+  def ST_Relate(a: String, b: String): Column = wrapExpression[ST_Relate](a, b)
+  def ST_Relate(a: Column, b: Column, intersectionMatrix: Column): Column = 
wrapExpression[ST_Relate](a, b, intersectionMatrix)
+  def ST_Relate(a: String, b: String, intersectionMatrix: String): Column = 
wrapExpression[ST_Relate](a, b, intersectionMatrix)
+
   def ST_Within(a: Column, b: Column): Column = wrapExpression[ST_Within](a, b)
   def ST_Within(a: String, b: String): Column = wrapExpression[ST_Within](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 1fa25d270..da9bacd3c 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
@@ -1172,6 +1172,15 @@ class dataFrameAPITestScala extends TestBaseScala {
       assert(actualResult)
     }
 
+    it("Passed ST_Relate") {
+      val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('LINESTRING (1 1, 5 
5)') AS g1, ST_GeomFromWKT('POLYGON ((3 3, 3 7, 7 7, 7 3, 3 3))') as g2, 
'1010F0212' as im")
+      val actual = baseDf.select(ST_Relate("g1", "g2")).first().get(0)
+      assert(actual.equals("1010F0212"))
+
+      val actualBoolean = baseDf.select(ST_Relate("g1", "g2", 
"im")).first().getBoolean(0)
+      assert(actualBoolean)
+    }
+
     it("Passed ST_Touches") {
       val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('POLYGON ((0 0, 1 
0, 1 1, 0 0))') AS a, ST_GeomFromWKT('POLYGON ((1 1, 1 0, 2 0, 1 1))') AS b")
       val df = baseDf.select(ST_Touches("a", "b"))
diff --git 
a/spark/common/src/test/scala/org/apache/sedona/sql/predicateTestScala.scala 
b/spark/common/src/test/scala/org/apache/sedona/sql/predicateTestScala.scala
index 6ec688856..006910d84 100644
--- a/spark/common/src/test/scala/org/apache/sedona/sql/predicateTestScala.scala
+++ b/spark/common/src/test/scala/org/apache/sedona/sql/predicateTestScala.scala
@@ -194,6 +194,15 @@ class predicateTestScala extends TestBaseScala {
       assert(!notCrosses.take(1)(0).get(0).asInstanceOf[Boolean])
     }
 
+    it("Passed ST_Relate") {
+      val baseDf = sparkSession.sql("SELECT ST_GeomFromWKT('LINESTRING (1 1, 5 
5)') AS g1, ST_GeomFromWKT('POLYGON ((3 3, 3 7, 7 7, 7 3, 3 3))') as g2, 
'1010F0212' as im");
+      val actual = baseDf.selectExpr("ST_Relate(g1, g2)").first().get(0)
+      assert(actual.equals("1010F0212"))
+
+      val actualBoolean = baseDf.selectExpr("ST_Relate(g1, g2, 
im)").first().getBoolean(0)
+      assert(actualBoolean)
+    }
+
     it("Passed ST_Touches") {
       var pointCsvDF = sparkSession.read.format("csv").option("delimiter", 
",").option("header", "false").load(csvPointInputLocation)
       pointCsvDF.createOrReplaceTempView("pointtable")

Reply via email to