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-498] Add Spheroidal ST_Buffer #1251

Merged
merged 60 commits into from
Mar 4, 2024
Merged
Show file tree
Hide file tree
Changes from 59 commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
2af48a3
Init: ST_Buffer Spheroidal variant
prantogg Feb 8, 2024
62908bb
Merge branch 'master' into spheroidal-ST_Buffer
prantogg Feb 8, 2024
cbfd51d
Add tests
prantogg Feb 9, 2024
d01819e
make BestSRID ST function; Add tests
prantogg Feb 20, 2024
27f1f6b
Implement ST_BestSRID
prantogg Feb 20, 2024
0f89438
fix typos
prantogg Feb 20, 2024
6881f06
fix test case; Add comments
prantogg Feb 20, 2024
412a8d4
Add docs; Fix test case
prantogg Feb 20, 2024
244a348
Docs: fix typo
prantogg Feb 20, 2024
b66dc2a
Docs: add Snowflake doc
prantogg Feb 21, 2024
12c1af6
add flink and scala tests
prantogg Feb 21, 2024
600ebe1
Docs: Fix indentation
prantogg Feb 21, 2024
c59b8b9
Fix Snowflake test case
prantogg Feb 21, 2024
1ece852
Fix Snowflake compile issue; Update doc
prantogg Feb 21, 2024
6e8b513
refactor snowflake test
prantogg Feb 21, 2024
c1d8b67
Docs: update
prantogg Feb 21, 2024
06fc006
Merge branch 'apache:master' into spheroidal-ST_Buffer
prantogg Feb 21, 2024
22b6aed
Merge branch 'add-ST_BestSRID' into spheroidal-ST_Buffer
prantogg Feb 21, 2024
fe685fd
Update docs; Add python tests
prantogg Feb 22, 2024
982be17
nit: update docs, change useSpheroidal to useSpheroid
prantogg Feb 22, 2024
79dc271
docs: update
prantogg Feb 22, 2024
3f57294
Merge branch 'master' into spheroidal-ST_Buffer
prantogg Feb 22, 2024
69a2352
Fix duplicates
prantogg Feb 22, 2024
d71f9c5
Fix lint; fix scala/java test
prantogg Feb 22, 2024
2c2d97e
Change function signature; fix tests
prantogg Feb 23, 2024
977ee84
move spheroid calculation to FunctionGeotools; Fix python test
prantogg Feb 24, 2024
ab72359
Enforce lon/lat input geometries for ST_BestSRID and ST_Buffer sphero…
prantogg Feb 25, 2024
8f994fc
Docs: Update
prantogg Feb 25, 2024
7542d75
Fix typo
prantogg Feb 25, 2024
ddb795e
Fix incorrect wrong type config
prantogg Feb 25, 2024
754b263
Fix python test
prantogg Feb 25, 2024
632a878
Add handling for geometries crossing the dateline
prantogg Feb 26, 2024
1c07550
add geometryChanged() to normalizeLongitude()
prantogg Feb 27, 2024
9dbe575
Use CoordinateSequenceFilter for ST_ShiftLongitude
prantogg Feb 28, 2024
fd3712f
change return type to Geometry for shiftLongitude
prantogg Feb 28, 2024
06ba679
Merge branch 'master' into spheroidal-ST_Buffer
prantogg Feb 28, 2024
0c882c0
Remove duplicate functions and tests
prantogg Feb 28, 2024
64ce098
Fix ST_Buffer definition in python
prantogg Feb 28, 2024
1a4e9f5
Add support in snowflake; add snowflake docs
prantogg Feb 28, 2024
afb528c
Fix class definition for snowflake
prantogg Feb 28, 2024
a1674ac
update python test case
prantogg Feb 29, 2024
8af5ec3
Fix python test
prantogg Feb 29, 2024
12c5760
Fix python test
prantogg Feb 29, 2024
0f52875
Merge branch 'master' into spheroidal-ST_Buffer
prantogg Feb 29, 2024
91a7845
Update
prantogg Feb 29, 2024
2227a88
Update
prantogg Feb 29, 2024
b73d2ad
Fix snowflake error
prantogg Feb 29, 2024
0d9071d
Update snowflake test
prantogg Feb 29, 2024
3282d5e
fix snowflake test
prantogg Feb 29, 2024
05815bd
fix snowflake test
prantogg Feb 29, 2024
48b9aaf
fix snowflake test
prantogg Feb 29, 2024
52d2ad8
fix snowflake test
prantogg Feb 29, 2024
5b60a38
fix snowflake test
prantogg Feb 29, 2024
94272c5
fix snowflake test
prantogg Feb 29, 2024
17768ce
fix snowflake test
prantogg Feb 29, 2024
cc3a1c4
fix snowflake test
prantogg Feb 29, 2024
2d05970
Update Docs
prantogg Feb 29, 2024
c05cc4e
Address comments
prantogg Mar 3, 2024
877db2d
Address comments
prantogg Mar 4, 2024
8bef013
Address comments - update docs
prantogg Mar 4, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
140 changes: 100 additions & 40 deletions common/src/main/java/org/apache/sedona/common/Functions.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
import java.util.stream.Collectors;

