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 b4c064380952605ed3211f65a9e9e3ce9abd3c65
Author: Furqaan Khan <[email protected]>
AuthorDate: Fri May 3 23:12:18 2024 -0400

    feat: Add ST_RelateMatch (#181)
---
 .../java/org/apache/sedona/common/Predicates.java  |  4 ++++
 .../org/apache/sedona/common/PredicatesTest.java   | 17 ++++++++++++++++
 docs/api/flink/Predicate.md                        | 23 ++++++++++++++++++++++
 docs/api/snowflake/vector-data/Predicate.md        | 21 ++++++++++++++++++++
 docs/api/sql/Predicate.md                          | 23 ++++++++++++++++++++++
 .../main/java/org/apache/sedona/flink/Catalog.java |  1 +
 .../sedona/flink/expressions/Predicates.java       | 14 +++++++++++++
 .../org/apache/sedona/flink/PredicateTest.java     |  7 +++++++
 python/sedona/sql/st_predicates.py                 | 13 ++++++++++++
 python/tests/sql/test_dataframe_api.py             |  3 +++
 python/tests/sql/test_predicate.py                 |  4 ++++
 .../sedona/snowflake/snowsql/TestPredicates.java   |  9 +++++++++
 .../sedona/snowflake/snowsql/TestPredicatesV2.java |  9 +++++++++
 .../org/apache/sedona/snowflake/snowsql/UDFs.java  |  5 +++++
 .../apache/sedona/snowflake/snowsql/UDFsV2.java    |  5 +++++
 .../scala/org/apache/sedona/sql/UDF/Catalog.scala  |  1 +
 .../sql/sedona_sql/expressions/Predicates.scala    |  8 ++++++++
 .../sql/sedona_sql/expressions/st_predicates.scala |  3 +++
 .../apache/sedona/sql/dataFrameAPITestScala.scala  |  6 ++++++
 .../org/apache/sedona/sql/predicateTestScala.scala |  5 +++++
 20 files changed, 181 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 2994731d6..0eb0fa904 100644
--- a/common/src/main/java/org/apache/sedona/common/Predicates.java
+++ b/common/src/main/java/org/apache/sedona/common/Predicates.java
@@ -72,4 +72,8 @@ public class Predicates {
         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 87e16bd81..e333671c6 100644
--- a/common/src/test/java/org/apache/sedona/common/PredicatesTest.java
+++ b/common/src/test/java/org/apache/sedona/common/PredicatesTest.java
@@ -118,6 +118,23 @@ public class PredicatesTest extends TestBase {
         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 7359e8094..9ae317196 100644
--- a/docs/api/flink/Predicate.md
+++ b/docs/api/flink/Predicate.md
@@ -206,6 +206,29 @@ 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: `vTBD`
+
+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 d6473a350..f08d63132 100644
--- a/docs/api/snowflake/vector-data/Predicate.md
+++ b/docs/api/snowflake/vector-data/Predicate.md
@@ -171,6 +171,27 @@ 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 a9f29a532..ffdcb0b75 100644
--- a/docs/api/sql/Predicate.md
+++ b/docs/api/sql/Predicate.md
@@ -238,6 +238,29 @@ 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: `vTBD`
+
+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 bdea8749e..b40e48080 100644
--- a/flink/src/main/java/org/apache/sedona/flink/Catalog.java
+++ b/flink/src/main/java/org/apache/sedona/flink/Catalog.java
@@ -198,6 +198,7 @@ public class Catalog {
                 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 d353cdfd1..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
@@ -240,6 +240,20 @@ public class Predicates {
         }
     }
 
+    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 561a86ec9..82b0998bb 100644
--- a/flink/src/test/java/org/apache/sedona/flink/PredicateTest.java
+++ b/flink/src/test/java/org/apache/sedona/flink/PredicateTest.java
@@ -141,6 +141,13 @@ public class PredicateTest extends TestBase{
         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 4ffd721f6..1969c8b5e 100644
--- a/python/sedona/sql/st_predicates.py
+++ b/python/sedona/sql/st_predicates.py
@@ -160,6 +160,19 @@ def ST_Relate(a: ColumnOrName, b: ColumnOrName, 
intersectionMatrix: Optional[Col
 
     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 fe176e654..a39bc4885 100644
--- a/python/tests/sql/test_dataframe_api.py
+++ b/python/tests/sql/test_dataframe_api.py
@@ -225,6 +225,7 @@ test_configurations = [
     (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),
@@ -428,6 +429,8 @@ wrong_type_configurations = [
     (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 93cc43b76..504ef81f1 100644
--- a/python/tests/sql/test_predicate.py
+++ b/python/tests/sql/test_predicate.py
@@ -207,6 +207,10 @@ class TestPredicate(TestBase):
         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 161c6a208..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
@@ -130,6 +130,15 @@ public class TestPredicates extends TestBase{
                 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 2f3c23490..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
@@ -133,6 +133,15 @@ public class TestPredicatesV2
         );
     }
 
+    @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 e58020f17..19d897d1b 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
@@ -1264,6 +1264,11 @@ public class UDFs {
         );
     }
 
+    @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 1345d10c2..b307f509b 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
@@ -1051,6 +1051,11 @@ public class UDFsV2
         );
     }
 
+    @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 298bb3cf1..4a4550c4c 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
@@ -92,6 +92,7 @@ object Catalog {
     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 9d601a28e..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
@@ -208,6 +208,14 @@ case class ST_Relate(inputExpressions: Seq[Expression])
   }
 }
 
+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 61ed641dd..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
@@ -51,6 +51,9 @@ object st_predicates extends DataFrameAPI {
   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 da9bacd3c..cf3263f4b 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
@@ -1181,6 +1181,12 @@ class dataFrameAPITestScala extends TestBaseScala {
       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 006910d84..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
@@ -203,6 +203,11 @@ class predicateTestScala extends TestBaseScala {
       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