Skip to content

Commit

Permalink
[SEDONA-617] Add ST_Rotate (#1497)
Browse files Browse the repository at this point in the history
* init

* update scala test

* add scala dataframeapi tests

* add tests for flink

* add tests for python

* add tests for snowflake

* add java tests

* add docs

* fix typo

* fix python test

* fix snowflake test

* add ST_ReducePrecision for snowflake tests

* fix tests

* fix python implementation

* Fix snowflake implementation

* fix typo

* Fix snowflake test

* Fix snowflake test

* Fix whitespaces in tests

* fix docs
  • Loading branch information
prantogg committed Jun 27, 2024
1 parent 4583e9e commit a473aef
Show file tree
Hide file tree
Showing 19 changed files with 477 additions and 0 deletions.
56 changes: 56 additions & 0 deletions common/src/main/java/org/apache/sedona/common/Functions.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import org.locationtech.jts.algorithm.hull.ConcaveHull;
import org.locationtech.jts.geom.*;
import org.locationtech.jts.geom.impl.CoordinateArraySequence;
import org.locationtech.jts.geom.util.AffineTransformation;
import org.locationtech.jts.geom.util.GeometryFixer;
import org.locationtech.jts.io.ByteOrderValues;
import org.locationtech.jts.io.gml2.GMLWriter;
Expand Down Expand Up @@ -2101,4 +2102,59 @@ public static Geometry points(Geometry geometry) {
// Creating a MultiPoint from the extracted coordinates
return GEOMETRY_FACTORY.createMultiPointFromCoords(coordinates);
}

/**
* Rotates a geometry by a given angle in radians.
*
* @param geometry The input geometry to rotate.
* @param angle The angle in radians to rotate the geometry.
* @return The rotated geometry.
*/
public static Geometry rotate(Geometry geometry, double angle) {
if (geometry == null || geometry.isEmpty()) {
return geometry;
}
AffineTransformation rotation = AffineTransformation.rotationInstance(angle);
return rotation.transform(geometry);
}

/**
* Rotates a geometry by a given angle in radians around a given origin point (x, y).
*
* @param geometry The input geometry to rotate.
* @param angle The angle in radians to rotate the geometry.
* @param originX The x coordinate of the origin point around which to rotate.
* @param originY The y coordinate of the origin point around which to rotate.
* @return The rotated geometry.
*/
public static Geometry rotate(Geometry geometry, double angle, double originX, double originY) {
if (geometry == null || geometry.isEmpty()) {
return geometry;
}
AffineTransformation rotation = AffineTransformation.rotationInstance(angle, originX, originY);
return rotation.transform(geometry);
}

/**
* Rotates a geometry by a given angle in radians around a given origin point.
*
* @param geometry The input geometry to rotate.
* @param angle The angle in radians to rotate the geometry.
* @param pointOrigin The origin point around which to rotate.
* @return The rotated geometry.
* @throws IllegalArgumentException if the pointOrigin is not a Point geometry.
*/
public static Geometry rotate(Geometry geometry, double angle, Geometry pointOrigin) {
if (geometry == null || geometry.isEmpty()) {
return geometry;
}
if (pointOrigin == null || pointOrigin.isEmpty() || !(pointOrigin instanceof Point)) {
throw new IllegalArgumentException("The origin must be a non-empty Point geometry.");
}
Point origin = (Point) pointOrigin;
double originX = origin.getX();
double originY = origin.getY();
AffineTransformation rotation = AffineTransformation.rotationInstance(angle, originX, originY);
return rotation.transform(geometry);
}
}
22 changes: 22 additions & 0 deletions common/src/test/java/org/apache/sedona/common/FunctionsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3630,4 +3630,26 @@ public void points() throws ParseException {
String result1 = Functions.asEWKT(Functions.points(geometry3D));
assertEquals("MULTIPOINT Z((0 0 1), (1 1 2), (2 2 3), (0 0 1))", result1);
}

