You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sedona.apache.org by ji...@apache.org on 2022/02/21 01:22:32 UTC

[incubator-sedona] branch master updated: [SEDONA-82] Create symmetrical difference function (#580)

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/incubator-sedona.git


The following commit(s) were added to refs/heads/master by this push:
     new 6675e70  [SEDONA-82] Create symmetrical difference function (#580)
6675e70 is described below

commit 6675e70385824456bc73ff69a27a5a06fdfc6392
Author: Magdalena <69...@users.noreply.github.com>
AuthorDate: Mon Feb 21 02:22:24 2022 +0100

    [SEDONA-82] Create symmetrical difference function (#580)
---
 docs/api/sql/Function.md                           | 21 +++++++++++++
 python/tests/sql/test_function.py                  | 18 +++++++++++
 .../scala/org/apache/sedona/sql/UDF/Catalog.scala  |  1 +
 .../sql/sedona_sql/expressions/Functions.scala     | 31 +++++++++++++++++++
 .../org/apache/sedona/sql/functionTestScala.scala  | 36 +++++++++++++++++++++-
 5 files changed, 106 insertions(+), 1 deletion(-)

diff --git a/docs/api/sql/Function.md b/docs/api/sql/Function.md
index fcf7cdc..a9bb5ae 100644
--- a/docs/api/sql/Function.md
+++ b/docs/api/sql/Function.md
@@ -942,4 +942,25 @@ Result:
 
 ```
 POLYGON ((0 -3, -3 -3, -3 3, 0 3, 0 -3))
+```
+
+## ST_SymDifference
+
+Introduction: Return the symmetrical difference between geometry A and B (return parts of geometries which are in either of the sets, but not in their intersection)
+
+
+Format: `ST_SymDifference (A:geometry, B:geometry)`
+
+Since: `v1.2.0`
+
+Example:
+
+```SQL
+SELECT ST_SymDifference(ST_GeomFromWKT('POLYGON ((-3 -3, 3 -3, 3 3, -3 3, -3 -3))'), ST_GeomFromWKT('POLYGON ((-2 -3, 4 -3, 4 3, -2 3, -2 -3))'))
+```
+
+Result:
+
+```
+MULTIPOLYGON (((-2 -3, -3 -3, -3 3, -2 3, -2 -3)), ((3 -3, 3 3, 4 3, 4 -3, 3 -3)))
 ```
\ No newline at end of file
diff --git a/python/tests/sql/test_function.py b/python/tests/sql/test_function.py
index 3fec4a1..83bb2c7 100644
--- a/python/tests/sql/test_function.py
+++ b/python/tests/sql/test_function.py
@@ -300,6 +300,24 @@ class TestPredicateJoin(TestBase):
         diff = self.spark.sql("select ST_Difference(a,b) from test_diff")
         assert diff.take(1)[0][0].wkt == "POLYGON EMPTY"
 
+    def test_st_sym_difference_part_of_right_overlaps_left(self):
+        test_table = self.spark.sql("select ST_GeomFromWKT('POLYGON ((-1 -1, 1 -1, 1 1, -1 1, -1 -1))') as a,ST_GeomFromWKT('POLYGON ((0 -2, 2 -2, 2 0, 0 0, 0 -2))') as b")
+        test_table.createOrReplaceTempView("test_sym_diff")
+        diff = self.spark.sql("select ST_SymDifference(a,b) from test_sym_diff")
+        assert diff.take(1)[0][0].wkt == "MULTIPOLYGON (((0 -1, -1 -1, -1 1, 1 1, 1 0, 0 0, 0 -1)), ((0 -1, 1 -1, 1 0, 2 0, 2 -2, 0 -2, 0 -1)))"
+
+    def test_st_sym_difference_not_overlaps_left(self):
+        test_table = self.spark.sql("select ST_GeomFromWKT('POLYGON ((-3 -3, 3 -3, 3 3, -3 3, -3 -3))') as a,ST_GeomFromWKT('POLYGON ((5 -3, 7 -3, 7 -1, 5 -1, 5 -3))') as b")
+        test_table.createOrReplaceTempView("test_sym_diff")
+        diff = self.spark.sql("select ST_SymDifference(a,b) from test_sym_diff")
+        assert diff.take(1)[0][0].wkt == "MULTIPOLYGON (((-3 -3, -3 3, 3 3, 3 -3, -3 -3)), ((5 -3, 5 -1, 7 -1, 7 -3, 5 -3)))"
+
+    def test_st_sym_difference_contains(self):
+        test_table = self.spark.sql("select ST_GeomFromWKT('POLYGON ((-3 -3, 3 -3, 3 3, -3 3, -3 -3))') as a,ST_GeomFromWKT('POLYGON ((-1 -1, 1 -1, 1 1, -1 1, -1 -1))') as b")
+        test_table.createOrReplaceTempView("test_sym_diff")
+        diff = self.spark.sql("select ST_SymDifference(a,b) from test_sym_diff")
+        assert diff.take(1)[0][0].wkt == "POLYGON ((-3 -3, -3 3, 3 3, 3 -3, -3 -3), (-1 -1, 1 -1, 1 1, -1 1, -1 -1))"
+
     def test_st_azimuth(self):
         sample_points = create_sample_points(20)
         sample_pair_points = [[el, sample_points[1]] for el in sample_points]
diff --git a/sql/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala b/sql/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala
index 54cfc14..a6926ff 100644
--- a/sql/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala
+++ b/sql/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala
@@ -52,6 +52,7 @@ object Catalog {
     ST_Transform,
     ST_Intersection,
     ST_Difference,
+    ST_SymDifference,
     ST_IsValid,
     ST_PrecisionReduce,
     ST_Equals,
diff --git a/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala b/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala
index 7b5a011..08916f9 100644
--- a/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala
+++ b/sql/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala
@@ -1474,3 +1474,34 @@ case class ST_Difference(inputExpressions: Seq[Expression])
     copy(inputExpressions = newChildren)
   }
 }
+
+/**
+ * Return the symmetrical difference between geometry A and B
+ *
+ * @param inputExpressions
+ */
+case class ST_SymDifference(inputExpressions: Seq[Expression])
+  extends Expression with CodegenFallback {
+  assert(inputExpressions.length == 2)
+
+  override def nullable: Boolean = true
+
+  override def eval(input: InternalRow): Any = {
+    val leftGeometry = inputExpressions(0).toGeometry(input)
+    val rightGeometry = inputExpressions(1).toGeometry(input)
+
+    (leftGeometry, rightGeometry) match {
+      case (leftGeometry: Geometry, rightGeometry: Geometry)
+      => new GenericArrayData(GeometrySerializer.serialize(leftGeometry.symDifference(rightGeometry)))
+      case _ => null
+    }
+  }
+
+  override def dataType: DataType = GeometryUDT
+
+  override def children: Seq[Expression] = inputExpressions
+
+  protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = {
+    copy(inputExpressions = newChildren)
+  }
+}
\ No newline at end of file
diff --git a/sql/src/test/scala/org/apache/sedona/sql/functionTestScala.scala b/sql/src/test/scala/org/apache/sedona/sql/functionTestScala.scala
index f788543..779b022 100644
--- a/sql/src/test/scala/org/apache/sedona/sql/functionTestScala.scala
+++ b/sql/src/test/scala/org/apache/sedona/sql/functionTestScala.scala
@@ -376,8 +376,40 @@ class functionTestScala extends TestBaseScala with Matchers with GeometrySample
       val testtable = sparkSession.sql("select ST_GeomFromWKT('POLYGON ((-1 -1, 1 -1, 1 1, -1 1, -1 -1))') as a,ST_GeomFromWKT('POLYGON ((-3 -3, 3 -3, 3 3, -3 3, -3 -3))') as b")
       testtable.createOrReplaceTempView("testtable")
       val diff = sparkSession.sql("select ST_Difference(a,b) from testtable")
-      assert(diff.take(1)(0).get(0).asInstanceOf[Geometry].toText.equals("POLYGON EMPTY"))    }
+      assert(diff.take(1)(0).get(0).asInstanceOf[Geometry].toText.equals("POLYGON EMPTY"))
+    }
+
+    it("Passed ST_SymDifference - part of right overlaps left") {
+
+      val testtable = sparkSession.sql("select ST_GeomFromWKT('POLYGON ((-1 -1, 1 -1, 1 1, -1 1, -1 -1))') as a,ST_GeomFromWKT('POLYGON ((0 -2, 2 -2, 2 0, 0 0, 0 -2))') as b")
+      testtable.createOrReplaceTempView("sym_table")
+      val symDiff = sparkSession.sql("select ST_SymDifference(a,b) from sym_table")
+      assert(symDiff.take(1)(0).get(0).asInstanceOf[Geometry].toText.equals("MULTIPOLYGON (((0 -1, -1 -1, -1 1, 1 1, 1 0, 0 0, 0 -1)), ((0 -1, 1 -1, 1 0, 2 0, 2 -2, 0 -2, 0 -1)))"))
+    }
+
+    it("Passed ST_SymDifference - right not overlaps left") {
+
+      val testtable = sparkSession.sql("select ST_GeomFromWKT('POLYGON ((-3 -3, 3 -3, 3 3, -3 3, -3 -3))') as a,ST_GeomFromWKT('POLYGON ((5 -3, 7 -3, 7 -1, 5 -1, 5 -3))') as b")
+      testtable.createOrReplaceTempView("sym_table")
+      val symDiff = sparkSession.sql("select ST_SymDifference(a,b) from sym_table")
+      assert(symDiff.take(1)(0).get(0).asInstanceOf[Geometry].toText.equals("MULTIPOLYGON (((-3 -3, -3 3, 3 3, 3 -3, -3 -3)), ((5 -3, 5 -1, 7 -1, 7 -3, 5 -3)))"))
+    }
 
