Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SEDONA-645] Add ST_RotateX #1554

Merged
merged 4 commits into from
Aug 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)");
Expand Down
20 changes: 20 additions & 0 deletions docs/api/flink/Function.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
18 changes: 18 additions & 0 deletions docs/api/snowflake/vector-data/Function.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
20 changes: 20 additions & 0 deletions docs/api/sql/Function.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions flink/src/main/java/org/apache/sedona/flink/Catalog.java
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
16 changes: 16 additions & 0 deletions flink/src/test/java/org/apache/sedona/flink/FunctionTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down
14 changes: 14 additions & 0 deletions python/sedona/sql/st_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 2 additions & 0 deletions python/tests/sql/test_dataframe_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]),
Expand Down Expand Up @@ -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, "")),
Expand Down
11 changes: 11 additions & 0 deletions python/tests/sql/test_function.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)"],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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](),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
(
Expand Down
Loading