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 86378f030 [SEDONA-593] [SEDONA-594] Add ST_Relate, ST_RelateMatch 
(#1465)
86378f030 is described below

commit 86378f0301b75cade8077ce68c3ecc7a1e72bbe6
Author: Jia Yu <[email protected]>
AuthorDate: Fri Jun 7 13:28:12 2024 -0700

    [SEDONA-593] [SEDONA-594] Add ST_Relate, ST_RelateMatch (#1465)
    
    * [TASK-131] Add ST_Relate (#180)
    
    * add: ST_Relate
    
    * fix: snowflake udf registration
    
    * Update version
    
    * feat: Add ST_RelateMatch (#181)
    
    * Update versions
    
    ---------
    
    Co-authored-by: Furqaan Khan <[email protected]>
---
 .../java/org/apache/sedona/common/Predicates.java  | 13 ++++
 .../org/apache/sedona/common/PredicatesTest.java   | 59 ++++++++++++++++++
 docs/api/flink/Predicate.md                        | 71 ++++++++++++++++++++++
 docs/api/snowflake/vector-data/Predicate.md        | 67 ++++++++++++++++++++
 docs/api/sql/Predicate.md                          | 71 ++++++++++++++++++++++
 .../main/java/org/apache/sedona/flink/Catalog.java |  2 +
 .../sedona/flink/expressions/Predicates.java       | 40 ++++++++++++
 .../org/apache/sedona/flink/PredicateTest.java     | 17 ++++++
 python/sedona/sql/st_predicates.py                 | 30 +++++++++
 python/tests/sql/test_dataframe_api.py             |  7 +++
 python/tests/sql/test_predicate.py                 | 12 ++++
 .../sedona/snowflake/snowsql/TestPredicates.java   | 23 +++++++
 .../sedona/snowflake/snowsql/TestPredicatesV2.java | 25 ++++++++
 .../org/apache/sedona/snowflake/snowsql/UDFs.java  | 22 +++++++
 .../apache/sedona/snowflake/snowsql/UDFsV2.java    | 22 +++++++
 .../scala/org/apache/sedona/sql/UDF/Catalog.scala  |  2 +
 .../sql/sedona_sql/expressions/Predicates.scala    | 16 +++++
 .../sql/sedona_sql/expressions/st_predicates.scala |  8 +++
 .../apache/sedona/sql/dataFrameAPITestScala.scala  | 15 +++++
 .../org/apache/sedona/sql/predicateTestScala.scala | 14 +++++
 20 files changed, 536 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..0eb0fa904 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,16 @@ 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);
+    }
+
+    public static boolean relateMatch(String matrix1, String matrix2) {
+        return IntersectionMatrix.matches(matrix1, matrix2);
+    }
 }
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..e333671c6 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,65 @@ 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 testRelateMatch() {
+        String matrix1 = "101202FFF";
+        String matrix2 = "TTTTTTFFF";
+        boolean actual = Predicates.relateMatch(matrix1, matrix2);
+        assertTrue(actual);
+
+        matrix2 = "TTFTTTFFF";
+        actual = Predicates.relateMatch(matrix1, matrix2);
+        assertFalse(actual);
+
+        matrix1 = "FF1FF0102";
+        matrix2 = "FF1F***02";
+        actual = Predicates.relateMatch(matrix1, matrix2);
+        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..f4c8514d0 100644
--- a/docs/api/flink/Predicate.md
+++ b/docs/api/flink/Predicate.md
@@ -158,6 +158,77 @@ 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: `v1.6.1`
+
+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_RelateMatch
+
+Introduction: This function tests the relationship between two [Dimensionally 
Extended 9-Intersection Model (DE-9IM)](https://en.wikipedia.org/wiki/DE-9IM) 
matrices representing geometry intersections. It evaluates whether the DE-9IM 
matrix specified in `matrix1` satisfies the intersection pattern defined by 
`matrix2`. The `matrix2` parameter can be an exact DE-9IM value or a pattern 
containing wildcard characters.
+
+!!!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_RelateMatch(matrix1: String, matrix2: String)`
+
+Since: `v1.6.1`
+
+SQL Example:
+
+```sql
+SELECT ST_RelateMatch('101202FFF', 'TTTTTTFFF')
+```
+
+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..f08d63132 100644
--- a/docs/api/snowflake/vector-data/Predicate.md
+++ b/docs/api/snowflake/vector-data/Predicate.md
@@ -125,6 +125,73 @@ 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_RelateMatch
+
+Introduction: This function tests the relationship between two [Dimensionally 
Extended 9-Intersection Model (DE-9IM)](https://en.wikipedia.org/wiki/DE-9IM) 
matrices representing geometry intersections. It evaluates whether the DE-9IM 
matrix specified in `matrix1` satisfies the intersection pattern defined by 
`matrix2`. The `matrix2` parameter can be an exact DE-9IM value or a pattern 
containing wildcard characters.
+
+!!!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_RelateMatch(matrix1: String, matrix2: String)`
+
+SQL Example:
+
+```sql
+SELECT ST_RelateMatch('101202FFF', 'TTTTTTFFF')
+```
+
+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..561b75571 100644
--- a/docs/api/sql/Predicate.md
+++ b/docs/api/sql/Predicate.md
@@ -190,6 +190,77 @@ 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: `v1.6.1`
+
+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_RelateMatch
+
+Introduction: This function tests the relationship between two [Dimensionally 
Extended 9-Intersection Model (DE-9IM)](https://en.wikipedia.org/wiki/DE-9IM) 
matrices representing geometry intersections. It evaluates whether the DE-9IM 
matrix specified in `matrix1` satisfies the intersection pattern defined by 
`matrix2`. The `matrix2` parameter can be an exact DE-9IM value or a pattern 
containing wildcard characters.
+
+!!!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_RelateMatch(matrix1: String, matrix2: String)`
+
+Since: `v1.6.1`
+
+SQL Example:
+
+```sql
+SELECT ST_RelateMatch('101202FFF', 'TTTTTTFFF')
+```
+
+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 e40874356..8e60a8483 100644
--- a/flink/src/main/java/org/apache/sedona/flink/Catalog.java
+++ b/flink/src/main/java/org/apache/sedona/flink/Catalog.java
@@ -200,6 +200,8 @@ public class Catalog {
                 new Predicates.ST_OrderingEquals(),
                 new Predicates.ST_Overlaps(),
                 new Predicates.ST_Touches(),
+                new Predicates.ST_Relate(),
+                new Predicates.ST_RelateMatch(),
                 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..ad0aea5d0 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,46 @@ 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_RelateMatch extends ScalarFunction {
+        /**
+         * Constructor for relation checking without duplicate removal
+         */
+        public ST_RelateMatch() {
+        }
+
+        @DataTypeHint("Boolean")
+        public Boolean eval(@DataTypeHint("String") String matrix1,
+                            @DataTypeHint("String") String matrix2) {
+            return org.apache.sedona.common.Predicates.relateMatch(matrix1, 
matrix2);
+        }
+    }
+
     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..82b0998bb 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,23 @@ 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 testRelateMatch() {
+        Table table = tableEnv.sqlQuery("SELECT '101202FFF' as matrix1, 
'TTTTTTFFF' as matrix2");
+        Boolean actual = (Boolean) 
first(table.select(call(Predicates.ST_RelateMatch.class.getSimpleName(), 
$("matrix1"), $("matrix2")))).getField(0);
+        assertEquals(true, actual);
+    }
+
     @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..1969c8b5e 100644
--- a/python/sedona/sql/st_predicates.py
+++ b/python/sedona/sql/st_predicates.py
@@ -143,6 +143,36 @@ 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_RelateMatch(matrix1: ColumnOrName, matrix2: ColumnOrName) -> Column:
+    """Check whether two DE-9IM are related to each other.
+
+    :param matrix1: One geometry column to check.
+    :type matrix1: ColumnOrName
+    :param matrix2: Other geometry column to check.
+    :type matrix2: ColumnOrName
+    :return: True if a and b touch and False otherwise, as a boolean column.
+    :rtype: Column
+    """
+    return _call_predicate_function("ST_RelateMatch", (matrix1, matrix2))
+
 
 @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 d8804856f..6de3a7699 100644
--- a/python/tests/sql/test_dataframe_api.py
+++ b/python/tests/sql/test_dataframe_api.py
@@ -224,6 +224,9 @@ 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_RelateMatch, (lambda: f.lit("101202FFF"), lambda: 
f.lit("TTTTTTFFF")), "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),
@@ -431,6 +434,10 @@ 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_RelateMatch, (None, "")),
+    (stp.ST_RelateMatch, ("", 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..504ef81f1 100644
--- a/python/tests/sql/test_predicate.py
+++ b/python/tests/sql/test_predicate.py
@@ -199,6 +199,18 @@ 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_relate_match(self):
+        actual = self.spark.sql("SELECT ST_RelateMatch('101202FFF', 
'TTTTTTFFF') ").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..4095b0261 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,29 @@ 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_RelateMatch() {
+        registerUDF("ST_RelateMatch", String.class, String.class);
+        verifySqlSingleRes(
+                "SELECT SEDONA.ST_RelateMatch('101202FFF', 'TTTTTTFFF')",
+                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..56917c9f7 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,31 @@ 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_RelateMatch() {
+        registerUDFV2("ST_RelateMatch", String.class, String.class);
+        verifySqlSingleRes(
+                "SELECT SEDONA.ST_RelateMatch('101202FFF', 'TTTTTTFFF')",
+                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 98514cc57..ff044c546 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
@@ -1284,6 +1284,28 @@ 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 = {"matrix1", "matrix2"})
+    public static Boolean ST_RelateMatch(String matrix1, String matrix2) {
+        return Predicates.relateMatch(matrix1, matrix2);
+    }
+
     @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 155b29b79..fbb4d2879 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
@@ -1042,6 +1042,28 @@ 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 = {"matrix1", "matrix2"}, argTypes = 
{"String", "String"})
+    public static boolean ST_RelateMatch(String matrix1, String matrix2) {
+        return Predicates.relateMatch(matrix1, matrix2);
+    }
+
     @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 7e1fa981a..bbf06b75e 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,8 @@ object Catalog {
     function[ST_ReducePrecision](),
     function[ST_Equals](),
     function[ST_Touches](),
+    function[ST_Relate](),
+    function[ST_RelateMatch](),
     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..eb41e704f 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,22 @@ 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)
+  }
+}
+
+case class ST_RelateMatch(inputExpressions: Seq[Expression])
+  extends InferredExpression(inferrableFunction2(Predicates.relateMatch)) {
+
+  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..1ac8cb15b 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,14 @@ 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_RelateMatch(a: Column, b: Column): Column = 
wrapExpression[ST_RelateMatch](a, b)
+  def ST_RelateMatch(a: String, b: String): Column = 
wrapExpression[ST_RelateMatch](a, b)
+
   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 a34f3aeea..08e0a6d0e 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
@@ -1205,6 +1205,21 @@ 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_RelateMatch") {
+      val baseDf = sparkSession.sql("SELECT '101202FFF' as matrix1, 
'TTTTTTFFF' as matrix2")
+      val actual = baseDf.select(ST_RelateMatch("matrix1", 
"matrix2")).first().getBoolean(0)
+      assert(actual)
+    }
+
     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..0dc5be9be 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,20 @@ 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_RelateMatch") {
+      val actual = sparkSession.sql("SELECT ST_RelateMatch('101202FFF', 
'TTTTTTFFF')").first().getBoolean(0)
+      assert(actual)
+    }
+
     it("Passed ST_Touches") {
       var pointCsvDF = sparkSession.read.format("csv").option("delimiter", 
",").option("header", "false").load(csvPointInputLocation)
       pointCsvDF.createOrReplaceTempView("pointtable")


Reply via email to