import static com.google.common.geometry.S2.DBL_EPSILON;
import static org.apache.sedona.common.FunctionsGeoTools.bufferSpheroid;


public class Functions {
Expand Down Expand Up @@ -87,25 +88,40 @@ public static Geometry boundary(Geometry geometry) {
}

public static Geometry buffer(Geometry geometry, double radius) {
return buffer(geometry, radius, "");
return buffer(geometry, radius, false, "");
}

public static Geometry buffer(Geometry geometry, double radius, String params) {
if (params.isEmpty()) {
return BufferOp.bufferOp(geometry, radius);
}
public static Geometry buffer(Geometry geometry, double radius, boolean useSpheroid) {
return buffer(geometry, radius, useSpheroid, "");
}

BufferParameters bufferParameters = parseBufferParams(params);
public static Geometry buffer(Geometry geometry, double radius, boolean useSpheroid, String params) throws IllegalArgumentException {
BufferParameters bufferParameters = new BufferParameters();

// convert the sign to the appropriate direction
// left - radius should be positive
// right - radius should be negative
if (bufferParameters.isSingleSided() &&
(params.toLowerCase().contains("left") && radius < 0 || params.toLowerCase().contains("right") && radius > 0)) {
// Processing parameters
if (!params.isEmpty()) {
bufferParameters = parseBufferParams(params);

// convert the sign to the appropriate direction
jiayuasu marked this conversation as resolved.
Show resolved Hide resolved
// left - radius should be positive
// right - radius should be negative
if (bufferParameters.isSingleSided() &&
(params.toLowerCase().contains("left") && radius < 0 || params.toLowerCase().contains("right") && radius > 0)) {
radius = -radius;
}
}

return BufferOp.bufferOp(geometry, radius, bufferParameters);
if (useSpheroid) {
// Spheroidal buffering logic
try {
return bufferSpheroid(geometry, radius, bufferParameters);
} catch (RuntimeException e) {
throw new RuntimeException("Error processing spheroidal buffer", e);
}
} else {
// Existing planar buffer logic with params handling
return BufferOp.bufferOp(geometry, radius, bufferParameters);
}
}

private static BufferParameters parseBufferParams(String params) {
Expand Down Expand Up @@ -186,17 +202,26 @@ else if (singleParam[0].equalsIgnoreCase(listBufferParameters[5])) {
return bufferParameters;
}

public static int bestSRID(Geometry geometry) {
public static int bestSRID(Geometry geometry) throws IllegalArgumentException {
// Shift longitudes if geometry crosses dateline
if (crossesDateLine(geometry)) {
shiftLongitude(geometry);
}

// Check envelope
Envelope envelope = geometry.getEnvelopeInternal();
if (envelope.isNull()) return Spheroid.EPSG_WORLD_MERCATOR; // Fallback EPSG

// Calculate the center of the envelope
double centerX = (envelope.getMinX() + envelope.getMaxX()) / 2.0; // centroid.getX();
double centerX = (envelope.getMinX() + envelope.getMaxX()) / 2.0; // centroid.getX();
double centerY = (envelope.getMinY() + envelope.getMaxY()) / 2.0; // centroid.getY();

// Calculate angular width and height
double xwidth = Spheroid.angularWidth(envelope);
double ywidth = Spheroid.angularHeight(envelope);
Double xwidth = Spheroid.angularWidth(envelope);
Double ywidth = Spheroid.angularHeight(envelope);
if (xwidth.isNaN() | ywidth.isNaN()) {
throw new IllegalArgumentException("Only lon/lat coordinate systems are supported by ST_BestSRID");
}

// Prioritize polar regions for Lambert Azimuthal Equal Area projection
if (centerY >= 70.0 && ywidth < 45.0) return Spheroid.EPSG_NORTH_LAMBERT;
Expand All @@ -208,8 +233,8 @@ public static int bestSRID(Geometry geometry) {
if (centerX == -180.0 || centerX == 180.0) {
zone = 59; // UTM zone 60
} else {
zone = (int)Math.floor((centerX + 180.0) / 6.0);
zone = Math.min(zone, 59);
zone = (int) Math.floor((centerX + 180.0) / 6.0);
zone = (zone > 59) ? zone - 60 : zone;
}
return (centerY < 0.0) ? Spheroid.EPSG_SOUTH_UTM_START + zone : Spheroid.EPSG_NORTH_UTM_START + zone;
}
Expand All @@ -218,6 +243,41 @@ public static int bestSRID(Geometry geometry) {
return Spheroid.EPSG_WORLD_MERCATOR;
}

/**
* Corrects the longitudes of a geometry to be within the -180 to 180 range.
* This method modifies the original geometry.
*
* @param geometry The geometry to be corrected.
*/
public static void normalizeLongitude(Geometry geometry) {
if (geometry == null || geometry.isEmpty()) {
return;
}

geometry.apply(new CoordinateSequenceFilter() {
@Override
public void filter(CoordinateSequence seq, int i) {
double x = seq.getX(i);
// Normalize the longitude to be within -180 to 180 range
while (x > 180) x -= 360;
while (x < -180) x += 360;
seq.setOrdinate(i, CoordinateSequence.X, x);
}

@Override
public boolean isDone() {
return false; // Continue processing until all coordinates are processed
}

@Override
public boolean isGeometryChanged() {
return true; // The geometry is changed as we are modifying the coordinates
}
});

geometry.geometryChanged(); // Notify the geometry that its coordinates have been changed
}

/**
* Checks if a geometry crosses the International Date Line.
*
Expand Down Expand Up @@ -263,28 +323,7 @@ public boolean isGeometryChanged() {
return filter.isDone();
}

public static Geometry envelope(Geometry geometry) {
return geometry.getEnvelope();
}

public static double distance(Geometry left, Geometry right) {
return left.distance(right);
}

public static double distance3d(Geometry left, Geometry right) {
return new Distance3DOp(left, right).distance();
}

public static double length(Geometry geometry) {
return geometry.getLength();
}

public static Geometry normalize(Geometry geometry) {
geometry.normalize();
return geometry;
}

public static Geometry shiftLongitude(Geometry geometry) {
public static Geometry shiftLongitude (Geometry geometry){
geometry.apply(new CoordinateSequenceFilter() {
@Override
public void filter(CoordinateSequence seq, int i) {
Expand All @@ -310,6 +349,27 @@ public boolean isGeometryChanged() {
return geometry;
}

public static Geometry envelope (Geometry geometry){
return geometry.getEnvelope();
}

public static double distance (Geometry left, Geometry right){
return left.distance(right);
}

public static double distance3d (Geometry left, Geometry right){
return new Distance3DOp(left, right).distance();
}

public static double length (Geometry geometry){
return geometry.getLength();
}

public static Geometry normalize (Geometry geometry){
geometry.normalize();
return geometry;
}

public static Double x(Geometry geometry) {
if (geometry instanceof Point) {
return geometry.getCoordinate().x;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
import org.locationtech.jts.geom.Envelope;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.operation.buffer.BufferOp;
import org.locationtech.jts.operation.buffer.BufferParameters;
import org.locationtech.jts.triangulate.VoronoiDiagramBuilder;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.NoSuchAuthorityCodeException;
Expand Down Expand Up @@ -134,4 +136,39 @@ public static Geometry voronoiPolygons(Geometry geom, double tolerance, Geometry
}
return builder.getDiagram(FunctionsGeoTools.GEOMETRY_FACTORY);
}

public static Geometry bufferSpheroid(Geometry geometry, double radius, BufferParameters params) throws IllegalArgumentException {
// Determine the best SRID for spheroidal calculations
int bestCRS = Functions.bestSRID(geometry);
jiayuasu marked this conversation as resolved.
Show resolved Hide resolved
int originalCRS = geometry.getSRID();
final int WGS84CRS = 4326;

// Shift longitude if geometry crosses dateline
if (Functions.crossesDateLine(geometry)) {
Functions.shiftLongitude(geometry);
}
// geometry = (Predicates.crossesDateLine(geometry)) ? Functions.shiftLongitude(geometry) : geometry;

// If originalCRS is not set, use WGS84 as the originalCRS for transformation
String sourceCRSCode = (originalCRS == 0) ? "EPSG:" + WGS84CRS : "EPSG:" + originalCRS;
String targetCRSCode = "EPSG:" + bestCRS;

try {
// Transform the geometry to the selected SRID
Geometry transformedGeometry = transform(geometry, sourceCRSCode, targetCRSCode);
// Apply the buffer operation in the selected SRID
Geometry bufferedGeometry = BufferOp.bufferOp(transformedGeometry, radius, params);

// Transform back to the original SRID or to WGS 84 if original SRID was not set
int backTransformCRSCode = (originalCRS == 0) ? WGS84CRS : originalCRS;
Geometry bufferedResult = transform(bufferedGeometry, targetCRSCode, "EPSG:" + backTransformCRSCode);
bufferedResult.setSRID(backTransformCRSCode);

// Normalize longitudes between -180 and 180
Functions.normalizeLongitude(bufferedResult);
return bufferedResult;
} catch (FactoryException | TransformException e) {
throw new RuntimeException(e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@
import org.locationtech.jts.geom.*;
import org.apache.sedona.common.sphere.Spheroid;

import java.util.concurrent.atomic.AtomicBoolean;

public class Predicates {
public static boolean contains(Geometry leftGeometry, Geometry rightGeometry) {
return leftGeometry.contains(rightGeometry);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import org.locationtech.jts.geom.Envelope;
import org.locationtech.jts.geom.Geometry;

import static java.lang.Float.NaN;
import static java.lang.Math.abs;

public class Spheroid
Expand Down Expand Up @@ -131,7 +132,7 @@ else if (geomType.equals("MultiPolygon") || geomType.equals("GeometryCollection"
}
}

public static double angularWidth(Envelope envelope) {
public static Double angularWidth(Envelope envelope) {
double lon1 = envelope.getMinX();
double lon2 = envelope.getMaxX();
double lat = (envelope.getMinY() + envelope.getMaxY()) / 2; // Mid-latitude for width calculation
Expand All @@ -141,12 +142,12 @@ public static double angularWidth(Envelope envelope) {
double distance = g.s12; // Distance in meters

// Convert distance to angular width in degrees
double angularWidth = Math.toDegrees(distance / (Geodesic.WGS84.EquatorialRadius() * Math.PI / 180));
Double angularWidth = Math.toDegrees(distance / (Geodesic.WGS84.EquatorialRadius() * Math.PI / 180));

return angularWidth;
}

public static double angularHeight(Envelope envelope) {
public static Double angularHeight(Envelope envelope) {
double lat1 = envelope.getMinY();
double lat2 = envelope.getMaxY();
double lon = (envelope.getMinX() + envelope.getMaxX()) / 2; // Mid-longitude for height calculation
Expand All @@ -156,7 +157,7 @@ public static double angularHeight(Envelope envelope) {
double distance = g.s12; // Distance in meters

// Convert distance to angular height in degrees
double angularHeight = Math.toDegrees(distance / (Geodesic.WGS84.EquatorialRadius() * Math.PI / 180));
Double angularHeight = Math.toDegrees(distance / (Geodesic.WGS84.EquatorialRadius() * Math.PI / 180));

return angularHeight;
}
Expand Down