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