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 c332019a5 [SEDONA-631] Add ST_Expand (#1527)
c332019a5 is described below
commit c332019a5c643299e3fee50e509e7b2b56e63a90
Author: Furqaan Khan <[email protected]>
AuthorDate: Wed Jul 24 19:14:58 2024 -0400
[SEDONA-631] Add ST_Expand (#1527)
* feat: add ST_Expand
* fix: snowflake tests
* fix: simplify the implementation, remove BBox implementation
* chore: undo changes in BBox
* docs: add additional behavior details
* add test to check preservation of SRID
---
.../java/org/apache/sedona/common/Functions.java | 54 ++++++++++++++++++++
.../org/apache/sedona/common/FunctionsTest.java | 58 ++++++++++++++++++++++
docs/api/flink/Function.md | 39 +++++++++++++++
docs/api/snowflake/vector-data/Function.md | 37 ++++++++++++++
docs/api/sql/Function.md | 39 +++++++++++++++
.../main/java/org/apache/sedona/flink/Catalog.java | 1 +
.../apache/sedona/flink/expressions/Functions.java | 29 +++++++++++
.../java/org/apache/sedona/flink/FunctionTest.java | 39 +++++++++++++++
python/sedona/sql/st_functions.py | 25 ++++++++++
python/tests/sql/test_dataframe_api.py | 5 ++
python/tests/sql/test_function.py | 15 ++++++
.../sedona/snowflake/snowsql/TestFunctions.java | 18 +++++++
.../sedona/snowflake/snowsql/TestFunctionsV2.java | 18 +++++++
.../org/apache/sedona/snowflake/snowsql/UDFs.java | 18 +++++++
.../apache/sedona/snowflake/snowsql/UDFsV2.java | 27 ++++++++++
.../scala/org/apache/sedona/sql/UDF/Catalog.scala | 1 +
.../sql/sedona_sql/expressions/Functions.scala | 11 ++++
.../sql/sedona_sql/expressions/st_functions.scala | 19 +++++++
.../org/apache/sedona/sql/PreserveSRIDSuite.scala | 1 +
.../apache/sedona/sql/dataFrameAPITestScala.scala | 16 ++++++
.../org/apache/sedona/sql/functionTestScala.scala | 16 ++++++
21 files changed, 486 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 f0ffd0bc5..ce6dd4a20 100644
--- a/common/src/main/java/org/apache/sedona/common/Functions.java
+++ b/common/src/main/java/org/apache/sedona/common/Functions.java
@@ -25,6 +25,7 @@ import com.google.common.geometry.S2CellId;
import com.uber.h3core.exceptions.H3Exception;
import com.uber.h3core.util.LatLng;
import java.util.*;
+import java.util.List;
import java.util.stream.Collectors;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.sedona.common.geometryObjects.Circle;
@@ -37,6 +38,8 @@ import
org.locationtech.jts.algorithm.construct.LargestEmptyCircle;
import org.locationtech.jts.algorithm.construct.MaximumInscribedCircle;
import org.locationtech.jts.algorithm.hull.ConcaveHull;
import org.locationtech.jts.geom.*;
+import org.locationtech.jts.geom.Point;
+import org.locationtech.jts.geom.Polygon;
import org.locationtech.jts.geom.util.AffineTransformation;
import org.locationtech.jts.geom.util.GeometryFixer;
import org.locationtech.jts.io.ByteOrderValues;
@@ -90,6 +93,57 @@ public class Functions {
return boundary;
}
+ public static Geometry expand(Geometry geometry, double uniformDelta) {
+ return expand(geometry, uniformDelta, uniformDelta, uniformDelta);
+ }
+
+ public static Geometry expand(Geometry geometry, double deltaX, double
deltaY) {
+ return expand(geometry, deltaX, deltaY, 0);
+ }
+
+ public static Geometry expand(Geometry geometry, double deltaX, double
deltaY, double deltaZ) {
+ if (geometry == null || geometry.isEmpty()) {
+ return geometry;
+ }
+
+ Coordinate[] coordinates = geometry.getCoordinates();
+ double minX = Double.MAX_VALUE;
+ double maxX = -Double.MAX_VALUE;
+ double minY = Double.MAX_VALUE;
+ double maxY = -Double.MAX_VALUE;
+ double minZ = Double.MAX_VALUE;
+ double maxZ = -Double.MAX_VALUE;
+
+ for (int i = 0; i < coordinates.length; i++) {
+ minX = Math.min(minX, coordinates[i].x);
+ maxX = Math.max(maxX, coordinates[i].x);
+ minY = Math.min(minY, coordinates[i].y);
+ maxY = Math.max(maxY, coordinates[i].y);
+ minZ = Math.min(minZ, coordinates[i].z);
+ maxZ = Math.max(maxZ, coordinates[i].z);
+ }
+
+ minX = minX - deltaX;
+ maxX = maxX + deltaX;
+ minY = minY - deltaY;
+ maxY = maxY + deltaY;
+
+ if (Functions.hasZ(geometry)) {
+ minZ = minZ - deltaZ;
+ maxZ = maxZ + deltaZ;
+ Coordinate[] newCoords = new Coordinate[5];
+ newCoords[0] = new Coordinate(minX, minY, minZ);
+ newCoords[1] = new Coordinate(minX, maxY, minZ);
+ newCoords[2] = new Coordinate(maxX, maxY, maxZ);
+ newCoords[3] = new Coordinate(maxX, minY, maxZ);
+ newCoords[4] = newCoords[0];
+ return geometry.getFactory().createPolygon(newCoords);
+ }
+ Geometry result = Constructors.polygonFromEnvelope(minX, minY, maxX, maxY);
+ result.setSRID(geometry.getSRID());
+ return result;
+ }
+
public static Geometry buffer(Geometry geometry, double radius) {
return buffer(geometry, radius, false, "");
}
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 a4a3a8c9a..74ec6b433 100644
--- a/common/src/test/java/org/apache/sedona/common/FunctionsTest.java
+++ b/common/src/test/java/org/apache/sedona/common/FunctionsTest.java
@@ -2250,6 +2250,64 @@ public class FunctionsTest extends TestBase {
assertEquals(expected, actual);
}
+ @Test
+ public void testExpand() throws ParseException {
+ Geometry geometry =
+ GEOMETRY_FACTORY.createPolygon(coordArray(50, 50, 50, 80, 80, 80, 80,
50, 50, 50));
+ String actual = Functions.asWKT(Functions.expand(geometry, 10, 0));
+ String expected = "POLYGON ((40 50, 40 80, 90 80, 90 50, 40 50))";
+ assertEquals(expected, actual);
+
+ geometry = Constructors.geomFromWKT("POINT (10 20 1)", 4326);
+ Geometry result = Functions.expand(geometry, 10);
+ actual = Functions.asWKT(result);
+ expected = "POLYGON Z((0 10 -9, 0 30 -9, 20 30 11, 20 10 11, 0 10 -9))";
+ assertEquals(expected, actual);
+ assertEquals(4326, result.getSRID());
+
+ geometry = Constructors.geomFromWKT("LINESTRING (0 0, 1 1, 2 2)", 0);
+ actual = Functions.asWKT(Functions.expand(geometry, 10, 10));
+ expected = "POLYGON ((-10 -10, -10 12, 12 12, 12 -10, -10 -10))";
+ assertEquals(expected, actual);
+
+ geometry =
+ Constructors.geomFromWKT(
+ "MULTIPOLYGON (((52 68 1, 42 64 1, 66 62 2, 88 64 2, 85 68 2, 72
70 1, 52 68 1)), ((50 50 2, 50 80 2, 80 80 3, 80 50 2, 50 50 2)))",
+ 4326);
+ actual = Functions.asWKT(Functions.expand(geometry, 10.5, 2, 5));
+ expected = "POLYGON Z((31.5 48 -4, 31.5 82 -4, 98.5 82 8, 98.5 48 8, 31.5
48 -4))";
+ assertEquals(expected, actual);
+
+ geometry = Constructors.geomFromWKT("MULTIPOINT((10 20 1), (20 30 2))", 0);
+ actual = Functions.asWKT(Functions.expand(geometry, 9.5, 3.5));
+ expected = "POLYGON Z((0.5 16.5 1, 0.5 33.5 1, 29.5 33.5 2, 29.5 16.5 2,
0.5 16.5 1))";
+ assertEquals(expected, actual);
+
+ geometry =
+ Constructors.geomFromWKT(
+ "MULTILINESTRING ((1 0 4, 2 0 4, 4 0 4),(1 0 4, 2 0 4, 4 0 4))",
0);
+ actual = Functions.asWKT(Functions.expand(geometry, 0));
+ expected = "POLYGON Z((1 0 4, 1 0 4, 4 0 4, 4 0 4, 1 0 4))";
+ assertEquals(expected, actual);
+
+ geometry =
+ Constructors.geomFromWKT(
+ "GEOMETRYCOLLECTION (POINT (10 10),LINESTRING (20 20, 30
30),POLYGON ((25 25, 35 35, 35 35, 25 25)),MULTIPOINT (30 30, 40
40),MULTILINESTRING ((40 40, 50 50), (45 45, 55 55)),MULTIPOLYGON (((50 50, 60
60, 60 60, 50 50)), ((55 55, 65 65, 65 65, 55 55))))",
+ 1234);
+ result = Functions.expand(geometry, 10);
+ actual = Functions.asWKT(result);
+ expected = "POLYGON ((0 0, 0 75, 75 75, 75 0, 0 0))";
+ assertEquals(expected, actual);
+ assertEquals(1234, result.getSRID());
+
+ // The function drops the M dimension
+ geometry =
+ Constructors.geomFromWKT("POLYGON M((50 50 1, 50 80 2, 80 80 3, 80 50
2, 50 50 1))", 0);
+ actual = Functions.asWKT(Functions.expand(geometry, 0));
+ expected = "POLYGON ((50 50, 50 80, 80 80, 80 50, 50 50))";
+ assertEquals(expected, actual);
+ }
+
@Test
public void testBuffer() {
Polygon polygon =
diff --git a/docs/api/flink/Function.md b/docs/api/flink/Function.md
index 1b2163d19..c188d6b84 100644
--- a/docs/api/flink/Function.md
+++ b/docs/api/flink/Function.md
@@ -1211,6 +1211,45 @@ Output:
POLYGON ((0 0, 0 3, 1 3, 1 0, 0 0))
```
+## ST_Expand
+
+Introduction: Returns a geometry expanded from the bounding box of the input.
The expansion can be specified in two ways:
+
+1. By individual axis using `deltaX`, `deltaY`, or `deltaZ` parameters.
+2. Uniformly across all axes using the `uniformDelta` parameter.
+
+!!!Note
+ Things to consider when using this function:
+
+ 1. The `uniformDelta` parameter expands Z dimensions for XYZ geometries;
otherwise, it only affects XY dimensions.
+ 2. For XYZ geometries, specifying only `deltaX` and `deltaY` will preserve
the original Z dimension.
+ 3. If the input geometry has an M dimension then using this function will
drop the said M dimension.
+
+Format:
+
+`ST_Expand(geometry: Geometry, uniformDelta: Double)`
+
+`ST_Expand(geometry: Geometry, deltaX: Double, deltaY: Double)`
+
+`ST_Expand(geometry: Geometry, deltaX: Double, deltaY: Double, deltaZ: Double)`
+
+Since: `v1.6.1`
+
+SQL Example:
+
+```sql
+SELECT ST_Expand(
+ ST_GeomFromWKT('POLYGON Z((50 50 1, 50 80 2, 80 80 3, 80 50 2, 50 50
1))'),
+ 10
+ )
+```
+
+Output:
+
+```
+POLYGON Z((40 40 -9, 40 90 -9, 90 90 13, 90 40 13, 40 40 -9))
+```
+
## ST_ExteriorRing
Introduction: Returns a LINESTRING representing the exterior ring (shell) of a
POLYGON. Returns NULL if the geometry is not a polygon.
diff --git a/docs/api/snowflake/vector-data/Function.md
b/docs/api/snowflake/vector-data/Function.md
index 196afa55f..0657b9413 100644
--- a/docs/api/snowflake/vector-data/Function.md
+++ b/docs/api/snowflake/vector-data/Function.md
@@ -933,6 +933,43 @@ SELECT ST_Envelope(polygondf.countyshape)
FROM polygondf
```
+## ST_Expand
+
+Introduction: Returns a geometry expanded from the bounding box of the input.
The expansion can be specified in two ways:
+
+1. By individual axis using `deltaX`, `deltaY`, or `deltaZ` parameters.
+2. Uniformly across all axes using the `uniformDelta` parameter.
+
+Format:
+
+`ST_Expand(geometry: Geometry, uniformDelta: Double)`
+
+`ST_Expand(geometry: Geometry, deltaX: Double, deltaY: Double)`
+
+`ST_Expand(geometry: Geometry, deltaX: Double, deltaY: Double, deltaZ: Double)`
+
+!!!Note
+ Things to consider when using this function:
+
+ 1. The `uniformDelta` parameter expands Z dimensions for XYZ geometries;
otherwise, it only affects XY dimensions.
+ 2. For XYZ geometries, specifying only `deltaX` and `deltaY` will preserve
the original Z dimension.
+ 3. If the input geometry has an M dimension then using this function will
drop the said M dimension.
+
+SQL Example:
+
+```sql
+SELECT ST_Expand(
+ ST_GeomFromWKT('POLYGON Z((50 50 1, 50 80 2, 80 80 3, 80 50 2, 50 50
1))'),
+ 10
+ )
+```
+
+Output:
+
+```
+POLYGON Z((40 40 -9, 40 90 -9, 90 90 13, 90 40 13, 40 40 -9))
+```
+
## ST_ExteriorRing
Introduction: Returns a line string representing the exterior ring of the
POLYGON geometry. Return NULL if the geometry is not a polygon.
diff --git a/docs/api/sql/Function.md b/docs/api/sql/Function.md
index 9d70cee0c..6f4d1b18e 100644
--- a/docs/api/sql/Function.md
+++ b/docs/api/sql/Function.md
@@ -1216,6 +1216,45 @@ Output:
POLYGON ((0 0, 0 3, 1 3, 1 0, 0 0))
```
+## ST_Expand
+
+Introduction: Returns a geometry expanded from the bounding box of the input.
The expansion can be specified in two ways:
+
+1. By individual axis using `deltaX`, `deltaY`, or `deltaZ` parameters.
+2. Uniformly across all axes using the `uniformDelta` parameter.
+
+!!!Note
+ Things to consider when using this function:
+
+ 1. The `uniformDelta` parameter expands Z dimensions for XYZ geometries;
otherwise, it only affects XY dimensions.
+ 2. For XYZ geometries, specifying only `deltaX` and `deltaY` will preserve
the original Z dimension.
+ 3. If the input geometry has an M dimension then using this function will
drop the said M dimension.
+
+Format:
+
+`ST_Expand(geometry: Geometry, uniformDelta: Double)`
+
+`ST_Expand(geometry: Geometry, deltaX: Double, deltaY: Double)`
+
+`ST_Expand(geometry: Geometry, deltaX: Double, deltaY: Double, deltaZ: Double)`
+
+Since: `v1.6.1`
+
+SQL Example:
+
+```sql
+SELECT ST_Expand(
+ ST_GeomFromWKT('POLYGON Z((50 50 1, 50 80 2, 80 80 3, 80 50 2, 50 50
1))'),
+ 10
+ )
+```
+
+Output:
+
+```
+POLYGON Z((40 40 -9, 40 90 -9, 90 90 13, 90 40 13, 40 40 -9))
+```
+
## ST_ExteriorRing
Introduction: Returns a line string representing the exterior ring of the
POLYGON geometry. Return NULL if the geometry is not a polygon.
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 b19e99166..ca8cbc017 100644
--- a/flink/src/main/java/org/apache/sedona/flink/Catalog.java
+++ b/flink/src/main/java/org/apache/sedona/flink/Catalog.java
@@ -70,6 +70,7 @@ public class Catalog {
new Functions.ST_ConcaveHull(),
new Functions.ST_ConvexHull(),
new Functions.ST_CrossesDateLine(),
+ new Functions.ST_Expand(),
new Functions.ST_Envelope(),
new Functions.ST_Difference(),
new Functions.ST_Dimension(),
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 9b378e0e7..e38def518 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
@@ -244,6 +244,35 @@ public class Functions {
}
}
+ public static class ST_Expand 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 uniformDelta) {
+ Geometry geom = (Geometry) o;
+ return org.apache.sedona.common.Functions.expand(geom, uniformDelta);
+ }
+
+ @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 deltaX,
+ @DataTypeHint(value = "Double") Double deltaY) {
+ Geometry geom = (Geometry) o;
+ return org.apache.sedona.common.Functions.expand(geom, deltaX, deltaY);
+ }
+
+ @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 deltaX,
+ @DataTypeHint(value = "Double") Double deltaY,
+ @DataTypeHint(value = "Double") Double deltaZ) {
+ Geometry geom = (Geometry) o;
+ return org.apache.sedona.common.Functions.expand(geom, deltaX, deltaY,
deltaZ);
+ }
+ }
+
public static class ST_Dimension extends ScalarFunction {
@DataTypeHint("Integer")
public Integer 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 ea2cb0754..5f22124dd 100644
--- a/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java
+++ b/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java
@@ -379,6 +379,45 @@ public class FunctionTest extends TestBase {
first(linestringTable).getField(0).toString());
}
+ @Test
+ public void testExpand() {
+ Table baseTable =
+ tableEnv.sqlQuery(
+ "SELECT ST_GeomFromWKT('POLYGON ((50 50 1, 50 80 2, 80 80 3, 80 50
2, 50 50 1))') as geom");
+ String actual =
+ (String)
+ first(
+ baseTable
+ .select(call(Functions.ST_Expand.class, $("geom"), 10))
+ .as("geom")
+ .select(call(Functions.ST_AsText.class, $("geom"))))
+ .getField(0);
+ String expected = "POLYGON Z((40 40 -9, 40 90 -9, 90 90 13, 90 40 13, 40
40 -9))";
+ assertEquals(expected, actual);
+
+ actual =
+ (String)
+ first(
+ baseTable
+ .select(call(Functions.ST_Expand.class, $("geom"), 5,
6))
+ .as("geom")
+ .select(call(Functions.ST_AsText.class, $("geom"))))
+ .getField(0);
+ expected = "POLYGON Z((45 44 1, 45 86 1, 85 86 3, 85 44 3, 45 44 1))";
+ assertEquals(expected, actual);
+
+ actual =
+ (String)
+ first(
+ baseTable
+ .select(call(Functions.ST_Expand.class, $("geom"), 6,
5, -3))
+ .as("geom")
+ .select(call(Functions.ST_AsText.class, $("geom"))))
+ .getField(0);
+ expected = "POLYGON Z((44 45 4, 44 85 4, 86 85 0, 86 45 0, 44 45 4))";
+ assertEquals(expected, actual);
+ }
+
@Test
public void testFlipCoordinates() {
Table pointTable = createPointTable_real(testDataSize);
diff --git a/python/sedona/sql/st_functions.py
b/python/sedona/sql/st_functions.py
index 8100da893..02fae8f24 100644
--- a/python/sedona/sql/st_functions.py
+++ b/python/sedona/sql/st_functions.py
@@ -511,6 +511,31 @@ def ST_Envelope(geometry: ColumnOrName) -> Column:
return _call_st_function("ST_Envelope", geometry)
+@validate_argument_types
+def ST_Expand(geometry: ColumnOrName, deltaX_uniformDelta: Union[ColumnOrName,
float], deltaY: Optional[Union[ColumnOrName, float]] = None, deltaZ:
Optional[Union[ColumnOrName, float]] = None) -> Column:
+ """Expand the given geometry column by a constant unit in each direction
+
+ :param geometry: Geometry column to calculate the envelope of.
+ :type geometry: ColumnOrName
+ :param deltaX_uniformDelta: it is either deltaX or uniformDelta depending
on the number of arguments provided
+ :type deltaX_uniformDelta: Union[ColumnOrName, float]
+ :param deltaY: Constant unit of deltaY
+ :type deltaY: Union[ColumnOrName, float]
+ :param deltaZ: Constant unit of deltaZ
+ :type deltaZ: Union[ColumnOrName, float]
+ :return: Envelope of geometry as a geometry column.
+ :rtype: Column
+ """
+ if deltaZ is None:
+ args = (geometry, deltaX_uniformDelta, deltaY)
+ if deltaY is None:
+ args = (geometry, deltaX_uniformDelta)
+ else:
+ args = (geometry, deltaX_uniformDelta, deltaY, deltaZ)
+
+ return _call_st_function("ST_Expand", args)
+
+
@validate_argument_types
def ST_ExteriorRing(polygon: ColumnOrName) -> Column:
"""Get a linestring representing the exterior ring of a polygon geometry
diff --git a/python/tests/sql/test_dataframe_api.py
b/python/tests/sql/test_dataframe_api.py
index 16c000a34..0493ad527 100644
--- a/python/tests/sql/test_dataframe_api.py
+++ b/python/tests/sql/test_dataframe_api.py
@@ -124,6 +124,8 @@ test_configurations = [
(stf.ST_DumpPoints, ("line",), "linestring_geom", "", ["POINT (0 0)",
"POINT (1 0)", "POINT (2 0)", "POINT (3 0)", "POINT (4 0)", "POINT (5 0)"]),
(stf.ST_EndPoint, ("line",), "linestring_geom", "", "POINT (5 0)"),
(stf.ST_Envelope, ("geom",), "triangle_geom", "", "POLYGON ((0 0, 0 1, 1
1, 1 0, 0 0))"),
+ (stf.ST_Expand, ("geom", 2.0), "triangle_geom", "", "POLYGON ((-2 -2, -2
3, 3 3, 3 -2, -2 -2))"),
+ (stf.ST_Expand, ("geom", 2.0, 2.0), "triangle_geom", "", "POLYGON ((-2 -2,
-2 3, 3 3, 3 -2, -2 -2))"),
(stf.ST_ExteriorRing, ("geom",), "triangle_geom", "", "LINESTRING (0 0, 1
0, 1 1, 0 0)"),
(stf.ST_FlipCoordinates, ("point",), "point_geom", "", "POINT (1 0)"),
(stf.ST_Force_2D, ("point",), "point_geom", "", "POINT (0 1)"),
@@ -339,6 +341,9 @@ wrong_type_configurations = [
(stf.ST_DelaunayTriangles, (None,)),
(stf.ST_EndPoint, (None,)),
(stf.ST_Envelope, (None,)),
+ (stf.ST_Expand, (None,"")),
+ (stf.ST_Expand, (None,None)),
+ (stf.ST_Expand, ("",None)),
(stf.ST_ExteriorRing, (None,)),
(stf.ST_FlipCoordinates, (None,)),
(stf.ST_Force_2D, (None,)),
diff --git a/python/tests/sql/test_function.py
b/python/tests/sql/test_function.py
index 5f923b5cb..b6e7ceb66 100644
--- a/python/tests/sql/test_function.py
+++ b/python/tests/sql/test_function.py
@@ -171,6 +171,21 @@ class TestPredicateJoin(TestBase):
function_df = self.spark.sql("select
ST_Envelope(polygondf.countyshape) from polygondf")
function_df.show()
+ def test_st_expand(self):
+ baseDf = self.spark.sql(
+ "SELECT ST_GeomFromWKT('POLYGON ((50 50 1, 50 80 2, 80 80 3, 80 50
2, 50 50 1))') as geom")
+ actual = baseDf.selectExpr("ST_AsText(ST_Expand(geom, 10))").first()[0]
+ expected = "POLYGON Z((40 40 -9, 40 90 -9, 90 90 13, 90 40 13, 40 40
-9))"
+ assert expected == actual
+
+ actual = baseDf.selectExpr("ST_AsText(ST_Expand(geom, 5,
6))").first()[0]
+ expected = "POLYGON Z((45 44 1, 45 86 1, 85 86 3, 85 44 3, 45 44 1))"
+ assert expected == actual
+
+ actual = baseDf.selectExpr("ST_AsText(ST_Expand(geom, 6, 5,
-3))").first()[0]
+ expected = "POLYGON Z((44 45 4, 44 85 4, 86 85 0, 86 45 0, 44 45 4))"
+ assert expected == actual
+
def test_st_centroid(self):
polygon_wkt_df = self.spark.read.format("csv"). \
option("delimiter", "\t"). \
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 f59f63905..7aec36c1e 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
@@ -362,6 +362,24 @@ public class TestFunctions extends TestBase {
"POLYGON ((1 2, 1 4, 3 4, 3 2, 1 2))");
}
+ @Test
+ public void test_ST_Expand() {
+ registerUDF("ST_Expand", byte[].class, double.class);
+ verifySqlSingleRes(
+ "select
sedona.ST_AsText(sedona.ST_Expand(sedona.ST_GeomFromText('POLYGON ((50 50 1, 50
80 2, 80 80 3, 80 50 2, 50 50 1))'), 10))",
+ "POLYGON Z((40 40 -9, 40 90 -9, 90 90 13, 90 40 13, 40 40 -9))");
+
+ registerUDF("ST_Expand", byte[].class, double.class, double.class);
+ verifySqlSingleRes(
+ "select
sedona.ST_AsText(sedona.ST_Expand(sedona.ST_GeomFromText('POLYGON ((50 50 1, 50
80 2, 80 80 3, 80 50 2, 50 50 1))'), 5, 6))",
+ "POLYGON Z((45 44 1, 45 86 1, 85 86 3, 85 44 3, 45 44 1))");
+
+ registerUDF("ST_Expand", byte[].class, double.class, double.class,
double.class);
+ verifySqlSingleRes(
+ "select
sedona.ST_AsText(sedona.ST_Expand(sedona.ST_GeomFromText('POLYGON ((50 50 1, 50
80 2, 80 80 3, 80 50 2, 50 50 1))'), 6, 5, -3))",
+ "POLYGON Z((44 45 4, 44 85 4, 86 85 0, 86 45 0, 44 45 4))");
+ }
+
@Test
public void test_ST_ExteriorRing() {
registerUDF("ST_ExteriorRing", byte[].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 896cd9cb7..ebb63e66a 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
@@ -348,6 +348,24 @@ public class TestFunctionsV2 extends TestBase {
"POLYGON((1 2,1 4,3 4,3 2,1 2))");
}
+ @Test
+ public void test_ST_Expand() {
+ registerUDFV2("ST_Expand", String.class, double.class);
+ verifySqlSingleRes(
+ "select ST_AsText(sedona.ST_Expand(ST_GeometryFromWKT('POLYGON Z((50
50 1, 50 80 2, 80 80 3, 80 50 2, 50 50 1))'), 10))",
+ "POLYGONZ((40 40 -9,40 90 -9,90 90 13,90 40 13,40 40 -9))");
+
+ registerUDFV2("ST_Expand", String.class, double.class, double.class);
+ verifySqlSingleRes(
+ "select ST_AsText(sedona.ST_Expand(ST_GeometryFromWKT('POLYGON Z((50
50 1, 50 80 2, 80 80 3, 80 50 2, 50 50 1))'), 5, 6))",
+ "POLYGONZ((45 44 1,45 86 1,85 86 3,85 44 3,45 44 1))");
+
+ registerUDFV2("ST_Expand", String.class, double.class, double.class,
double.class);
+ verifySqlSingleRes(
+ "select ST_AsText(sedona.ST_Expand(ST_GeometryFromWKT('POLYGON Z((50
50 1, 50 80 2, 80 80 3, 80 50 2, 50 50 1))'), 6, 5, -3))",
+ "POLYGONZ((44 45 4,44 85 4,86 85 0,86 45 0,44 45 4))");
+ }
+
@Test
public void test_ST_ExteriorRing() {
registerUDFV2("ST_ExteriorRing", String.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 2a6429dd6..573933c8b 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
@@ -340,6 +340,24 @@ public class UDFs {
return
GeometrySerde.serialize(Functions.envelope(GeometrySerde.deserialize(geometry)));
}
+ @UDFAnnotations.ParamMeta(argNames = {"geometry", "uniformDelta"})
+ public static byte[] ST_Expand(byte[] geometry, double uniformDelta) {
+ return GeometrySerde.serialize(
+ Functions.expand(GeometrySerde.deserialize(geometry), uniformDelta));
+ }
+
+ @UDFAnnotations.ParamMeta(argNames = {"geometry", "deltaX", "deltaY"})
+ public static byte[] ST_Expand(byte[] geometry, double deltaX, double
deltaY) {
+ return GeometrySerde.serialize(
+ Functions.expand(GeometrySerde.deserialize(geometry), deltaX, deltaY));
+ }
+
+ @UDFAnnotations.ParamMeta(argNames = {"geometry", "deltaX", "deltaY",
"deltaZ"})
+ public static byte[] ST_Expand(byte[] geometry, double deltaX, double
deltaY, double deltaZ) {
+ return GeometrySerde.serialize(
+ Functions.expand(GeometrySerde.deserialize(geometry), deltaX, deltaY,
deltaZ));
+ }
+
@UDFAnnotations.ParamMeta(argNames = {"leftGeometry", "rightGeometry"})
public static boolean ST_Equals(byte[] leftGeometry, byte[] rightGeometry) {
return Predicates.equals(
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 1b7880785..063bd5515 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
@@ -468,6 +468,33 @@ public class UDFsV2 {
return
GeometrySerde.serGeoJson(Functions.envelope(GeometrySerde.deserGeoJson(geometry)));
}
+ @UDFAnnotations.ParamMeta(
+ argNames = {"geometry", "uniformDelta"},
+ argTypes = {"Geometry", "double"},
+ returnTypes = "Geometry")
+ public static String ST_Expand(String geometry, double uniformDelta) {
+ return GeometrySerde.serGeoJson(
+ Functions.expand(GeometrySerde.deserGeoJson(geometry), uniformDelta));
+ }
+
+ @UDFAnnotations.ParamMeta(
+ argNames = {"geometry", "deltaX", "deltaY"},
+ argTypes = {"Geometry", "double", "double"},
+ returnTypes = "Geometry")
+ public static String ST_Expand(String geometry, double deltaX, double
deltaY) {
+ return GeometrySerde.serGeoJson(
+ Functions.expand(GeometrySerde.deserGeoJson(geometry), deltaX,
deltaY));
+ }
+
+ @UDFAnnotations.ParamMeta(
+ argNames = {"geometry", "deltaX", "deltaY", "deltaZ"},
+ argTypes = {"Geometry", "double", "double", "double"},
+ returnTypes = "Geometry")
+ public static String ST_Expand(String geometry, double deltaX, double
deltaY, double deltaZ) {
+ return GeometrySerde.serGeoJson(
+ Functions.expand(GeometrySerde.deserGeoJson(geometry), deltaX, deltaY,
deltaZ));
+ }
+
@UDFAnnotations.ParamMeta(
argNames = {"leftGeometry", "rightGeometry"},
argTypes = {"Geometry", "Geometry"})
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 1742324d0..45b54d1b6 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
@@ -80,6 +80,7 @@ object Catalog {
function[ST_BestSRID](),
function[ST_ShiftLongitude](),
function[ST_Envelope](),
+ function[ST_Expand](),
function[ST_Length](),
function[ST_Length2D](),
function[ST_Area](),
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 cf52d5bc7..76db9bebb 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
@@ -207,6 +207,17 @@ case class ST_Envelope(inputExpressions: Seq[Expression])
}
}
+case class ST_Expand(inputExpressions: Seq[Expression])
+ extends InferredExpression(
+ inferrableFunction4(Functions.expand),
+ inferrableFunction3(Functions.expand),
+ inferrableFunction2(Functions.expand)) {
+
+ protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) =
{
+ copy(inputExpressions = newChildren)
+ }
+}
+
/**
* Return the length measurement of a Geometry
*
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 e28cd120a..81d00210c 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
@@ -169,6 +169,25 @@ object st_functions extends DataFrameAPI {
def ST_Envelope(geometry: Column): Column =
wrapExpression[ST_Envelope](geometry)
def ST_Envelope(geometry: String): Column =
wrapExpression[ST_Envelope](geometry)
+ def ST_Expand(geometry: Column, uniformDelta: Column) =
+ wrapExpression[ST_Expand](geometry, uniformDelta)
+ def ST_Expand(geometry: String, uniformDelta: String) =
+ wrapExpression[ST_Expand](geometry, uniformDelta)
+ def ST_Expand(geometry: String, uniformDelta: Double) =
+ wrapExpression[ST_Expand](geometry, uniformDelta)
+ def ST_Expand(geometry: Column, deltaX: Column, deltaY: Column) =
+ wrapExpression[ST_Expand](geometry, deltaX, deltaY)
+ def ST_Expand(geometry: String, deltaX: String, deltaY: String) =
+ wrapExpression[ST_Expand](geometry, deltaX, deltaY)
+ def ST_Expand(geometry: String, deltaX: Double, deltaY: Double) =
+ wrapExpression[ST_Expand](geometry, deltaX, deltaY)
+ def ST_Expand(geometry: Column, deltaX: Column, deltaY: Column, deltaZ:
Column) =
+ wrapExpression[ST_Expand](geometry, deltaX, deltaY, deltaZ)
+ def ST_Expand(geometry: String, deltaX: String, deltaY: String, deltaZ:
String) =
+ wrapExpression[ST_Expand](geometry, deltaX, deltaY, deltaZ)
+ def ST_Expand(geometry: String, deltaX: Double, deltaY: Double, deltaZ:
Double) =
+ wrapExpression[ST_Expand](geometry, deltaX, deltaY, deltaZ)
+
def ST_ExteriorRing(polygon: Column): Column =
wrapExpression[ST_ExteriorRing](polygon)
def ST_ExteriorRing(polygon: String): Column =
wrapExpression[ST_ExteriorRing](polygon)
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 414a9aeed..7a9bdb411 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
@@ -44,6 +44,7 @@ class PreserveSRIDSuite extends TestBaseScala with
TableDrivenPropertyChecks {
("ST_Buffer(geom1, 1)", 1000),
("ST_ShiftLongitude(geom1)", 1000),
("ST_Envelope(geom1)", 1000),
+ ("ST_Expand(geom1, 0)", 1000),
("ST_Centroid(geom1)", 1000),
("ST_Transform(geom1, 'EPSG:4326', 'EPSG:3857')", 3857),
("ST_Intersection(geom1, ST_Point(0, 1))", 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 79859e427..307e5a9fe 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
@@ -520,6 +520,22 @@ class dataFrameAPITestScala extends TestBaseScala {
assert(actualResult == expectedResult)
}
+ it("Passed ST_Expand") {
+ val baseDf = sparkSession.sql(
+ "SELECT ST_GeomFromWKT('POLYGON ((50 50 1, 50 80 2, 80 80 3, 80 50 2,
50 50 1))') as geom")
+ var actual = baseDf.select(ST_AsText(ST_Expand("geom",
10))).first().get(0)
+ var expected = "POLYGON Z((40 40 -9, 40 90 -9, 90 90 13, 90 40 13, 40 40
-9))"
+ assertEquals(expected, actual)
+
+ actual = baseDf.select(ST_AsText(ST_Expand("geom", 5, 6))).first().get(0)
+ expected = "POLYGON Z((45 44 1, 45 86 1, 85 86 3, 85 44 3, 45 44 1))"
+ assertEquals(expected, actual)
+
+ actual = baseDf.select(ST_AsText(ST_Expand("geom", 6, 5,
-3))).first().get(0)
+ expected = "POLYGON Z((44 45 4, 44 85 4, 86 85 0, 86 45 0, 44 45 4))"
+ assertEquals(expected, actual)
+ }
+
it("Passed ST_YMax") {
val polygonDf =
sparkSession.sql("SELECT ST_GeomFromWKT('POLYGON ((0 0, 1 0, 1 1, 0
0))') AS geom")
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 176ab296a..202115a2d 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
@@ -175,6 +175,22 @@ class functionTestScala
assert(functionDf.count() > 0);
}
+ it("Passed ST_Expand") {
+ val baseDf = sparkSession.sql(
+ "SELECT ST_GeomFromWKT('POLYGON ((50 50 1, 50 80 2, 80 80 3, 80 50 2,
50 50 1))') as geom")
+ var actual = baseDf.selectExpr("ST_AsText(ST_Expand(geom,
10))").first().get(0)
+ var expected = "POLYGON Z((40 40 -9, 40 90 -9, 90 90 13, 90 40 13, 40 40
-9))"
+ assertEquals(expected, actual)
+
+ actual = baseDf.selectExpr("ST_AsText(ST_Expand(geom, 5,
6))").first().get(0)
+ expected = "POLYGON Z((45 44 1, 45 86 1, 85 86 3, 85 44 3, 45 44 1))"
+ assertEquals(expected, actual)
+
+ actual = baseDf.selectExpr("ST_AsText(ST_Expand(geom, 6, 5,
-3))").first().get(0)
+ expected = "POLYGON Z((44 45 4, 44 85 4, 86 85 0, 86 45 0, 44 45 4))"
+ assertEquals(expected, actual)
+ }
+
it("Passed ST_YMax") {
var test = sparkSession.sql(
"SELECT ST_YMax(ST_GeomFromWKT('POLYGON ((-3 -3, 3 -3, 3 -2, -3 -1, -3
-3))'))")