+    it("Passed ST_SymDifference - contains") {
+
+      val testtable = sparkSession.sql("select ST_GeomFromWKT('POLYGON ((-3 -3, 3 -3, 3 3, -3 3, -3 -3))') as a,ST_GeomFromWKT('POLYGON ((-1 -1, 1 -1, 1 1, -1 1, -1 -1))') as b")
+      testtable.createOrReplaceTempView("sym_table")
+      val symDiff = sparkSession.sql("select ST_SymDifference(a,b) from sym_table")
+      assert(symDiff.take(1)(0).get(0).asInstanceOf[Geometry].toText.equals("POLYGON ((-3 -3, -3 3, 3 3, 3 -3, -3 -3), (-1 -1, 1 -1, 1 1, -1 1, -1 -1))"))
+    }
+
+    it("Passed ST_SymDifference - one null") {
+
+      val testtable = sparkSession.sql("select ST_GeomFromWKT('POLYGON ((-3 -3, 3 -3, 3 3, -3 3, -3 -3))') as a")
+      testtable.createOrReplaceTempView("sym_table")
+      val symDiff = sparkSession.sql("select ST_SymDifference(a,null) from sym_table")
+      assert(symDiff.first().get(0) == null)
+    }
 
     it("Passed ST_Azimuth") {
 
@@ -1304,5 +1336,7 @@ class functionTestScala extends TestBaseScala with Matchers with GeometrySample
     assert(functionDf.first().get(0) == null)
     functionDf = sparkSession.sql("select ST_Difference(null, null)")
     assert(functionDf.first().get(0) == null)
+    functionDf = sparkSession.sql("select ST_SymDifference(null, null)")
+    assert(functionDf.first().get(0) == null)
   }
 }