From 7de48aa9e9eebc60631a5e904db98ad9c12074d1 Mon Sep 17 00:00:00 2001 From: Akshay Gogeri Date: Tue, 12 Apr 2022 14:45:23 -0700 Subject: [PATCH 1/5] Add ST_Reverse Function to SQL API --- docs/api/sql/Function.md | 26 +++++++++++++++++++ .../org/apache/sedona/sql/UDF/Catalog.scala | 1 + .../sedona_sql/expressions/Functions.scala | 22 ++++++++++++++++ 3 files changed, 49 insertions(+) diff --git a/docs/api/sql/Function.md b/docs/api/sql/Function.md index 71b2da8a57..406c10a00a 100644 --- a/docs/api/sql/Function.md +++ b/docs/api/sql/Function.md @@ -1029,3 +1029,29 @@ Result: ``` POLYGON ((3 -1, 3 -3, -3 -3, -3 3, 3 3, 3 1, 5 0, 3 -1)) ``` + +## ST_Reverse + +Introduction: Return the geometry with vertex order reversed + +Format: `ST_Reverse (A:geometry)` + +Since: `v1.2.0` + +Example: + +```SQL +SELECT ST_AsText( + ST_Reverse(ST_GeomFromText('LINESTRING(0 0, 1 2, 2 4, 3 6)')) +) AS geom +``` + +Result: + +``` ++---------------------------------------------------------------+ +|geom | ++---------------------------------------------------------------+ +|LINESTRING (3 6, 2 4, 1 2, 0 0) | ++---------------------------------------------------------------+ +``` \ No newline at end of file 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 473eb5c7f0..76750fcfd7 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 @@ -101,6 +101,7 @@ object Catalog { ST_GeomFromGeoHash, ST_Collect, ST_Multi, + ST_Reverse, // Expression for rasters RS_NormalizedDifference, RS_Mean, 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 fbe446d442..2c8e632dcc 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 @@ -1527,4 +1527,26 @@ case class ST_Multi(inputExpressions: Seq[Expression]) extends UnaryGeometryExpr override protected def nullSafeEval(geometry: Geometry): Any ={ Collect.createMultiGeometry(Seq(geometry)).toGenericArrayData } +} + +/** + * Returns the geometry with vertex order reversed + * + * @param inputExpressions + */ +case class ST_Reverse(inputExpressions: Seq[Expression]) + extends UnaryGeometryExpression with CodegenFallback { + assert(inputExpressions.length == 1) + + override protected def nullSafeEval(geometry: Geometry): Any = { + new GenericArrayData(GeometrySerializer.serialize(geometry.reverse())) + } + + 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 From ad2b21ddad583a524f764748d3eae0f5e4250fa8 Mon Sep 17 00:00:00 2001 From: Akshay Gogeri Date: Tue, 12 Apr 2022 14:49:02 -0700 Subject: [PATCH 2/5] Add Unit Test --- python/tests/sql/test_function.py | 33 ++++++++++++++++ .../apache/sedona/sql/functionTestScala.scala | 38 +++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/python/tests/sql/test_function.py b/python/tests/sql/test_function.py index b7ea1606cc..e24c96495d 100644 --- a/python/tests/sql/test_function.py +++ b/python/tests/sql/test_function.py @@ -880,6 +880,39 @@ def test_st_collect_on_multiple_columns(self): "MULTIPOLYGON (((1 2, 1 4, 3 4, 3 2, 1 2)), ((0.5 0.5, 5 0, 5 5, 0 5, 0.5 0.5)))" }) + def test_st_reverse(self): + test_cases = { + "'POLYGON((-1 0 0, 1 0 0, 0 0 1, 0 1 0, -1 0 0))'": + "POLYGON Z((-1 0 0, 0 1 0, 0 0 1, 1 0 0, -1 0 0))", + "'LINESTRING(0 0, 1 2, 2 4, 3 6)'": + "LINESTRING (3 6, 2 4, 1 2, 0 0)", + "'POINT(1 2)'": + "POINT (1 2)", + "'MULTIPOINT((10 40 66), (40 30 77), (20 20 88), (30 10 99))'": + "MULTIPOINT Z((10 40 66), (40 30 77), (20 20 88), (30 10 99))", + "'MULTIPOLYGON(((30 20 11, 45 40 11, 10 40 11, 30 20 11)), " \ + "((15 5 11, 40 10 11, 10 20 11, 5 10 11, 15 5 11)))'": + "MULTIPOLYGON Z(((30 20 11, 10 40 11, 45 40 11, 30 20 11)), " \ + "((15 5 11, 5 10 11, 10 20 11, 40 10 11, 15 5 11)))", + "'MULTILINESTRING((10 10 11, 20 20 11, 10 40 11), " \ + "(40 40 11, 30 30 11, 40 20 11, 30 10 11))'": + "MULTILINESTRING Z((10 40 11, 20 20 11, 10 10 11), " \ + "(30 10 11, 40 20 11, 30 30 11, 40 40 11))", + "'MULTIPOLYGON(((40 40 11, 20 45 11, 45 30 11, 40 40 11)), " \ + "((20 35 11, 10 30 11, 10 10 11, 30 5 11, 45 20 11, 20 35 11)," \ + "(30 20 11, 20 15 11, 20 25 11, 30 20 11)))'": + "MULTIPOLYGON Z(((40 40 11, 45 30 11, 20 45 11, 40 40 11)), " \ + "((20 35 11, 45 20 11, 30 5 11, 10 10 11, 10 30 11, 20 35 11), " \ + "(30 20 11, 20 25 11, 20 15 11, 30 20 11)))", + "'POLYGON((0 0 11, 0 5 11, 5 5 11, 5 0 11, 0 0 11), " \ + "(1 1 11, 2 1 11, 2 2 11, 1 2 11, 1 1 11))'": + "POLYGON Z((0 0 11, 5 0 11, 5 5 11, 0 5 11, 0 0 11), " \ + "(1 1 11, 1 2 11, 2 2 11, 2 1 11, 1 1 11))" + } + for input_geom, expected_geom in test_cases: + reversed_geometry = self.spark.sql("select ST_AsText(ST_Reverse(ST_GeomFromText({})))".format(input_geom)) + assert reversed_geometry.take(1)[0][0].wkt == expected_geom + def calculate_st_is_ring(self, wkt): geometry_collected = self.__wkt_list_to_data_frame([wkt]). \ selectExpr("ST_IsRing(geom) as is_ring") \ 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 6f55c05af1..5544fb9ade 100644 --- a/sql/src/test/scala/org/apache/sedona/sql/functionTestScala.scala +++ b/sql/src/test/scala/org/apache/sedona/sql/functionTestScala.scala @@ -1266,6 +1266,42 @@ class functionTestScala extends TestBaseScala with Matchers with GeometrySample } + it ("Should pass ST_Reverse") { + val geomTestCases = Map( + "'POLYGON((-1 0 0, 1 0 0, 0 0 1, 0 1 0, -1 0 0))'" + -> "POLYGON Z((-1 0 0, 0 1 0, 0 0 1, 1 0 0, -1 0 0))", + "'LINESTRING(0 0, 1 2, 2 4, 3 6)'" + -> "LINESTRING (3 6, 2 4, 1 2, 0 0)", + "'POINT(1 2)'" + -> "POINT (1 2)", + "'MULTIPOINT((10 40 66), (40 30 77), (20 20 88), (30 10 99))'" + -> "MULTIPOINT Z((10 40 66), (40 30 77), (20 20 88), (30 10 99))", + """'MULTIPOLYGON(((30 20 11, 45 40 11, 10 40 11, 30 20 11)), + |((15 5 11, 40 10 11, 10 20 11, 5 10 11, 15 5 11)))'""".stripMargin.replaceAll("\n", " ") + -> """MULTIPOLYGON Z(((30 20 11, 10 40 11, 45 40 11, 30 20 11)), + |((15 5 11, 5 10 11, 10 20 11, 40 10 11, 15 5 11)))""".stripMargin.replaceAll("\n", " "), + """'MULTILINESTRING((10 10 11, 20 20 11, 10 40 11), + |(40 40 11, 30 30 11, 40 20 11, 30 10 11))'""".stripMargin.replaceAll("\n", " ") + -> """MULTILINESTRING Z((10 40 11, 20 20 11, 10 10 11), + |(30 10 11, 40 20 11, 30 30 11, 40 40 11))""".stripMargin.replaceAll("\n", " "), + """'MULTIPOLYGON(((40 40 11, 20 45 11, 45 30 11, 40 40 11)), + |((20 35 11, 10 30 11, 10 10 11, 30 5 11, 45 20 11, 20 35 11), + |(30 20 11, 20 15 11, 20 25 11, 30 20 11)))'""".stripMargin.replaceAll("\n", " ") + -> """MULTIPOLYGON Z(((40 40 11, 45 30 11, 20 45 11, 40 40 11)), + |((20 35 11, 45 20 11, 30 5 11, 10 10 11, 10 30 11, 20 35 11), + |(30 20 11, 20 25 11, 20 15 11, 30 20 11)))""".stripMargin.replaceAll("\n", " "), + """'POLYGON((0 0 11, 0 5 11, 5 5 11, 5 0 11, 0 0 11), + |(1 1 11, 2 1 11, 2 2 11, 1 2 11, 1 1 11))'""".stripMargin.replaceAll("\n", " ") + -> """POLYGON Z((0 0 11, 5 0 11, 5 5 11, 0 5 11, 0 0 11), + |(1 1 11, 1 2 11, 2 2 11, 2 1 11, 1 1 11))""".stripMargin.replaceAll("\n", " ") + ) + for((inputGeom, expectedGeom) <- geomTestCases) { + var df = sparkSession.sql(s"select ST_AsText(ST_Reverse(ST_GeomFromText($inputGeom)))") + var result = df.collect() + assert(result.head.get(0).asInstanceOf[String]==expectedGeom) + } + } + it("handles nulls") { var functionDf: DataFrame = null functionDf = sparkSession.sql("select ST_Distance(null, null)") @@ -1372,5 +1408,7 @@ class functionTestScala extends TestBaseScala with Matchers with GeometrySample assert(functionDf.first().get(0) == null) functionDf = sparkSession.sql("select ST_Union(null, null)") assert(functionDf.first().get(0) == null) + functionDf = sparkSession.sql("select ST_Reverse(null)") + assert(functionDf.first().get(0) == null) } } From 739f16f1ab69790b0852b8fffafd2193675190e1 Mon Sep 17 00:00:00 2001 From: Akshay Gogeri Date: Tue, 12 Apr 2022 16:48:39 -0700 Subject: [PATCH 3/5] Add ST_Reverse function to flink API --- docs/api/flink/Function.md | 21 ++++++++++++++++++- .../java/org/apache/sedona/flink/Catalog.java | 3 ++- .../sedona/flink/expressions/Functions.java | 8 +++++++ .../org/apache/sedona/flink/FunctionTest.java | 8 +++++++ python/tests/sql/test_function.py | 2 +- 5 files changed, 39 insertions(+), 3 deletions(-) diff --git a/docs/api/flink/Function.md b/docs/api/flink/Function.md index c559abf2c1..63ee21c983 100644 --- a/docs/api/flink/Function.md +++ b/docs/api/flink/Function.md @@ -99,4 +99,23 @@ Result: +-----------------------------+ |u3r0p | +-----------------------------+ -``` \ No newline at end of file +``` + +## ST_Reverse + +Introduction: Return the geometry with vertex order reversed + +Format: `ST_Reverse (A:geometry)` + +Since: `v1.2.0` + +Example: + +```SQL +SELECT ST_Reverse(df.geometry) AS geom +FROM df +``` + +Input: `POLYGON ((-0.5 -0.5, -0.5 0.5, 0.5 0.5, 0.5 -0.5, -0.5 -0.5))` + +Output: `POLYGON ((-0.5 -0.5, 0.5 -0.5, 0.5 0.5, -0.5 0.5, -0.5 -0.5))` \ No newline at end of file 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 440f4ad44f..4898575e06 100644 --- a/flink/src/main/java/org/apache/sedona/flink/Catalog.java +++ b/flink/src/main/java/org/apache/sedona/flink/Catalog.java @@ -30,7 +30,8 @@ public static UserDefinedFunction[] getFuncs() { new Functions.ST_Distance(), new Functions.ST_Transform(), new Functions.ST_FlipCoordinates(), - new Functions.ST_GeoHash() + new Functions.ST_GeoHash(), + new Functions.ST_Reverse() }; } diff --git a/flink/src/main/java/org/apache/sedona/flink/expressions/Functions.java b/flink/src/main/java/org/apache/sedona/flink/expressions/Functions.java index 6f43143557..46a919a1ee 100644 --- a/flink/src/main/java/org/apache/sedona/flink/expressions/Functions.java +++ b/flink/src/main/java/org/apache/sedona/flink/expressions/Functions.java @@ -84,4 +84,12 @@ public Optional eval(@DataTypeHint(value = "RAW", bridgedTo = org.locati return Optional.empty(); } } + + public static class ST_Reverse extends ScalarFunction { + @DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class) + public Geometry eval(@DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class) Object o) { + Geometry geom = (Geometry) o; + return geom.reverse(); + } + } } diff --git a/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java b/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java index 2d244a7aa2..e4a14511ad 100644 --- a/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java +++ b/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java @@ -64,4 +64,12 @@ public void testGeomToGeoHash() { ); assertEquals(first(pointTable).getField(0), Optional.of("s0000")); } + + @Test + public void testReverse() { + Table polygonTable = createPolygonTable(1); + Table ReversedTable = polygonTable.select(call(Functions.ST_Reverse.class.getSimpleName(), $(polygonColNames[0]))); + Geometry result = (Geometry) first(ReversedTable).getField(0); + assertEquals("POLYGON ((-0.5 -0.5, 0.5 -0.5, 0.5 0.5, -0.5 0.5, -0.5 -0.5))", result.toString()); + } } diff --git a/python/tests/sql/test_function.py b/python/tests/sql/test_function.py index e24c96495d..183a0eb782 100644 --- a/python/tests/sql/test_function.py +++ b/python/tests/sql/test_function.py @@ -909,7 +909,7 @@ def test_st_reverse(self): "POLYGON Z((0 0 11, 5 0 11, 5 5 11, 0 5 11, 0 0 11), " \ "(1 1 11, 1 2 11, 2 2 11, 2 1 11, 1 1 11))" } - for input_geom, expected_geom in test_cases: + for input_geom, expected_geom in test_cases.items(): reversed_geometry = self.spark.sql("select ST_AsText(ST_Reverse(ST_GeomFromText({})))".format(input_geom)) assert reversed_geometry.take(1)[0][0].wkt == expected_geom From 60fc71eb8d9c3c68b6628cb43c3c234d482f8f0f Mon Sep 17 00:00:00 2001 From: Akshay Gogeri Date: Tue, 12 Apr 2022 17:28:45 -0700 Subject: [PATCH 4/5] fix unit test --- python/tests/sql/test_function.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/tests/sql/test_function.py b/python/tests/sql/test_function.py index 183a0eb782..dd68ca8f8a 100644 --- a/python/tests/sql/test_function.py +++ b/python/tests/sql/test_function.py @@ -911,7 +911,7 @@ def test_st_reverse(self): } for input_geom, expected_geom in test_cases.items(): reversed_geometry = self.spark.sql("select ST_AsText(ST_Reverse(ST_GeomFromText({})))".format(input_geom)) - assert reversed_geometry.take(1)[0][0].wkt == expected_geom + assert reversed_geometry.take(1)[0][0] == expected_geom def calculate_st_is_ring(self, wkt): geometry_collected = self.__wkt_list_to_data_frame([wkt]). \ From 175cce31efad332b4f394d374de23838eb14b8b5 Mon Sep 17 00:00:00 2001 From: Akshay Gogeri Date: Tue, 12 Apr 2022 22:49:58 -0700 Subject: [PATCH 5/5] resolve review comments: Update ST_Reverse support from v1.2.1 --- docs/api/flink/Function.md | 2 +- docs/api/sql/Function.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api/flink/Function.md b/docs/api/flink/Function.md index 63ee21c983..6444eca823 100644 --- a/docs/api/flink/Function.md +++ b/docs/api/flink/Function.md @@ -107,7 +107,7 @@ Introduction: Return the geometry with vertex order reversed Format: `ST_Reverse (A:geometry)` -Since: `v1.2.0` +Since: `v1.2.1` Example: diff --git a/docs/api/sql/Function.md b/docs/api/sql/Function.md index 406c10a00a..a7a023387e 100644 --- a/docs/api/sql/Function.md +++ b/docs/api/sql/Function.md @@ -1036,7 +1036,7 @@ Introduction: Return the geometry with vertex order reversed Format: `ST_Reverse (A:geometry)` -Since: `v1.2.0` +Since: `v1.2.1` Example: