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


The following commit(s) were added to refs/heads/master by this push:
     new 0e3d4c947 [SEDONA-645] Add ST_RotateX (#1554)
0e3d4c947 is described below

commit 0e3d4c947502a503bf2ea4fa2eb9b2d7ac9722d7
Author: Furqaan Khan <[email protected]>
AuthorDate: Mon Aug 19 13:53:59 2024 -0400

    [SEDONA-645] Add ST_RotateX (#1554)
    
    * feat: Add ST_RotateX
    
    * chore: remove show statement
    
    * fix: snowflake test
    
    * docs: add new sedona versioning
---
 .../java/org/apache/sedona/common/Functions.java   |  9 ++++++++
 .../org/apache/sedona/common/FunctionsTest.java    | 15 ++++++++++++
 docs/api/flink/Function.md                         | 20 ++++++++++++++++
 docs/api/snowflake/vector-data/Function.md         | 18 +++++++++++++++
 docs/api/sql/Function.md                           | 20 ++++++++++++++++
 .../main/java/org/apache/sedona/flink/Catalog.java |  1 +
 .../apache/sedona/flink/expressions/Functions.java | 10 ++++++++
 .../java/org/apache/sedona/flink/FunctionTest.java | 16 +++++++++++++
 python/sedona/sql/st_functions.py                  | 14 +++++++++++
 python/tests/sql/test_dataframe_api.py             |  2 ++
 python/tests/sql/test_function.py                  | 11 +++++++++
 .../sedona/snowflake/snowsql/TestFunctions.java    |  8 +++++++
 .../sedona/snowflake/snowsql/TestFunctionsV2.java  |  9 ++++++++
 .../org/apache/sedona/snowflake/snowsql/UDFs.java  |  5 ++++
 .../apache/sedona/snowflake/snowsql/UDFsV2.java    |  8 +++++++
 .../scala/org/apache/sedona/sql/UDF/Catalog.scala  |  1 +
 .../sql/sedona_sql/expressions/Functions.scala     |  7 ++++++
 .../sql/sedona_sql/expressions/st_functions.scala  |  7 ++++++
 .../org/apache/sedona/sql/PreserveSRIDSuite.scala  |  1 +
 .../apache/sedona/sql/dataFrameAPITestScala.scala  | 20 ++++++++++++++++
 .../org/apache/sedona/sql/functionTestScala.scala  | 27 ++++++++++++++++++++++
 21 files changed, 229 insertions(+)

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 4c9ce0a12..ecb0265bb 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 class Functions {
     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 8f0f13117..9ba0cbbc2 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 class FunctionsTest extends TestBase {
     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 3e56772e6..27e054027 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 11d6bbdb6..df04e2f94 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 d1e86a21a..eab48026c 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 ca8cbc017..7ce8a444c 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 class Catalog {
       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 3e5d606ee..80d3a6e60 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 class Functions {
     }
   }
 
+  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 1b6d1173f..3b203c46e 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 class FunctionTest extends TestBase {
     // 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 f95a45288..6dbd41a99 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 448743f5c..cac26607d 100644
--- a/python/tests/sql/test_dataframe_api.py
+++ b/python/tests/sql/test_dataframe_api.py
@@ -205,6 +205,7 @@ test_configurations = [
     (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 @@ wrong_type_configurations = [
     (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 b6e7ceb66..be49c5250 100644
--- a/python/tests/sql/test_function.py
+++ b/python/tests/sql/test_function.py
@@ -949,6 +949,17 @@ class TestPredicateJoin(TestBase):
         ]
         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 de94fb3a5..2e957ad09 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 class TestFunctions extends TestBase {
         "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 5e80d8a46..6f6f81f2e 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 class TestFunctionsV2 extends TestBase {
         "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 09c778cb2..aca52f847 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 class UDFs {
         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 a9a9bc208..113ea91bf 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 class UDFsV2 {
         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 45b54d1b6..9dda973ce 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 852a9e5f6..41dd99589 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 fdf0aea1d..6dd411b7f 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 7a9bdb411..9d90bbaf8 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 4b114c08c..fa2e0e644 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 b231aa5b4..72d5b990d 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(
       (

Reply via email to