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 1909171502 [GH-2552] Add ST_OrientedEnvelope (#2553)
1909171502 is described below
commit 1909171502fe16cd3d7dec6128b9b29e372819da
Author: Joonas Pessi <[email protected]>
AuthorDate: Sun Dec 7 02:40:33 2025 +0200
[GH-2552] Add ST_OrientedEnvelope (#2553)
---
.../java/org/apache/sedona/common/Functions.java | 5 ++
.../org/apache/sedona/common/FunctionsTest.java | 70 ++++++++++++++++++++++
docs/api/flink/Function.md | 20 +++++++
docs/api/snowflake/vector-data/Function.md | 14 +++++
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 | 10 ++++
python/sedona/spark/sql/st_functions.py | 12 ++++
python/tests/sql/test_dataframe_api.py | 12 ++++
python/tests/sql/test_function.py | 11 ++++
.../sedona/snowflake/snowsql/TestFunctions.java | 8 +++
.../sedona/snowflake/snowsql/TestFunctionsV2.java | 8 +++
.../org/apache/sedona/snowflake/snowsql/UDFs.java | 5 ++
.../apache/sedona/snowflake/snowsql/UDFsV2.java | 9 +++
.../scala/org/apache/sedona/sql/UDF/Catalog.scala | 1 +
.../sql/sedona_sql/expressions/Functions.scala | 8 +++
.../sql/sedona_sql/expressions/st_functions.scala | 5 ++
.../apache/sedona/sql/dataFrameAPITestScala.scala | 9 +++
.../org/apache/sedona/sql/functionTestScala.scala | 17 ++++++
20 files changed, 255 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 0976316be9..0be88a9b1b 100644
--- a/common/src/main/java/org/apache/sedona/common/Functions.java
+++ b/common/src/main/java/org/apache/sedona/common/Functions.java
@@ -36,6 +36,7 @@ import org.apache.sedona.common.sphere.Spheroid;
import org.apache.sedona.common.subDivide.GeometrySubDivider;
import org.apache.sedona.common.utils.*;
import org.locationtech.jts.algorithm.Angle;
+import org.locationtech.jts.algorithm.MinimumAreaRectangle;
import org.locationtech.jts.algorithm.MinimumBoundingCircle;
import org.locationtech.jts.algorithm.Orientation;
import org.locationtech.jts.algorithm.construct.LargestEmptyCircle;
@@ -1227,6 +1228,10 @@ public class Functions {
return circle;
}
+ public static Geometry orientedEnvelope(Geometry geometry) {
+ return MinimumAreaRectangle.getMinimumRectangle(geometry);
+ }
+
public static InscribedCircle maximumInscribedCircle(Geometry geometry) {
// Calculating the tolerance
Envelope envelope = geometry.getEnvelopeInternal();
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 71983f7344..8098944865 100644
--- a/common/src/test/java/org/apache/sedona/common/FunctionsTest.java
+++ b/common/src/test/java/org/apache/sedona/common/FunctionsTest.java
@@ -756,6 +756,76 @@ public class FunctionsTest extends TestBase {
assertEquals(3857, centroid.getSRID());
}
+ @Test
+ public void orientedEnvelope() throws ParseException {
+ Geometry axisAlignedRect = Constructors.geomFromWKT("POLYGON ((0 0, 4 0, 4
2, 0 2, 0 0))", 0);
+ String actual =
Functions.asWKT(Functions.orientedEnvelope(axisAlignedRect));
+ String expected = "POLYGON ((0 0, 0 2, 4 2, 4 0, 0 0))";
+ assertEquals(expected, actual);
+
+ Geometry rotatedSquare = Constructors.geomFromWKT("POLYGON ((1 0, 2 1, 1
2, 0 1, 1 0))", 0);
+ actual = Functions.asWKT(Functions.orientedEnvelope(rotatedSquare));
+ expected = "POLYGON ((1 0, 0 1, 1 2, 2 1, 1 0))";
+ assertEquals(expected, actual);
+
+ Geometry diagonalPolygon = Constructors.geomFromWKT("POLYGON ((0 0, 1 0, 5
4, 4 4, 0 0))", 0);
+ actual = Functions.asWKT(Functions.orientedEnvelope(diagonalPolygon));
+ expected = "POLYGON ((0 0, 4.5 4.5, 5 4, 0.5 -0.5, 0 0))";
+ assertEquals(expected, actual);
+
+ Geometry narrowRect = Constructors.geomFromWKT("POLYGON ((0 0, 10 0, 10 1,
0 1, 0 0))", 0);
+ actual = Functions.asWKT(Functions.orientedEnvelope(narrowRect));
+ expected = "POLYGON ((0 0, 0 1, 10 1, 10 0, 0 0))";
+ assertEquals(expected, actual);
+
+ Geometry triangle = Constructors.geomFromWKT("POLYGON ((0 0, 4 0, 2 3, 0
0))", 0);
+ actual = Functions.asWKT(Functions.orientedEnvelope(triangle));
+ expected = "POLYGON ((4 0, 0 0, 0 3, 4 3, 4 0))";
+ assertEquals(expected, actual);
+
+ Geometry irregularPolygon =
+ Constructors.geomFromWKT("POLYGON ((0 0, 3 1, 5 0, 4 4, 1 3, 0 0))",
0);
+ actual =
+
Functions.asWKT(Functions.reducePrecision(Functions.orientedEnvelope(irregularPolygon),
2));
+ expected = "POLYGON ((5 0, 0.29 -1.18, -0.71 2.82, 4 4, 5 0))";
+ assertEquals(expected, actual);
+
+ Geometry point = Constructors.geomFromWKT("POINT (1 2)", 0);
+ actual = Functions.asWKT(Functions.orientedEnvelope(point));
+ expected = "POINT (1 2)";
+ assertEquals(expected, actual);
+
+ Geometry line = Constructors.geomFromWKT("LINESTRING (0 0, 10 0)", 0);
+ actual = Functions.asWKT(Functions.orientedEnvelope(line));
+ expected = "LINESTRING (0 0, 10 0)";
+ assertEquals(expected, actual);
+
+ Geometry diagonalLine = Constructors.geomFromWKT("LINESTRING (0 0, 5 5)",
0);
+ actual = Functions.asWKT(Functions.orientedEnvelope(diagonalLine));
+ expected = "LINESTRING (0 0, 5 5)";
+ assertEquals(expected, actual);
+
+ Geometry empty = Constructors.geomFromWKT("POLYGON EMPTY", 0);
+ Geometry orientedEmpty = Functions.orientedEnvelope(empty);
+ assertTrue(orientedEmpty.isEmpty());
+
+ Geometry geomWithSRID = Constructors.geomFromWKT("POLYGON ((0 0, 1 0, 1 1,
0 1, 0 0))", 4326);
+ Geometry orientedWithSRID = Functions.orientedEnvelope(geomWithSRID);
+ assertEquals(4326, orientedWithSRID.getSRID());
+
+ Geometry multiPoint = Constructors.geomFromWKT("MULTIPOINT ((0 0), (-1
-1), (3 2))", 0);
+ actual =
Functions.asWKT(Functions.reducePrecision(Functions.orientedEnvelope(multiPoint),
2));
+ expected = "POLYGON ((-1 -1, -1.12 -0.84, 2.88 2.16, 3 2, -1 -1))";
+ assertEquals(expected, actual);
+
+ Geometry linestring = Constructors.geomFromWKT("LINESTRING (55 75, 125
150)", 0);
+ Geometry pointGeom = Constructors.geomFromWKT("POINT (20 80)", 0);
+ Geometry collection = linestring.union(pointGeom);
+ actual =
Functions.asWKT(Functions.reducePrecision(Functions.orientedEnvelope(collection),
2));
+ expected = "POLYGON ((125 150, 138.08 130.38, 33.08 60.38, 20 80, 125
150))";
+ assertEquals(expected, actual);
+ }
+
@Test
public void getGoogleS2CellIDsPoint() {
Point point = GEOMETRY_FACTORY.createPoint(new Coordinate(1, 2));
diff --git a/docs/api/flink/Function.md b/docs/api/flink/Function.md
index 98a7dc1ba1..d74552617f 100644
--- a/docs/api/flink/Function.md
+++ b/docs/api/flink/Function.md
@@ -1310,6 +1310,26 @@ Output:
POLYGON ((0 0, 0 3, 1 3, 1 0, 0 0))
```
+## ST_OrientedEnvelope
+
+Introduction: Returns the minimum-area rotated rectangle enclosing a geometry.
The rectangle may be rotated relative to the coordinate axes. Degenerate inputs
may result in a Point or LineString being returned.
+
+Format: `ST_OrientedEnvelope(geom: Geometry)`
+
+Since: `v1.8.1`
+
+Example:
+
+```sql
+SELECT ST_OrientedEnvelope(ST_GeomFromWKT('POLYGON ((0 0, 1 0, 5 4, 4 4, 0
0))'))
+```
+
+Output:
+
+```
+POLYGON ((0 0, 4.5 4.5, 5 4, 0.5 -0.5, 0 0))
+```
+
## ST_Expand
Introduction: Returns a geometry expanded from the bounding box of the input.
The expansion can be specified in two ways:
diff --git a/docs/api/snowflake/vector-data/Function.md
b/docs/api/snowflake/vector-data/Function.md
index 14d9ca417e..28eb433c3b 100644
--- a/docs/api/snowflake/vector-data/Function.md
+++ b/docs/api/snowflake/vector-data/Function.md
@@ -1040,6 +1040,20 @@ SELECT ST_Envelope(polygondf.countyshape)
FROM polygondf
```
+## ST_OrientedEnvelope
+
+Introduction: Returns the minimum-area rotated rectangle enclosing a geometry.
The rectangle may be rotated relative to the coordinate axes. Degenerate inputs
may result in a Point or LineString being returned.
+
+Format: `ST_OrientedEnvelope(geometry: Geometry)`
+
+SQL example:
+
+```sql
+SELECT ST_OrientedEnvelope(ST_GeomFromWKT('POLYGON ((0 0, 1 0, 5 4, 4 4, 0
0))'))
+```
+
+Output: `POLYGON ((0 0, 4.5 4.5, 5 4, 0.5 -0.5, 0 0))`
+
## ST_Expand
Introduction: Returns a geometry expanded from the bounding box of the input.
The expansion can be specified in two ways:
diff --git a/docs/api/sql/Function.md b/docs/api/sql/Function.md
index 447707c329..9c61520f6d 100644
--- a/docs/api/sql/Function.md
+++ b/docs/api/sql/Function.md
@@ -3490,6 +3490,26 @@ Output:
3
```
+## ST_OrientedEnvelope
+
+Introduction: Returns the minimum-area rotated rectangle enclosing a geometry.
The rectangle may be rotated relative to the coordinate axes. Degenerate inputs
may result in a Point or LineString being returned.
+
+Format: `ST_OrientedEnvelope(geom: Geometry)`
+
+Since: `v1.8.1`
+
+SQL Example
+
+```sql
+SELECT ST_OrientedEnvelope(ST_GeomFromWKT('POLYGON ((0 0, 1 0, 5 4, 4 4, 0
0))'))
+```
+
+Output:
+
+```
+POLYGON ((0 0, 4.5 4.5, 5 4, 0.5 -0.5, 0 0))
+```
+
## ST_Perimeter
Introduction: This function calculates the 2D perimeter of a given geometry.
It supports Polygon, MultiPolygon, and GeometryCollection geometries (as long
as the GeometryCollection contains polygonal geometries). For other types, it
returns 0. To measure lines, use [ST_Length](#st_length).
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 af62005483..dcb593e9c3 100644
--- a/flink/src/main/java/org/apache/sedona/flink/Catalog.java
+++ b/flink/src/main/java/org/apache/sedona/flink/Catalog.java
@@ -73,6 +73,7 @@ public class Catalog {
new Functions.ST_CrossesDateLine(),
new Functions.ST_Expand(),
new Functions.ST_Envelope(),
+ new Functions.ST_OrientedEnvelope(),
new Functions.ST_Difference(),
new Functions.ST_Dimension(),
new Functions.ST_Distance(),
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 211d7db796..39fb3da564 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
@@ -289,6 +289,16 @@ public class Functions {
}
}
+ public static class ST_OrientedEnvelope 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 org.apache.sedona.common.Functions.orientedEnvelope(geom);
+ }
+ }
+
public static class ST_Expand 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 afa520af14..d1095c98e7 100644
--- a/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java
+++ b/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java
@@ -409,6 +409,16 @@ public class FunctionTest extends TestBase {
first(linestringTable).getField(0).toString());
}
+ @Test
+ public void testOrientedEnvelope() {
+ Table polygonTable =
+ tableEnv.sqlQuery("SELECT ST_GeomFromWKT('POLYGON ((0 0, 1 0, 5 4, 4
4, 0 0))') as geom");
+ Table resultTable =
+
polygonTable.select(call(Functions.ST_OrientedEnvelope.class.getSimpleName(),
$("geom")));
+ Geometry result = (Geometry) first(resultTable).getField(0);
+ assertEquals("POLYGON ((0 0, 4.5 4.5, 5 4, 0.5 -0.5, 0 0))",
result.toString());
+ }
+
@Test
public void testExpand() {
Table baseTable =
diff --git a/python/sedona/spark/sql/st_functions.py
b/python/sedona/spark/sql/st_functions.py
index 69a8c5ffef..bc6b2d5e20 100644
--- a/python/sedona/spark/sql/st_functions.py
+++ b/python/sedona/spark/sql/st_functions.py
@@ -1595,6 +1595,18 @@ def ST_NumInteriorRing(geometry: ColumnOrName) -> Column:
return _call_st_function("ST_NumInteriorRing", geometry)
+@validate_argument_types
+def ST_OrientedEnvelope(geometry: ColumnOrName) -> Column:
+ """Return the minimum rotated rectangle enclosing a geometry.
+
+ :param geometry: Geometry column to compute oriented envelope for.
+ :type geometry: ColumnOrName
+ :return: Minimum area rotated rectangle as a geometry column.
+ :rtype: Column
+ """
+ return _call_st_function("ST_OrientedEnvelope", geometry)
+
+
@validate_argument_types
def ST_PointN(geometry: ColumnOrName, n: Union[ColumnOrName, int]) -> Column:
"""Get the n-th point (starts at 1) for a geometry.
diff --git a/python/tests/sql/test_dataframe_api.py
b/python/tests/sql/test_dataframe_api.py
index 939411b5c1..3ea7ab2076 100644
--- a/python/tests/sql/test_dataframe_api.py
+++ b/python/tests/sql/test_dataframe_api.py
@@ -867,6 +867,13 @@ test_configurations = [
(stf.ST_NumInteriorRings, ("geom",), "geom_with_hole", "", 1),
(stf.ST_NumInteriorRing, ("geom",), "geom_with_hole", "", 1),
(stf.ST_NumPoints, ("line",), "linestring_geom", "", 6),
+ (
+ stf.ST_OrientedEnvelope,
+ ("geom",),
+ "diagonal_geom",
+ "",
+ "POLYGON ((0 0, 4.5 4.5, 5 4, 0.5 -0.5, 0 0))",
+ ),
(stf.ST_PointN, ("line", 2), "linestring_geom", "", "POINT (1 0)"),
(stf.ST_PointOnSurface, ("line",), "linestring_geom", "", "POINT (2 0)"),
(
@@ -1378,6 +1385,7 @@ wrong_type_configurations = [
(stf.ST_MinimumClearanceLine, (None,)),
(stf.ST_MinimumBoundingCircle, (None,)),
(stf.ST_MinimumBoundingRadius, (None,)),
+ (stf.ST_OrientedEnvelope, (None,)),
(stf.ST_Multi, (None,)),
(stf.ST_Normalize, (None,)),
(stf.ST_NPoints, (None,)),
@@ -1629,6 +1637,10 @@ class TestDataFrameAPI(TestBase):
return TestDataFrameAPI.spark.sql(
"SELECT ST_GeomFromWKT('POLYGON ((1 0, 1 1, 2 1, 2 0, 1 0))')
AS geom"
)
+ elif request.param == "diagonal_geom":
+ return TestDataFrameAPI.spark.sql(
+ "SELECT ST_GeomFromWKT('POLYGON ((0 0, 1 0, 5 4, 4 4, 0 0))')
AS geom"
+ )
elif request.param == "four_points":
return TestDataFrameAPI.spark.sql(
"SELECT ST_GeomFromWKT('POINT (0 0)') AS p1,
ST_GeomFromWKT('POINT (1 1)') AS p2, ST_GeomFromWKT('POINT (1 0)') AS p3,
ST_GeomFromWKT('POINT (6 2)') AS p4"
diff --git a/python/tests/sql/test_function.py
b/python/tests/sql/test_function.py
index b7a2f88172..1c35ce417f 100644
--- a/python/tests/sql/test_function.py
+++ b/python/tests/sql/test_function.py
@@ -2529,3 +2529,14 @@ class TestPredicateJoin(TestBase):
actual_with_max = actual_df_with_max.take(1)[0][0]
assert actual_with_max is not None
assert actual_with_max.geom_type == "MultiLineString"
+
+ def test_st_oriented_envelope(self):
+ actual = self.spark.sql(
+ "SELECT ST_AsText(ST_OrientedEnvelope(ST_GeomFromText('POLYGON ((0
0, 1 0, 5 4, 4 4, 0 0))')))"
+ ).take(1)[0][0]
+ assert actual == "POLYGON ((0 0, 4.5 4.5, 5 4, 0.5 -0.5, 0 0))"
+
+ actual = self.spark.sql(
+ "SELECT ST_AsText(ST_OrientedEnvelope(ST_GeomFromText('POINT (1
2)')))"
+ ).take(1)[0][0]
+ assert actual == "POINT (1 2)"
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 af36809f0b..20ab767fad 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
@@ -391,6 +391,14 @@ public class TestFunctions extends TestBase {
"POLYGON ((1 2, 1 4, 3 4, 3 2, 1 2))");
}
+ @Test
+ public void test_ST_OrientedEnvelope() {
+ registerUDF("ST_OrientedEnvelope", byte[].class);
+ verifySqlSingleRes(
+ "select
sedona.ST_AsText(sedona.ST_OrientedEnvelope(sedona.ST_GeomFromText('POLYGON ((0
0, 1 0, 5 4, 4 4, 0 0))')))",
+ "POLYGON ((0 0, 4.5 4.5, 5 4, 0.5 -0.5, 0 0))");
+ }
+
@Test
public void test_ST_Expand() {
registerUDF("ST_Expand", 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 e2efb34843..425a5e288e 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
@@ -375,6 +375,14 @@ public class TestFunctionsV2 extends TestBase {
"POLYGON((1 2,1 4,3 4,3 2,1 2))");
}
+ @Test
+ public void test_ST_OrientedEnvelope() {
+ registerUDFV2("ST_OrientedEnvelope", String.class);
+ verifySqlSingleRes(
+ "select
ST_AsText(sedona.ST_OrientedEnvelope(ST_GeometryFromWKT('POLYGON ((0 0, 1 0, 5
4, 4 4, 0 0))')))",
+ "POLYGON((0 0,4.5 4.5,5 4,0.5 -0.5,0 0))");
+ }
+
@Test
public void test_ST_Expand() {
registerUDFV2("ST_Expand", 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 2832c6accd..3e2d0095a6 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
@@ -373,6 +373,11 @@ public class UDFs {
return
GeometrySerde.serialize(Functions.envelope(GeometrySerde.deserialize(geometry)));
}
+ @UDFAnnotations.ParamMeta(argNames = {"geometry"})
+ public static byte[] ST_OrientedEnvelope(byte[] geometry) {
+ return
GeometrySerde.serialize(Functions.orientedEnvelope(GeometrySerde.deserialize(geometry)));
+ }
+
@UDFAnnotations.ParamMeta(argNames = {"geometry", "uniformDelta"})
public static byte[] ST_Expand(byte[] geometry, double uniformDelta) {
return GeometrySerde.serialize(
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 0f636f1f92..5d545f4b6c 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
@@ -519,6 +519,15 @@ public class UDFsV2 {
return
GeometrySerde.serGeoJson(Functions.envelope(GeometrySerde.deserGeoJson(geometry)));
}
+ @UDFAnnotations.ParamMeta(
+ argNames = {"geometry"},
+ argTypes = {"Geometry"},
+ returnTypes = "Geometry")
+ public static String ST_OrientedEnvelope(String geometry) {
+ return GeometrySerde.serGeoJson(
+ Functions.orientedEnvelope(GeometrySerde.deserGeoJson(geometry)));
+ }
+
@UDFAnnotations.ParamMeta(
argNames = {"geometry", "uniformDelta"},
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 e584e666e3..8b5882a989 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
@@ -208,6 +208,7 @@ object Catalog extends AbstractCatalog with Logging {
function[ST_XMin](),
function[ST_BuildArea](),
function[ST_OrderingEquals](),
+ function[ST_OrientedEnvelope](),
function[ST_CollectionExtract](defaultArgs = null),
function[ST_Normalize](),
function[ST_LineFromMultiPoint](),
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 954fb350ad..54424510ff 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
@@ -701,6 +701,14 @@ private[apache] case class
ST_MinimumBoundingCircle(inputExpressions: Seq[Expres
}
}
+private[apache] case class ST_OrientedEnvelope(inputExpressions:
Seq[Expression])
+ extends InferredExpression(Functions.orientedEnvelope _) {
+
+ protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) =
{
+ copy(inputExpressions = newChildren)
+ }
+}
+
private[apache] case class ST_HasZ(inputExpressions: Seq[Expression])
extends InferredExpression(Functions.hasZ _) {
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 6fd00a6b63..f34a128307 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
@@ -504,6 +504,11 @@ object st_functions {
def ST_MinimumBoundingRadius(geometry: String): Column =
wrapExpression[ST_MinimumBoundingRadius](geometry)
+ def ST_OrientedEnvelope(geometry: Column): Column =
+ wrapExpression[ST_OrientedEnvelope](geometry)
+ def ST_OrientedEnvelope(geometry: String): Column =
+ wrapExpression[ST_OrientedEnvelope](geometry)
+
def ST_IsPolygonCCW(geometry: Column): Column =
wrapExpression[ST_IsPolygonCCW](geometry)
def ST_IsPolygonCCW(geometry: String): Column =
wrapExpression[ST_IsPolygonCCW](geometry)
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 81bd279c00..8b8c8ca20c 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
@@ -1491,6 +1491,15 @@ class dataFrameAPITestScala extends TestBaseScala {
assert(actualRadius == expectedRadius)
}
+ it("Passed ST_OrientedEnvelope") {
+ val baseDf =
+ sparkSession.sql("SELECT ST_GeomFromWKT('POLYGON ((0 0, 1 0, 5 4, 4 4,
0 0))') AS geom")
+ val df = baseDf.select(ST_OrientedEnvelope("geom"))
+ val actual = df.take(1)(0).get(0).asInstanceOf[Geometry].toText()
+ val expected = "POLYGON ((0 0, 4.5 4.5, 5 4, 0.5 -0.5, 0 0))"
+ assertEquals(expected, actual)
+ }
+
it("Passed ST_LineSegments") {
val baseDf = sparkSession.sql(
"SELECT ST_GeomFromWKT('LINESTRING(120 140, 60 120, 30 20)') AS line,
ST_GeomFromWKT('POLYGON ((0 0, 0 1, 1 0, 0 0))') AS poly")
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 d905cb424b..a095fc1bfa 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
@@ -2373,6 +2373,21 @@ class functionTestScala
.toList should contain theSameElementsAs List(0, 1, 1)
}
+ it("Should pass ST_OrientedEnvelope") {
+ val testCases = Seq(
+ ("POLYGON ((0 0, 4 0, 4 2, 0 2, 0 0))", "POLYGON ((0 0, 0 2, 4 2, 4 0, 0
0))"),
+ ("POLYGON ((0 0, 1 0, 5 4, 4 4, 0 0))", "POLYGON ((0 0, 4.5 4.5, 5 4,
0.5 -0.5, 0 0))"),
+ ("POINT (1 2)", "POINT (1 2)"))
+
+ testCases.foreach { case (input, expected) =>
+ val actual = sparkSession
+ .sql(s"SELECT
ST_AsText(ST_OrientedEnvelope(ST_GeomFromWKT('$input')))")
+ .first()
+ .getString(0)
+ assert(expected.equals(actual), s"Input: $input, Expected: $expected,
Actual: $actual")
+ }
+ }
+
it("Should pass ST_LineSegments") {
val baseDf = sparkSession.sql(
"SELECT ST_GeomFromWKT('LINESTRING(120 140, 60 120, 30 20)') AS line,
ST_GeomFromWKT('POLYGON ((0 0, 0 1, 1 0, 0 0))') AS poly")
@@ -2733,6 +2748,8 @@ class functionTestScala
assert(functionDf.first().get(0) == null)
functionDf = sparkSession.sql("select ST_MinimumBoundingRadius(null)")
assert(functionDf.first().get(0) == null)
+ functionDf = sparkSession.sql("select ST_OrientedEnvelope(null)")
+ assert(functionDf.first().get(0) == null)
functionDf = sparkSession.sql("select ST_LineSubstring(null, 0, 0)")
assert(functionDf.first().get(0) == null)
functionDf = sparkSession.sql("select ST_LineInterpolatePoint(null, 0)")