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-631] Add ST_Expand #1527

Merged
merged 6 commits into from
Jul 24, 2024
Merged
Show file tree
Hide file tree
Changes from 5 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
54 changes: 54 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 @@ -25,6 +25,7 @@
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;
Expand All @@ -37,6 +38,8 @@
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;
Expand Down Expand Up @@ -90,6 +93,57 @@ public static Geometry boundary(Geometry geometry) {
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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When a Geometry does not have Z value, JTS will give NaN (https://locationtech.github.io/jts/javadoc/org/locationtech/jts/geom/Coordinate.html#getZ--). Does the Math comparison still work in this case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it just returns a NaN, and I am skipping the Z values when Geometry doesn't have it. I don't see it being a problem.

}

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, "");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2208,6 +2208,64 @@ public void nRingsMultiPolygonMixed() throws Exception {
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 =
Expand Down
39 changes: 39 additions & 0 deletions docs/api/flink/Function.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
37 changes: 37 additions & 0 deletions docs/api/snowflake/vector-data/Function.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
39 changes: 39 additions & 0 deletions docs/api/sql/Function.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please say this only deal with X, Y, Z value.

If uniformDelta is used and the geometry has Z, all X, Y, Z will be expanded. If only deltaX and deltaY are specified, only X and Y will be expanded even if the geometry has Z


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.
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 @@ -70,6 +70,7 @@ public static UserDefinedFunction[] getFuncs() {
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(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,35 @@ public Geometry eval(
}
}

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(
Expand Down
39 changes: 39 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 @@ -379,6 +379,45 @@ public void testEnvelope() {
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);
Expand Down
25 changes: 25 additions & 0 deletions python/sedona/sql/st_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading
Loading