diff --git a/common/src/main/java/org/apache/sedona/common/Functions.java b/common/src/main/java/org/apache/sedona/common/Functions.java index 4c9ce0a12b..ecb0265bbd 100644 --- a/common/src/main/java/org/apache/sedona/common/Functions.java +++ b/common/src/main/java/org/apache/sedona/common/Functions.java @@ -2213,6 +2213,15 @@ public static Geometry points(Geometry geometry) { return geometry.getFactory().createMultiPointFromCoords(coordinates); } + public static Geometry rotateX(Geometry geometry, double angle) { + if (GeomUtils.isAnyGeomEmpty(geometry)) { + return geometry; + } + double sinAngle = Math.sin(angle); + double cosAngle = Math.cos(angle); + return affine(geometry, 1, 0, 0, 0, cosAngle, -sinAngle, 0, sinAngle, cosAngle, 0, 0, 0); + } + /** * Rotates a geometry by a given angle in radians. * diff --git a/common/src/test/java/org/apache/sedona/common/FunctionsTest.java b/common/src/test/java/org/apache/sedona/common/FunctionsTest.java index 8f0f13117b..9ba0cbbc27 100644 --- a/common/src/test/java/org/apache/sedona/common/FunctionsTest.java +++ b/common/src/test/java/org/apache/sedona/common/FunctionsTest.java @@ -3803,6 +3803,21 @@ public void points() throws ParseException { assertEquals("MULTIPOINT Z((0 0 1), (1 1 2), (2 2 3), (0 0 1))", result1); } + @Test + public void rotateX() throws ParseException { + Geometry lineString = Constructors.geomFromEWKT("LINESTRING (50 160, 50 50, 100 50)"); + String actual = Functions.asEWKT(Functions.rotateX(lineString, Math.PI)); + String expected = "LINESTRING (50 -160, 50 -50, 100 -50)"; + assertEquals(expected, actual); + + lineString = Constructors.geomFromWKT("LINESTRING(1 2 3, 1 1 1)", 1234); + Geometry geomActual = Functions.rotateX(lineString, Math.PI / 2); + actual = Functions.asWKT(geomActual); + expected = "LINESTRING Z(1 -3 2, 1 -0.9999999999999999 1)"; + assertEquals(1234, geomActual.getSRID()); + assertEquals(expected, actual); + } + @Test public void rotate() throws ParseException { Geometry lineString = Constructors.geomFromEWKT("LINESTRING (50 160, 50 50, 100 50)"); diff --git a/docs/api/flink/Function.md b/docs/api/flink/Function.md index 3e56772e66..27e054027f 100644 --- a/docs/api/flink/Function.md +++ b/docs/api/flink/Function.md @@ -3232,6 +3232,26 @@ Output: SRID=4326;POLYGON ((0 0, -0.8390715290764524 -0.5440211108893698, -0.2950504181870827 -1.383092639965822, 0 0)) ``` +## ST_RotateX + +Introduction: Performs a counter-clockwise rotation of the specified geometry around the X-axis by the given angle measured in radians. + +Format: `ST_RotateX(geometry: Geometry, angle: Double)` + +Since: `v1.7.0` + +SQL Example: + +```sql +SELECT ST_RotateX(ST_GeomFromEWKT('SRID=4326;POLYGON ((0 0, 1 0, 1 1, 0 0))'), 10) +``` + +Output: + +``` +SRID=4326;POLYGON ((0 0, 1 0, 1 -0.8390715290764524, 0 0)) +``` + ## ST_S2CellIDs Introduction: Cover the geometry with Google S2 Cells, return the corresponding cell IDs with the given level. diff --git a/docs/api/snowflake/vector-data/Function.md b/docs/api/snowflake/vector-data/Function.md index 11d6bbdb6f..df04e2f941 100644 --- a/docs/api/snowflake/vector-data/Function.md +++ b/docs/api/snowflake/vector-data/Function.md @@ -2473,6 +2473,24 @@ Output: SRID=4326;POLYGON ((0 0, -0.8390715290764524 -0.5440211108893698, -0.2950504181870827 -1.383092639965822, 0 0)) ``` +## ST_RotateX + +Introduction: Performs a counter-clockwise rotation of the specified geometry around the X-axis by the given angle measured in radians. + +Format: `ST_RotateX(geometry: Geometry, angle: Double)` + +SQL Example: + +```sql +SELECT ST_RotateX(ST_GeomFromEWKT('SRID=4326;POLYGON ((0 0, 1 0, 1 1, 0 0))'), 10) +``` + +Output: + +``` +SRID=4326;POLYGON ((0 0, 1 0, 1 -0.8390715290764524, 0 0)) +``` + ## ST_S2CellIDs Introduction: Cover the geometry with Google S2 Cells, return the corresponding cell IDs with the given level. diff --git a/docs/api/sql/Function.md b/docs/api/sql/Function.md index d1e86a21a8..eab48026c5 100644 --- a/docs/api/sql/Function.md +++ b/docs/api/sql/Function.md @@ -3312,6 +3312,26 @@ Output: SRID=4326;POLYGON ((0 0, -0.8390715290764524 -0.5440211108893698, -0.2950504181870827 -1.383092639965822, 0 0)) ``` +## ST_RotateX + +Introduction: Performs a counter-clockwise rotation of the specified geometry around the X-axis by the given angle measured in radians. + +Format: `ST_RotateX(geometry: Geometry, angle: Double)` + +Since: `v1.7.0` + +SQL Example: + +```sql +SELECT ST_RotateX(ST_GeomFromEWKT('SRID=4326;POLYGON ((0 0, 1 0, 1 1, 0 0))'), 10) +``` + +Output: + +``` +SRID=4326;POLYGON ((0 0, 1 0, 1 -0.8390715290764524, 0 0)) +``` + ## ST_S2CellIDs Introduction: Cover the geometry with Google S2 Cells, return the corresponding cell IDs with the given level. 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 ca8cbc0172..7ce8a444c5 100644 --- a/flink/src/main/java/org/apache/sedona/flink/Catalog.java +++ b/flink/src/main/java/org/apache/sedona/flink/Catalog.java @@ -102,6 +102,7 @@ public static UserDefinedFunction[] getFuncs() { new Functions.ST_ReducePrecision(), new Functions.ST_Reverse(), new Functions.ST_Rotate(), + new Functions.ST_RotateX(), new Functions.ST_GeometryN(), new Functions.ST_InteriorRingN(), new Functions.ST_PointN(), 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 3e5d606ee8..80d3a6e60f 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 @@ -1926,6 +1926,16 @@ public String eval( } } + public static class ST_RotateX 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, + @DataTypeHint(value = "Double") Double angle) { + Geometry geom = (Geometry) o; + return org.apache.sedona.common.Functions.rotateX(geom, angle); + } + } + public static class ST_Rotate extends ScalarFunction { @DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class) public Geometry eval( 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 1b6d1173f0..3b203c46e4 100644 --- a/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java +++ b/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java @@ -2609,6 +2609,22 @@ public void testIsValidReason() { // standards } + @Test + public void testRotateX() { + Table tbl = + tableEnv.sqlQuery( + "SELECT ST_GeomFromEWKT('POLYGON ((0 0, 2 0, 1 1, 2 2, 0 2, 1 1, 0 0))') AS geom"); + String actual = + (String) + first( + tbl.select(call(Functions.ST_RotateX.class.getSimpleName(), $("geom"), Math.PI)) + .as("geom") + .select(call(Functions.ST_AsEWKT.class.getSimpleName(), $("geom")))) + .getField(0); + String expected = "POLYGON ((0 0, 2 0, 1 -1, 2 -2, 0 -2, 1 -1, 0 0))"; + assertEquals(expected, actual); + } + @Test public void testRotate() { Table tbl = diff --git a/python/sedona/sql/st_functions.py b/python/sedona/sql/st_functions.py index f95a452886..6dbd41a991 100644 --- a/python/sedona/sql/st_functions.py +++ b/python/sedona/sql/st_functions.py @@ -2020,6 +2020,20 @@ def ST_IsCollection(geometry: ColumnOrName) -> Column: return _call_st_function("ST_IsCollection", geometry) +@validate_argument_types +def ST_RotateX(geometry: ColumnOrName, angle: Union[ColumnOrName, float]) -> Column: + """Returns geometry rotated by the given angle in X axis + + @param geometry: Geometry column or name + :type geometry: ColumnOrName + @param angle: Rotation angle in radians + :type angle: float + @return: X-axis rotated geometry + """ + + return _call_st_function("ST_RotateX", (geometry, angle)) + + @validate_argument_types def ST_Rotate(geometry: ColumnOrName, angle: Union[ColumnOrName, float], originX: Union[ColumnOrName, float] = None, originY: Union[ColumnOrName, float] = None, pointOrigin: ColumnOrName = None) -> Column: diff --git a/python/tests/sql/test_dataframe_api.py b/python/tests/sql/test_dataframe_api.py index 448743f5cc..cac26607d0 100644 --- a/python/tests/sql/test_dataframe_api.py +++ b/python/tests/sql/test_dataframe_api.py @@ -205,6 +205,7 @@ (stf.ST_ReducePrecision, ("geom", 1), "precision_reduce_point", "", "POINT (0.1 0.2)"), (stf.ST_RemovePoint, ("line", 1), "linestring_geom", "", "LINESTRING (0 0, 2 0, 3 0, 4 0, 5 0)"), (stf.ST_Reverse, ("line",), "linestring_geom", "", "LINESTRING (5 0, 4 0, 3 0, 2 0, 1 0, 0 0)"), + (stf.ST_RotateX, ("line", 10.0), "4D_line", "ST_ReducePrecision(geom, 2)", "LINESTRING Z (1 -0.3 -1.383092639965822, 2 -0.59 -2.766185279931644, 3 -0.89 -4.149277919897466, -1 0.3 1.383092639965822)"), (stf.ST_Rotate, ("line", 10.0), "linestring_geom", "ST_ReducePrecision(geom, 2)", "LINESTRING (0 0, -0.84 -0.54, -1.68 -1.09, -2.52 -1.63, -3.36 -2.18, -4.2 -2.72)"), (stf.ST_Rotate, ("line", 10.0, 0.0, 0.0), "linestring_geom", "ST_ReducePrecision(geom, 2)", "LINESTRING (0 0, -0.84 -0.54, -1.68 -1.09, -2.52 -1.63, -3.36 -2.18, -4.2 -2.72)"), (stf.ST_S2CellIDs, ("point", 30), "point_geom", "", [1153451514845492609]), @@ -427,6 +428,7 @@ (stf.ST_RemovePoint, ("", 1.0)), (stf.ST_Reverse, (None,)), (stf.ST_Rotate, (None,None,)), + (stf.ST_Rotate, (None,None)), (stf.ST_S2CellIDs, (None, 2)), (stf.ST_S2ToGeom, (None,)), (stf.ST_SetPoint, (None, 1, "")), diff --git a/python/tests/sql/test_function.py b/python/tests/sql/test_function.py index b6e7ceb66d..be49c52503 100644 --- a/python/tests/sql/test_function.py +++ b/python/tests/sql/test_function.py @@ -949,6 +949,17 @@ def test_st_add_point(self): ] assert (collected_geometries[0] == "LINESTRING (0 0, 1 1, 1 0, 21 52)") + def test_st_rotate_x(self): + baseDf = self.spark.sql("SELECT ST_GeomFromWKT('LINESTRING (50 160, 50 50, 100 50)') as geom1, ST_GeomFromWKT('LINESTRING(1 2 3, 1 1 1)') AS geom2") + + actual = baseDf.selectExpr("ST_RotateX(geom1, PI())").first()[0].wkt + expected = "LINESTRING (50 -160, 50 -50, 100 -50)" + assert expected == actual + + actual = baseDf.selectExpr("ST_RotateX(geom2, PI() / 2)").first()[0].wkt + expected = "LINESTRING Z (1 -3 2, 1 -0.9999999999999999 1)" + assert expected == actual + def test_st_remove_point(self): result_and_expected = [ [self.calculate_st_remove("Linestring(0 0, 1 1, 1 0, 0 0)", 0), "LINESTRING (1 1, 1 0, 0 0)"], diff --git a/snowflake-tester/src/test/java/org/apache/sedona/snowflake/snowsql/TestFunctions.java b/snowflake-tester/src/test/java/org/apache/sedona/snowflake/snowsql/TestFunctions.java index de94fb3a5d..2e957ad091 100644 --- a/snowflake-tester/src/test/java/org/apache/sedona/snowflake/snowsql/TestFunctions.java +++ b/snowflake-tester/src/test/java/org/apache/sedona/snowflake/snowsql/TestFunctions.java @@ -1207,6 +1207,14 @@ public void test_ST_Translate() { "GEOMETRYCOLLECTION Z(MULTIPOLYGON Z(((3 2 3, 3 3 3, 4 3 3, 4 2 3, 3 2 3)), ((3 4 3, 5 6 3, 5 7 3, 3 4 3))), POINT Z(3 3 4), LINESTRING ZEMPTY)"); } + @Test + public void test_ST_RotateX() { + registerUDF("ST_RotateX", byte[].class, double.class); + verifySqlSingleRes( + "SELECT sedona.ST_AsText(sedona.ST_RotateX(sedona.ST_GeomFromWKT('LINESTRING (0 0, 1 0, 1 1, 0 0)'), 10))", + "LINESTRING (0 0, 1 0, 1 -0.8390715290764524, 0 0)"); + } + @Test public void test_ST_Rotate() { registerUDF("ST_Rotate", byte[].class, double.class); diff --git a/snowflake-tester/src/test/java/org/apache/sedona/snowflake/snowsql/TestFunctionsV2.java b/snowflake-tester/src/test/java/org/apache/sedona/snowflake/snowsql/TestFunctionsV2.java index 5e80d8a46b..6f6f81f2e0 100644 --- a/snowflake-tester/src/test/java/org/apache/sedona/snowflake/snowsql/TestFunctionsV2.java +++ b/snowflake-tester/src/test/java/org/apache/sedona/snowflake/snowsql/TestFunctionsV2.java @@ -1162,6 +1162,15 @@ public void test_ST_Translate() { "POINT(2 5)"); } + @Test + public void test_ST_RotateX() { + registerUDFV2("ST_RotateX", String.class, double.class); + registerUDFV2("ST_ReducePrecision", String.class, int.class); + verifySqlSingleRes( + "select ST_AsText(ST_ReducePrecision(sedona.ST_RotateX(ST_GeometryFromWKT('LINESTRING (0 0, 1 0, 1 1, 0 0)'), 10),2))", + "LINESTRING(0 0,1 0,1 -0.84,0 0)"); + } + @Test public void test_ST_Rotate() { registerUDFV2("ST_Rotate", String.class, double.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 09c778cb2b..aca52f8479 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 @@ -1250,6 +1250,11 @@ public static byte[] ST_Translate(byte[] geom, double deltaX, double deltaY, dou Functions.translate(GeometrySerde.deserialize(geom), deltaX, deltaY, deltaZ)); } + @UDFAnnotations.ParamMeta(argNames = {"geometry", "angle"}) + public static byte[] ST_RotateX(byte[] geometry, double angle) { + return GeometrySerde.serialize(Functions.rotateX(GeometrySerde.deserialize(geometry), angle)); + } + @UDFAnnotations.ParamMeta(argNames = {"geom", "angle"}) public static byte[] ST_Rotate(byte[] geom, double angle) { return GeometrySerde.serialize(Functions.rotate(GeometrySerde.deserialize(geom), angle)); 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 a9a9bc208b..113ea91bf6 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 @@ -1490,6 +1490,14 @@ public static String ST_Translate(String geom, double deltaX, double deltaY, dou Functions.translate(GeometrySerde.deserGeoJson(geom), deltaX, deltaY, deltaZ)); } + @UDFAnnotations.ParamMeta( + argNames = {"geometry", "angle"}, + argTypes = {"Geometry", "double"}, + returnTypes = "Geometry") + public static String ST_RotateX(String geometry, double angle) { + return GeometrySerde.serGeoJson(Functions.rotateX(GeometrySerde.deserGeoJson(geometry), angle)); + } + @UDFAnnotations.ParamMeta( argNames = {"geom", "angle"}, argTypes = {"Geometry", "double"}, 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 45b54d1b63..9dda973ce0 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 @@ -230,6 +230,7 @@ object Catalog { function[ST_DWithin](), function[ST_IsValidReason](), function[ST_Rotate](), + function[ST_RotateX](), // Expression for rasters function[RS_NormalizedDifference](), function[RS_Mean](), diff --git a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala index 852a9e5f67..41dd995895 100644 --- a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala +++ b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala @@ -1691,6 +1691,13 @@ case class ST_IsValidReason(inputExpressions: Seq[Expression]) copy(inputExpressions = newChildren) } +case class ST_RotateX(inputExpressions: Seq[Expression]) + extends InferredExpression(inferrableFunction2(Functions.rotateX)) { + + protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = + copy(inputExpressions = newChildren) +} + case class ST_Rotate(inputExpressions: Seq[Expression]) extends InferredExpression( inferrableFunction2(Functions.rotate), diff --git a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_functions.scala b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_functions.scala index fdf0aea1df..6dd411b7f2 100644 --- a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_functions.scala +++ b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_functions.scala @@ -471,6 +471,13 @@ object st_functions extends DataFrameAPI { def ST_Reverse(geometry: Column): Column = wrapExpression[ST_Reverse](geometry) def ST_Reverse(geometry: String): Column = wrapExpression[ST_Reverse](geometry) + def ST_RotateX(geometry: Column, angle: Column): Column = + wrapExpression[ST_RotateX](geometry, angle) + def ST_RotateX(geometry: String, angle: Double): Column = + wrapExpression[ST_RotateX](geometry, angle) + def ST_RotateX(geometry: String, angle: String): Column = + wrapExpression[ST_RotateX](geometry, angle) + def ST_Rotate(geometry: Column, angle: Column): Column = wrapExpression[ST_Rotate](geometry, angle) def ST_Rotate(geometry: String, angle: Double): Column = diff --git a/spark/common/src/test/scala/org/apache/sedona/sql/PreserveSRIDSuite.scala b/spark/common/src/test/scala/org/apache/sedona/sql/PreserveSRIDSuite.scala index 7a9bdb4112..9d90bbaf81 100644 --- a/spark/common/src/test/scala/org/apache/sedona/sql/PreserveSRIDSuite.scala +++ b/spark/common/src/test/scala/org/apache/sedona/sql/PreserveSRIDSuite.scala @@ -109,6 +109,7 @@ class PreserveSRIDSuite extends TestBaseScala with TableDrivenPropertyChecks { ("ST_BoundingDiagonal(geom1)", 1000), ("ST_DelaunayTriangles(geom4)", 1000), ("ST_Rotate(geom1, 10)", 1000), + ("ST_RotateX(geom1, 10)", 1000), ("ST_Collect(geom1, geom2, geom3)", 1000), ("ST_GeneratePoints(geom1, 3)", 1000)) 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 4b114c08c8..fa2e0e6447 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 @@ -2226,6 +2226,26 @@ class dataFrameAPITestScala extends TestBaseScala { assertEquals("SRID=4326;POINT ZM(1 2 3 100)", point2) } + it("Should pass ST_RotateX") { + val geomTestCases = Map( + ( + 1, + "'LINESTRING (50 160, 50 50, 100 50)'", + Math.PI) -> "'LINESTRING (50 -160, 50 -50, 100 -50)'", + ( + 2, + "'LINESTRING(1 2 3, 1 1 1)'", + Math.PI / 2) -> "'LINESTRING Z(1 -3 2, 1 -0.9999999999999999 1)'") + + for (((index, geom, angle), expectedResult) <- geomTestCases) { + val baseDf = sparkSession.sql(s"SELECT ST_GeomFromEWKT($geom) as geom") + val df = baseDf.select(ST_AsEWKT(ST_RotateX("geom", angle))) + + val actual = df.take(1)(0).get(0).asInstanceOf[String] + assert(actual == expectedResult.stripPrefix("'").stripSuffix("'")) + } + } + it("Passed ST_Rotate") { val baseDf = sparkSession.sql( "SELECT ST_GeomFromEWKT('SRID=4326;POLYGON ((0 0, 2 0, 2 2, 0 2, 1 1, 0 0))') AS geom1, ST_GeomFromText('POINT (2 2)') AS geom2") diff --git a/spark/common/src/test/scala/org/apache/sedona/sql/functionTestScala.scala b/spark/common/src/test/scala/org/apache/sedona/sql/functionTestScala.scala index b231aa5b4b..72d5b990d0 100644 --- a/spark/common/src/test/scala/org/apache/sedona/sql/functionTestScala.scala +++ b/spark/common/src/test/scala/org/apache/sedona/sql/functionTestScala.scala @@ -3391,6 +3391,33 @@ class functionTestScala } } + it("Should pass ST_RotateX") { + val geomTestCases = Map( + ( + 1, + "'LINESTRING (50 160, 50 50, 100 50)'", + "PI()") -> "'LINESTRING (50 -160, 50 -50, 100 -50)'", + ( + 2, + "'LINESTRING(1 2 3, 1 1 1)'", + "PI()/2") -> "'LINESTRING Z(1 -3 2, 1 -0.9999999999999999 1)'") + + for (((index, geom, angle), expectedResult) <- geomTestCases) { + val df = sparkSession.sql(s""" + |SELECT + | ST_AsEWKT( + | ST_RotateX( + | ST_GeomFromEWKT($geom), + | $angle + | ) + | ) AS geom + """.stripMargin) + + val actual = df.take(1)(0).get(0).asInstanceOf[String] + assert(actual == expectedResult.stripPrefix("'").stripSuffix("'")) + } + } + it("Should pass ST_Rotate") { val geomTestCases = Map( (