@Test
public void rotate() throws ParseException {
Geometry lineString = Constructors.geomFromEWKT("LINESTRING (50 160, 50 50, 100 50)");
String result = Functions.asEWKT(Functions.rotate(lineString, Math.PI));
assertEquals(
"LINESTRING (-50.00000000000002 -160, -50.00000000000001 -49.99999999999999, -100 -49.999999999999986)",
result);

lineString = Constructors.geomFromEWKT("LINESTRING (0 0, 1 0, 1 1, 0 0)");
Geometry pointOrigin = Constructors.geomFromEWKT("POINT (0 0)");
result = Functions.asEWKT(Functions.rotate(lineString, 10, pointOrigin));
assertEquals(
"LINESTRING (0 0, -0.8390715290764524 -0.5440211108893698, -0.2950504181870827 -1.383092639965822, 0 0)",
result);

lineString = Constructors.geomFromEWKT("LINESTRING (0 0, 1 0, 1 1, 0 0)");
result = Functions.asEWKT(Functions.rotate(lineString, 10, 0, 0));
assertEquals(
"LINESTRING (0 0, -0.8390715290764524 -0.5440211108893698, -0.2950504181870827 -1.383092639965822, 0 0)",
result);
}
}
26 changes: 26 additions & 0 deletions docs/api/flink/Function.md
Original file line number Diff line number Diff line change
Expand Up @@ -3084,6 +3084,32 @@ Output:
LINESTRING(0 0, 1 0)
```

## ST_Rotate

Introduction: Rotates a geometry by a specified angle in radians counter-clockwise around a given origin point. The origin for rotation can be specified as either a POINT geometry or x and y coordinates. If the origin is not specified, the geometry is rotated around POINT(0 0).

Formats;

`ST_Rotate (geometry: Geometry, angle: Double)`

`ST_Rotate (geometry: Geometry, angle: Double, originX: Double, originY: Double)`

`ST_Rotate (geometry: Geometry, angle: Double, pointOrigin: Geometry)`

Since: `v1.6.1`

SQL Example:

```sql
SELECT ST_Rotate(ST_GeomFromEWKT('SRID=4326;POLYGON ((0 0, 1 0, 1 1, 0 0))'), 10, 0, 0)
```

Output:

```
SRID=4326;POLYGON ((0 0, -0.8390715290764524 -0.5440211108893698, -0.2950504181870827 -1.383092639965822, 0 0))
```

## ST_S2CellIDs

Introduction: Cover the geometry with Google S2 Cells, return the corresponding cell IDs with the given level.
Expand Down
24 changes: 24 additions & 0 deletions docs/api/snowflake/vector-data/Function.md
Original file line number Diff line number Diff line change
Expand Up @@ -2317,6 +2317,30 @@ Result:
+---------------------------------------------------------------+
```

## ST_Rotate

Introduction: Rotates a geometry by a specified angle in radians counter-clockwise around a given origin point. The origin for rotation can be specified as either a POINT geometry or x and y coordinates. If the origin is not specified, the geometry is rotated around POINT(0 0).

Formats;

`ST_Rotate (geometry: Geometry, angle: Double)`

`ST_Rotate (geometry: Geometry, angle: Double, originX: Double, originY: Double)`

`ST_Rotate (geometry: Geometry, angle: Double, pointOrigin: Geometry)`

SQL Example:

```sql
SELECT ST_Rotate(ST_GeomFromEWKT('SRID=4326;POLYGON ((0 0, 1 0, 1 1, 0 0))'), 10, 0, 0)
```

Output:

```
SRID=4326;POLYGON ((0 0, -0.8390715290764524 -0.5440211108893698, -0.2950504181870827 -1.383092639965822, 0 0))
```

## ST_S2CellIDs

Introduction: Cover the geometry with Google S2 Cells, return the corresponding cell IDs with the given level.
Expand Down
26 changes: 26 additions & 0 deletions docs/api/sql/Function.md
Original file line number Diff line number Diff line change
Expand Up @@ -3164,6 +3164,32 @@ Output:
LINESTRING (3 6, 2 4, 1 2, 0 0)
```

## ST_Rotate

Introduction: Rotates a geometry by a specified angle in radians counter-clockwise around a given origin point. The origin for rotation can be specified as either a POINT geometry or x and y coordinates. If the origin is not specified, the geometry is rotated around POINT(0 0).

Formats;

`ST_Rotate (geometry: Geometry, angle: Double)`

`ST_Rotate (geometry: Geometry, angle: Double, originX: Double, originY: Double)`

`ST_Rotate (geometry: Geometry, angle: Double, pointOrigin: Geometry)`

Since: `v1.6.1`

SQL Example:

```sql
SELECT ST_Rotate(ST_GeomFromEWKT('SRID=4326;POLYGON ((0 0, 1 0, 1 1, 0 0))'), 10, 0, 0)
```

Output:

```
SRID=4326;POLYGON ((0 0, -0.8390715290764524 -0.5440211108893698, -0.2950504181870827 -1.383092639965822, 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 @@ -100,6 +100,7 @@ public static UserDefinedFunction[] getFuncs() {
new Functions.ST_PointOnSurface(),
new Functions.ST_ReducePrecision(),
new Functions.ST_Reverse(),
new Functions.ST_Rotate(),
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 @@ -1869,4 +1869,38 @@ public String eval(
return org.apache.sedona.common.Functions.isValidReason(geom, flag);
}
}

public static class ST_Rotate 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 o1,
@DataTypeHint(value = "Double") Double angle) {
Geometry geom1 = (Geometry) o1;
return org.apache.sedona.common.Functions.rotate(geom1, angle);
}

@DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class)
public Geometry eval(
@DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class)
Object o1,
@DataTypeHint(value = "Double") Double angle,
@DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class)
Object o2) {
Geometry geom1 = (Geometry) o1;
Geometry geom2 = (Geometry) o2;
return org.apache.sedona.common.Functions.rotate(geom1, angle, geom2);
}

@DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class)
public Geometry eval(
@DataTypeHint(value = "RAW", bridgedTo = org.locationtech.jts.geom.Geometry.class)
Object o1,
@DataTypeHint(value = "Double") Double angle,
@DataTypeHint(value = "Double") Double originX,
@DataTypeHint(value = "Double") Double originY) {
Geometry geom1 = (Geometry) o1;
return org.apache.sedona.common.Functions.rotate(geom1, angle, originX, originY);
}
}
}
45 changes: 45 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 @@ -2505,4 +2505,49 @@ public void testIsValidReason() {
esriValidityReason); // Expecting an error related to interior disconnection as per ESRI
// standards
}

@Test
public void testRotate() {
Table tbl =
tableEnv.sqlQuery(
"SELECT ST_GeomFromEWKT('POLYGON ((0 0, 2 0, 1 1, 2 2, 0 2, 1 1, 0 0))') AS geom1, ST_GeomFromEWKT('POINT (2 0)') AS geom2");
String actual =
(String)
first(
tbl.select(call(Functions.ST_Rotate.class.getSimpleName(), $("geom1"), Math.PI))
.as("geom")
.select(call(Functions.ST_AsEWKT.class.getSimpleName(), $("geom"))))
.getField(0);
String expected =
"POLYGON ((0 0, -2 0.0000000000000002, -1.0000000000000002 -0.9999999999999999, -2.0000000000000004 -1.9999999999999998, -0.0000000000000002 -2, -1.0000000000000002 -0.9999999999999999, 0 0))";
assertEquals(expected, actual);

actual =
(String)
first(
tbl.select(
call(
Functions.ST_Rotate.class.getSimpleName(),
$("geom1"),
50,
$("geom2")))
.as("geom")
.select(call(Functions.ST_AsEWKT.class.getSimpleName(), $("geom"))))
.getField(0);
expected =
"POLYGON ((0.0700679430157733 0.5247497074078575, 2 0, 1.2974088252118154 1.227340882196042, 2.5247497074078575 1.9299320569842267, 0.5948176504236309 2.454681764392084, 1.2974088252118154 1.227340882196042, 0.0700679430157733 0.5247497074078575))";
assertEquals(expected, actual);

actual =
(String)
first(
tbl.select(
call(Functions.ST_Rotate.class.getSimpleName(), $("geom1"), 50, 2, 0))
.as("geom")
.select(call(Functions.ST_AsEWKT.class.getSimpleName(), $("geom"))))
.getField(0);
expected =
"POLYGON ((0.0700679430157733 0.5247497074078575, 2 0, 1.2974088252118154 1.227340882196042, 2.5247497074078575 1.9299320569842267, 0.5948176504236309 2.454681764392084, 1.2974088252118154 1.227340882196042, 0.0700679430157733 0.5247497074078575))";
assertEquals(expected, actual);
}
}
28 changes: 28 additions & 0 deletions python/sedona/sql/st_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -1979,3 +1979,31 @@ def ST_IsCollection(geometry: ColumnOrName) -> Column:
:rtype: Column
"""
return _call_st_function("ST_IsCollection", geometry)


@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:
"""Return a counter-clockwise rotated geometry along the specified origin.
:param geometry: Geometry column or name.
:type geometry: ColumnOrName
:param angle: Rotation angle in radians.
:type angle: Union[ColumnOrName, float]
:param originX: Optional x-coordinate of the origin.
:type originX: Union[ColumnOrName, float]
:param originY: Optional y-coordinate of the origin.
:type originY: Union[ColumnOrName, float]
:param pointOrigin: Optional origin point for rotation.
:type pointOrigin: ColumnOrName
:return: Returns the rotated geometry.
:rtype: Column
"""
if pointOrigin is not None:
args = (geometry, angle, pointOrigin)
elif originX is not None and originY is not None:
args = (geometry, angle, originX, originY)
else:
args = (geometry, angle)

return _call_st_function("ST_Rotate", args)
3 changes: 3 additions & 0 deletions python/tests/sql/test_dataframe_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,8 @@
(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_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]),
(stf.ST_S2ToGeom, (lambda: f.expr("array(1154047404513689600)"),), "null", "ST_ReducePrecision(geom[0], 5)", "POLYGON ((0 2.46041, 2.46041 2.46041, 2.46041 0, 0 0, 0 2.46041))"),
(stf.ST_SetPoint, ("line", 1, lambda: f.expr("ST_Point(1.0, 1.0)")), "linestring_geom", "", "LINESTRING (0 0, 1 1, 2 0, 3 0, 4 0, 5 0)"),
Expand Down Expand Up @@ -413,6 +415,7 @@
(stf.ST_RemovePoint, ("", None)),
(stf.ST_RemovePoint, ("", 1.0)),
(stf.ST_Reverse, (None,)),
(stf.ST_Rotate, (None,None,)),
(stf.ST_S2CellIDs, (None, 2)),
(stf.ST_S2ToGeom, (None,)),
(stf.ST_SetPoint, (None, 1, "")),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1164,4 +1164,20 @@ public void test_ST_Translate() {
"SELECT sedona.ST_AsText(sedona.ST_Translate(sedona.ST_GeomFromText('GEOMETRYCOLLECTION(MULTIPOLYGON (((1 0 0, 1 1 0, 2 1 0, 2 0 0, 1 0 0)), ((1 2 0, 3 4 0, 3 5 0, 1 2 0))), POINT(1 1 1), LINESTRING EMPTY))'), 2, 2, 3))",
"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_Rotate() {
registerUDF("ST_Rotate", byte[].class, double.class);
verifySqlSingleRes(
"SELECT sedona.ST_AsText(sedona.ST_Rotate(sedona.ST_GeomFromWKT('LINESTRING (0 0, 1 0, 1 1, 0 0)'), 10))",
"LINESTRING (0 0, -0.8390715290764524 -0.5440211108893698, -0.2950504181870827 -1.383092639965822, 0 0)");
registerUDF("ST_Rotate", byte[].class, double.class, byte[].class);
verifySqlSingleRes(
"SELECT sedona.ST_AsText(sedona.ST_Rotate(sedona.ST_GeomFromWKT('LINESTRING (0 0, 1 0, 1 1, 0 0)'), 10, sedona.ST_GeomFromWKT('POINT (0 0)')))",
"LINESTRING (0 0, -0.8390715290764524 -0.5440211108893698, -0.2950504181870827 -1.383092639965822, 0 0)");
registerUDF("ST_Rotate", byte[].class, double.class, double.class, double.class);
verifySqlSingleRes(
"SELECT sedona.ST_AsText(sedona.ST_Rotate(sedona.ST_GeomFromWKT('LINESTRING (0 0, 1 0, 1 1, 0 0)'), 10, 0, 0))",
"LINESTRING (0 0, -0.8390715290764524 -0.5440211108893698, -0.2950504181870827 -1.383092639965822, 0 0)");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1119,4 +1119,20 @@ public void test_ST_Translate() {
"SELECT ST_AsText(sedona.ST_Translate(ST_GeometryFromWKT('POINT(1 3)'), 1, 2))",
"POINT(2 5)");
}

@Test
public void test_ST_Rotate() {
registerUDFV2("ST_Rotate", String.class, double.class);
verifySqlSingleRes(
"select ST_AsText(ST_ReducePrecision(sedona.ST_Rotate(ST_GeometryFromWKT('LINESTRING (0 0, 1 0, 1 1, 0 0)'), 10),2))",
"LINESTRING(0 0,-0.84 -0.54,-0.3 -1.38,0 0)");
registerUDFV2("ST_Rotate", String.class, double.class, String.class);
verifySqlSingleRes(
"select ST_AsText(ST_ReducePrecision(sedona.ST_Rotate(ST_GeometryFromWKT('LINESTRING (0 0, 1 0, 1 1, 0 0)'), 10, ST_GeometryFromWKT('POINT (0 0)')),2))",
"LINESTRING(0 0,-0.84 -0.54,-0.3 -1.38,0 0)");
registerUDFV2("ST_Rotate", String.class, double.class, double.class, double.class);
verifySqlSingleRes(
"select ST_AsText(ST_ReducePrecision(sedona.ST_Rotate(ST_GeometryFromWKT('LINESTRING (0 0, 1 0, 1 1, 0 0)'), 10, 0, 0),2))",
"LINESTRING(0 0,-0.84 -0.54,-0.3 -1.38,0 0)");
}
}
Loading

0 comments on commit a473aef

Please sign in to comment.