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 3e1880b3be..3177cf2fa4 100644 --- a/common/src/main/java/org/apache/sedona/common/Functions.java +++ b/common/src/main/java/org/apache/sedona/common/Functions.java @@ -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 { @@ -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 + // 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) { @@ -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; @@ -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; } @@ -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. * @@ -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) { @@ -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; diff --git a/common/src/main/java/org/apache/sedona/common/FunctionsGeoTools.java b/common/src/main/java/org/apache/sedona/common/FunctionsGeoTools.java index 09d47aa95a..b47c5ce8f7 100644 --- a/common/src/main/java/org/apache/sedona/common/FunctionsGeoTools.java +++ b/common/src/main/java/org/apache/sedona/common/FunctionsGeoTools.java @@ -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; @@ -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); + 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); + } + } } diff --git a/common/src/main/java/org/apache/sedona/common/Predicates.java b/common/src/main/java/org/apache/sedona/common/Predicates.java index 03a1951876..1027fe4be6 100644 --- a/common/src/main/java/org/apache/sedona/common/Predicates.java +++ b/common/src/main/java/org/apache/sedona/common/Predicates.java @@ -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); diff --git a/common/src/main/java/org/apache/sedona/common/sphere/Spheroid.java b/common/src/main/java/org/apache/sedona/common/sphere/Spheroid.java index 7b06f00d73..42c1022fd4 100644 --- a/common/src/main/java/org/apache/sedona/common/sphere/Spheroid.java +++ b/common/src/main/java/org/apache/sedona/common/sphere/Spheroid.java @@ -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 @@ -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 @@ -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 @@ -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; } 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 9b18059f36..47a0b84240 100644 --- a/common/src/test/java/org/apache/sedona/common/FunctionsTest.java +++ b/common/src/test/java/org/apache/sedona/common/FunctionsTest.java @@ -41,6 +41,7 @@ public class FunctionsTest extends TestBase { public static final GeometryFactory GEOMETRY_FACTORY = new GeometryFactory(); protected static final double FP_TOLERANCE = 1e-12; + protected static final double FP_TOLERANCE2 = 1e-4; protected static final CoordinateSequenceComparator COORDINATE_SEQUENCE_COMPARATOR = new CoordinateSequenceComparator(2){ @Override protected int compareCoordinate(CoordinateSequence s1, CoordinateSequence s2, int i, int dimension) { @@ -62,28 +63,28 @@ public void asEWKT() throws Exception{ Geometry geometry = geometryFactory.createPoint(new Coordinate(1.0, 2.0)); String actualResult = Functions.asEWKT(geometry); String expectedResult = "SRID=4236;POINT (1 2)"; - Geometry actual = Constructors.geomFromEWKT(expectedResult); + Geometry actual = geomFromEWKT(expectedResult); assertEquals(geometry, actual); assertEquals(expectedResult, actualResult); geometry = geometryFactory.createPoint(new Coordinate(1.0, 2.0, 3.0)); actualResult = Functions.asEWKT(geometry); expectedResult = "SRID=4236;POINT Z(1 2 3)"; - actual = Constructors.geomFromEWKT(expectedResult); + actual = geomFromEWKT(expectedResult); assertEquals(geometry, actual); assertEquals(expectedResult, actualResult); geometry = geometryFactory.createPoint(new CoordinateXYM(1.0, 2.0, 3.0)); actualResult = Functions.asEWKT(geometry); expectedResult = "SRID=4236;POINT M(1 2 3)"; - actual = Constructors.geomFromEWKT(expectedResult); + actual = geomFromEWKT(expectedResult); assertEquals(geometry, actual); assertEquals(expectedResult, actualResult); geometry = geometryFactory.createPoint(new CoordinateXYZM(1.0, 2.0, 3.0, 4.0)); actualResult = Functions.asEWKT(geometry); expectedResult = "SRID=4236;POINT ZM(1 2 3 4)"; - actual = Constructors.geomFromEWKT(expectedResult); + actual = geomFromEWKT(expectedResult); assertEquals(geometry, actual); assertEquals(expectedResult, actualResult); } @@ -93,28 +94,28 @@ public void asWKT() throws Exception { Geometry geometry = GEOMETRY_FACTORY.createPoint(new Coordinate(1.0, 2.0)); String actualResult = Functions.asWKT(geometry); String expectedResult = "POINT (1 2)"; - Geometry actual = Constructors.geomFromEWKT(expectedResult); + Geometry actual = geomFromEWKT(expectedResult); assertEquals(geometry, actual); assertEquals(expectedResult, actualResult); geometry = GEOMETRY_FACTORY.createPoint(new Coordinate(1.0, 2.0, 3.0)); actualResult = Functions.asWKT(geometry); expectedResult = "POINT Z(1 2 3)"; - actual = Constructors.geomFromEWKT(expectedResult); + actual = geomFromEWKT(expectedResult); assertEquals(geometry, actual); assertEquals(expectedResult, actualResult); geometry = GEOMETRY_FACTORY.createPoint(new CoordinateXYM(1.0, 2.0, 3.0)); actualResult = Functions.asWKT(geometry); expectedResult = "POINT M(1 2 3)"; - actual = Constructors.geomFromEWKT(expectedResult); + actual = geomFromEWKT(expectedResult); assertEquals(geometry, actual); assertEquals(expectedResult, actualResult); geometry = GEOMETRY_FACTORY.createPoint(new CoordinateXYZM(1.0, 2.0, 3.0, 4.0)); actualResult = Functions.asWKT(geometry); expectedResult = "POINT ZM(1 2 3 4)"; - actual = Constructors.geomFromEWKT(expectedResult); + actual = geomFromEWKT(expectedResult); assertEquals(geometry, actual); assertEquals(expectedResult, actualResult); } @@ -1159,23 +1160,147 @@ public void testBuffer() { assertEquals(expected, actual); LineString lineString = GEOMETRY_FACTORY.createLineString(coordArray(0, 0, 50, 70, 100, 100)); - actual = Functions.asWKT(Functions.reducePrecision(Functions.buffer(lineString, 10, "side=left"), 4)); + actual = Functions.asWKT(Functions.reducePrecision(Functions.buffer(lineString, 10, false, "side=left"), 4)); expected = "POLYGON ((50 70, 0 0, -8.1373 5.8124, 41.8627 75.8124, 43.2167 77.3476, 44.855 78.5749, 94.855 108.5749, 100 100, 50 70))"; assertEquals(expected, actual); lineString = GEOMETRY_FACTORY.createLineString(coordArray(0, 0, 50, 70, 70, -3)); - actual = Functions.asWKT(Functions.reducePrecision(Functions.buffer(lineString, 10, "endcap=square"), 4)); + actual = Functions.asWKT(Functions.reducePrecision(Functions.buffer(lineString, 10, false, "endcap=square"), 4)); expected = "POLYGON ((43.2156 77.3465, 44.8523 78.5733, 46.7044 79.4413, 48.6944 79.9144, 50.739 79.9727, 52.7527 79.6137, 54.6512 78.8525, 56.3552 77.7209, 57.7932 76.2663, 58.9052 74.5495, 59.6446 72.6424, 79.6446 -0.3576, 82.2869 -10.0022, 62.9978 -15.2869, 45.9128 47.0733, 8.1373 -5.8124, 2.325 -13.9497, -13.9497 -2.325, 41.8627 75.8124, 43.2156 77.3465))"; assertEquals(expected, actual); Point point = GEOMETRY_FACTORY.createPoint(new Coordinate(100, 90)); - actual = Functions.asWKT(Functions.reducePrecision(Functions.buffer(point, 10, "quad_segs=2"), 4)); + actual = Functions.asWKT(Functions.reducePrecision(Functions.buffer(point, 10, false, "quad_segs=2"), 4)); expected = "POLYGON ((107.0711 82.9289, 100 80, 92.9289 82.9289, 90 90, 92.9289 97.0711, 100 100, 107.0711 97.0711, 110 90, 107.0711 82.9289))"; assertEquals(expected, actual); } @Test - public void testBestSRID() { + public void testBufferSpheroidal() throws ParseException { + Geometry polygon1 = GEOMETRY_FACTORY.createPolygon(coordArray(16.2500, 48.2500, 16.3500, 48.2500, 16.3500, 48.2000, 16.2500, 48.2000, 16.2500, 48.2500)); + Geometry polygon2 = geomFromWKT("POLYGON((-120 30, -80 30, -80 50, -120 50, -120 30))", 4269); + Geometry point1 = geomFromWKT("POINT(-180 60)", 4269); + Geometry linestring1 = geomFromEWKT("LINESTRING(-91.185 30.4505, -91.187 30.452, -91.189 30.4535)"); + Geometry polygon3 = geomFromWKT("POLYGON((-120 30, -80 30, -80 50, -120 50, -120 30))", 4269); + Geometry linestring2 = geomFromEWKT("LINESTRING(0.05 15, -0.05 15)"); + + // Geometries crossing dateline + Geometry linestring3 = geomFromEWKT("LINESTRING(179.95 15, -179.95 15)"); + Geometry polygon4 = geomFromEWKT("POLYGON((179 -15, 179 15, -179 15, -179 -15, 179 -15))"); + Geometry polygon5 = geomFromEWKT("POLYGON((178 -10, 178 10, -178 10, -178 -10, 178 -10))"); + + Geometry result1 = Functions.buffer(polygon1, 100, true); + Geometry result2 = Functions.buffer(polygon2, 1000, true); + Geometry result3 = Functions.buffer(point1, 100, true); + Geometry result4 = Functions.buffer(linestring1, 10, true); + Geometry result5 = Functions.buffer(polygon3, 10000, true); + Geometry result6 = Functions.buffer(linestring2, 10000, true); + + // Geometries crossing dateline + Geometry result7 = Functions.buffer(linestring3, 1000000, true); + Geometry result8 = Functions.buffer(polygon4, 100, true); + Geometry result9 = Functions.buffer(polygon5, 1000000, true); + + Geometry expected1 = geomFromEWKT("POLYGON ((16.248653057837092 48.24999999781262, 16.24867891451385 48.25017542568975, 16.24875549793627 48.25034411793438, 16.24887986793391 48.25049959710696, 16.249047249310912 48.25063589306856, 16.249251215157546 48.250747772240985, 16.249483933612066 48.25083093858835, 16.249736468600165 48.250882198599385, 16.249999123001867 48.25089958393145, 16.35000087730807 48.25089956809054, 16.35026352923699 48.250882182692266, 16.350516061632444 48.250830922668115, 16.35074877731957 48.25074775640202, 16.350952740192536 48.250635877460866, 16.351120118386593 48.25049958194538, 16.351244485020548 48.25034410350243, 16.35132106495956 48.25017541233756, 16.351346918125614 48.24999998594946, 16.351345606585333 48.199999998284284, 16.351319726848168 48.19982442741669, 16.351243088946557 48.19965560959, 16.351118640921797 48.19950003769327, 16.350951169518318 48.19936369511042, 16.350747116034892 48.19925182561274, 16.350514328568078 48.19916873170147, 16.350261760179283 48.199117609152964, 16.34999912459177 48.19910042412578, 16.25000087573864 48.19910040825405, 16.24973823767017 48.199117593214716, 16.249485666681007 48.199168715750886, 16.249252876439694 48.19925180974529, 16.24904881997628 48.199363679477166, 16.248881345384483 48.199500022510115, 16.248756893991796 48.199655595141486, 16.24868025260387 48.199824414053964, 16.2486543693546 48.19999998641755, 16.248653057837092 48.24999999781262))"); + Geometry expected2 = geomFromEWKT("POLYGON ((-120.00898315284118 29.999999999999467, -120.00898315284118 49.999999999988724, -120.00881054407824 50.001129625613444, -120.0082993510474 50.002215815187945, -120.00746921861013 50.003216831381316, -120.00635204829044 50.00409421217581, -120.0049907723172 50.004814247955025, -120.00343770376273 50.00534927578318, -120.00175252618048 50.00567874131385, -119.99999999999999 50.005789987696524, -80 50.005789987696524, -79.9982474738195 50.00567874131385, -79.99656229623727 50.00534927578318, -79.99500922768277 50.004814247955025, -79.99364795170956 50.00409421217581, -79.99253078138989 50.003216831381316, -79.99170064895262 50.002215815187945, -79.99118945592176 50.001129625613444, -79.99101684715882 49.999999999988724, -79.99101684715882 29.999999999999467, -79.99118945592176 29.998474584401656, -79.99170064895262 29.997007767335376, -79.99253078138989 29.995655921596196, -79.99364795170956 29.994471003601635, -79.99500922768277 29.99349855586065, -79.99656229623727 29.99277595576926, -79.9982474738195 29.99233097818683, -80 29.992180727189663, -119.99999999999999 29.992180727189663, -120.00175252618048 29.99233097818683, -120.00343770376273 29.99277595576926, -120.0049907723172 29.99349855586065, -120.00635204829044 29.994471003601635, -120.00746921861013 29.995655921596196, -120.0082993510474 29.997007767335376, -120.00881054407824 29.998474584401656, -120.00898315284118 29.999999999999467))"); + Geometry expected3 = geomFromEWKT("POLYGON ((-179.9982096288439 59.99995928994206, -179.9982598922349 59.99978513613418, -179.99837702566958 59.99961923978765, -179.99855652694714 59.99946797603053, -179.998791497433 59.99933715760413, -179.99907290724568 59.999231811518854, -179.9993899422849 59.99915598591007, -179.99973041976276 59.99911259450936, 179.9999187437241 59.999103304702594, 179.99957102955173 59.99912847347155, 179.99923979915093 59.999187133678575, 179.99893778069153 59.999277031220146, 179.99867658007233 59.99939471162435, 179.99846623498902 59.99953565276752, 179.9983148292063 59.99969443861621, 179.99822818185396 59.999864967323326, 179.9982096236965 60.00004068568767, 179.99825986898801 60.00021484097138, 179.99837698785996 60.00038074040053, 179.99855648032874 60.00053200837772, 179.99879144910062 60.00066283151926, 179.99907286455522 60.00076818209686, 179.9993899117333 60.00084401129125, 179.9997304059989 60.000887404824965, -179.99991873860708 60.00089669498742, -179.99957100633523 60.000871524742664, -179.9992397613717 60.000812861453035, -179.9989377341035 60.000722959691345, -179.99867653177037 60.00060527457202, -179.99846619232903 60.00046432893652, -179.99831479868513 60.000305539502236, -179.99822816812048 60.00013500866213, -179.9982096288439 59.99995928994206))"); + Geometry expected4 = geomFromEWKT("POLYGON ((-91.18706814560424 30.451931798138848, -91.18906814731041 30.453431798551634, -91.18908219577726 30.453444627150773, -91.1890930855116 30.4534595836974, -91.18910039802604 30.453476093420573, -91.18910385230312 30.453493521861454, -91.18910331559482 30.453511199255043, -91.1890988085245 30.4535284462689, -91.18909050429437 30.453544600109474, -91.18907872203003 30.453559039992996, -91.18906391451668 30.453571211001968, -91.18904665079899 30.453580645410415, -91.18902759431288 30.453586980658557, -91.18900747739012 30.453589973285787, -91.18898707311482 30.453589508286903, -91.18896716561396 30.453585603531685, -91.18894851992349 30.453578409078172, -91.18893185258821 30.453568201405897, -91.18693185327697 30.45206820105564, -91.18493185405761 30.450568200774196, -91.18491780618592 30.450555372061554, -91.18490691698157 30.450540415431583, -91.18489960490953 30.450523905659963, -91.18489615096698 30.450506477208492, -91.18489668788492 30.450488799843015, -91.18490119502782 30.450471552894637, -91.18490949918679 30.450455399153434, -91.18492128123634 30.450440959397913, -91.18493608839827 30.450428788538883, -91.18495335164187 30.45041935429472, -91.1849724075514 30.45041301921735, -91.18499252382057 30.450410026759716, -91.18501292739447 30.450410491920046, -91.18503283417732 30.45041439682263, -91.18505147916453 30.450421591404723, -91.18506814584102 30.450431799183303, -91.18706814560424 30.451931798138848))"); + Geometry expected5 = geomFromEWKT("POLYGON ((-120.08983152841193 29.999999999999467, -120.08983152841193 49.999999999988724, -120.08810544078257 50.011295055219406, -120.082993510474 50.02215353087995, -120.07469218610126 50.03215857448533, -120.06352048290445 50.040926345154375, -120.04990772317231 50.048120665845154, -120.03437703762728 50.05346582623596, -120.01752526180509 50.05675706193107, -119.99999999999999 50.057868324959486, -80 50.057868324959486, -79.98247473819491 50.05675706193107, -79.96562296237272 50.05346582623596, -79.95009227682765 50.048120665845154, -79.93647951709555 50.040926345154375, -79.92530781389875 50.03215857448533, -79.91700648952599 50.02215353087995, -79.91189455921744 50.011295055219406, -79.91016847158805 49.999999999988724, -79.91016847158805 29.999999999999467, -79.91189455921744 29.984744778413628, -79.91700648952599 29.970073573592575, -79.92530781389875 29.956550575939833, -79.93647951709555 29.944696041120597, -79.95009227682765 29.934966209460505, -79.96562296237272 29.927735669848445, -79.98247473819491 29.92328286155486, -80 29.92177928675603, -119.99999999999999 29.92177928675603, -120.01752526180509 29.92328286155486, -120.03437703762728 29.927735669848445, -120.04990772317231 29.934966209460505, -120.06352048290445 29.944696041120597, -120.07469218610126 29.956550575939833, -120.082993510474 29.970073573592575, -120.08810544078257 29.984744778413628, -120.08983152841193 29.999999999999467))"); + Geometry expected6 = geomFromEWKT("POLYGON ((-0.0499827380800979 14.90970764615526, -0.0680979441919658 14.911439289332314, -0.0855181036092048 14.916572765433372, -0.1015745790932317 14.924910889089798, -0.1156509498611482 14.936133480337729, -0.1272066059871122 14.949809631917493, -0.1357974717923214 14.965414213210906, -0.1410930699533803 14.982347984759812, -0.1428892711961725 14.9999605602647, -0.1411162325232574 15.017575343788987, -0.1358412050267848 15.034515492689351, -0.1272660846886664 15.050129914661689, -0.1157197795798512 15.063818302233363, -0.101645667253286 15.07505424080832, -0.0855846090787039 15.083405496399754, -0.068154165764569 15.088550694439519, -0.0500248124892859 15.090291738028835, 0.0500174017959422 15.0902994910598, 0.0681488768495457 15.08856105434859, 0.0855824246963393 15.08341775106032, 0.1016472676386697 15.075067339333813, 0.1157254254859488 15.06383098366898, 0.1272755583302725 15.050140871793444, 0.1358538186573691 15.034523547105394, 0.1411309042551145 15.017579606456339, 0.1429046575925539 14.999960553897546, 0.1411077364057122 14.982343709853023, 0.1358100760363371 14.965406147948107, 0.1272160681615492 14.949798665976873, 0.1156565845591405 14.936120792425546, 0.1015761705812689 14.9248977863495, 0.0855159139181673 14.916560508451733, 0.0680926538252635 14.911428928455091, 0.0499753291173759 14.909699892939221, -0.0499827380800979 14.90970764615526))"); + Geometry expected7 = geomFromEWKT("POLYGON ((-179.908607846266 24.025403116254306, -177.99876698499668 23.825660822056278, -176.1851748144233 23.267372220454533, -174.54862792786022 22.380090414241554, -173.15522797402008 21.20765453023379, -172.05367901642606 19.803908497939666, -171.27542809803106 18.22850022788734, -170.83670228698406 16.543486102335997, -170.7412636921777 14.811036901560918, -170.9829095910743 13.092142713348924, -171.5471495735382 11.445952123266512, -172.41190582467604 9.929276420592164, -173.54741832839647 8.595821727211286, -174.91577148479777 7.494852554680671, -176.47058207976102 6.669211334615649, -178.157376393358 6.152879352694568, -179.9150124655878 5.968498588196282, 179.9878642700213 5.967715967762297, 178.22454996382154 6.1243064565567416, 176.52190735292885 6.617190677478861, 174.94354089208682 7.427303247307942, 173.5475157235323 8.522934832546376, 172.38432738193248 9.861905503007021, 171.4959038990676 11.393986435009106, 170.9153886431351 13.063103234007537, 170.66721922346738 14.80905645479948, 170.76695775874103 16.568760623485606, 171.22043647400534 18.277247812024235, 172.02203612722943 19.86884329704546, 173.1522881340142 21.278955415388584, 174.5754355707626 22.446796923405344, 176.2379815998183 23.319060447498003, 178.06938085914476 23.854152382752126, 179.98566424517946 24.026184454399257, -179.908607846266 24.025403116254306))"); + Geometry expected8 = geomFromEWKT("POLYGON ((179 -15.000873160293315, 178.999824747382 -15.000856382797105, 178.99965622962375 -15.000806695050459, 178.9995009227683 -15.000726006503347, 178.999364795171 -15.000617417938033, 178.99925307813902 -15.000485102312915, 178.9991700648953 -15.000334144403906, 178.99911894559222 -15.00017034540551, 178.9991016847159 -14.999999999999092, 178.9991016847159 14.99999999999908, 178.99911894559222 15.00017034540551, 178.9991700648953 15.000334144403906, 178.99925307813902 15.000485102312902, 178.999364795171 15.000617417938045, 178.9995009227683 15.000726006503333, 178.99965622962375 15.000806695050459, 178.999824747382 15.000856382797105, 179 15.000873160293315, -179.00000000000003 15.000873160293315, -178.99982474738198 15.000856382797105, -178.99965622962375 15.000806695050459, -178.9995009227683 15.000726006503333, -178.99936479517098 15.000617417938045, -178.99925307813902 15.000485102312902, -178.99917006489528 15.000334144403906, -178.9991189455922 15.00017034540551, -178.99910168471592 14.99999999999908, -178.99910168471592 -14.999999999999092, -178.9991189455922 -15.00017034540551, -178.99917006489528 -15.000334144403906, -178.99925307813902 -15.000485102312915, -178.99936479517098 -15.000617417938033, -178.9995009227683 -15.000726006503347, -178.99965622962375 -15.000806695050459, -178.99982474738198 -15.000856382797105, -179.00000000000003 -15.000873160293315, 179 -15.000873160293315))"); + Geometry expected9 = geomFromEWKT("POLYGON ((178 -18.747248844374, 176.24747381949112 -18.582729101204464, 174.5622962372712 -18.0945531116537, 173.0092276827665 -17.29887379200776, 171.64795170955566 -16.222572896097414, 170.53078138987692 -14.903037762363958, 169.70064895259912 -13.387566659631126, 169.18945592174327 -11.73221950803158, 169.01684715880478 -9.999999999999265, 169.01684715880478 9.999999999999238, 169.18945592174327 11.73221950803157, 169.70064895259912 13.387566659631101, 170.53078138987692 14.903037762363944, 171.64795170955566 16.2225728960974, 173.0092276827665 17.298873792007747, 174.5622962372712 18.094553111653685, 176.24747381949112 18.582729101204453, 178 18.747248844373974, -178 18.747248844373974, -176.24747381949115 18.582729101204453, -174.56229623727123 18.094553111653685, -173.00922768276646 17.298873792007747, -171.64795170955566 16.2225728960974, -170.5307813898769 14.903037762363944, -169.70064895259912 13.387566659631101, -169.18945592174325 11.73221950803157, -169.0168471588048 9.999999999999238, -169.0168471588048 -9.999999999999265, -169.18945592174325 -11.73221950803158, -169.70064895259912 -13.387566659631126, -170.5307813898769 -14.903037762363972, -171.64795170955566 -16.222572896097414, -173.00922768276646 -17.29887379200776, -174.56229623727123 -18.0945531116537, -176.24747381949115 -18.582729101204464, -178 -18.747248844374, 178 -18.747248844374))"); + + Geometry postgis_result1 = geomFromEWKT("POLYGON((16.24865305783681 48.249999997812985,16.248678914513565 48.250175425690124,16.248755497935985 48.250344117934766,16.248879867933624 48.25049959710733,16.249047249310628 48.25063589306893,16.249251215157262 48.25074777224136,16.24948393361178 48.25083093858872,16.24973646859988 48.25088219859975,16.249999123001583 48.25089958393185,16.35000087730765 48.25089956809112,16.35026352923657 48.25088218269285,16.350516061632025 48.2508309226687,16.35074877731915 48.2507477564026,16.35095274019212 48.25063587746145,16.351120118386177 48.250499581945974,16.35124448502013 48.250344103503025,16.351321064959144 48.25017541233815,16.3513469181252 48.24999998595004,16.351345606584914 48.19999999828486,16.35131972684775 48.19982442741726,16.351243088946138 48.19965560959058,16.351118640921378 48.19950003769385,16.3509511695179 48.199363695110996,16.350747116034473 48.19925182561332,16.350514328567655 48.19916873170203,16.35026176017886 48.19911760915353,16.34999912459135 48.19910042412636,16.250000875738355 48.19910040825441,16.249738237669884 48.19911759321507,16.249485666680723 48.19916871575125,16.24925287643941 48.199251809745654,16.249048819975993 48.19936367947753,16.2488813453842 48.199500022510485,16.248756893991516 48.19965559514185,16.248680252603585 48.199824414054326,16.248654369354316 48.199999986417914,16.24865305783681 48.249999997812985))"); + Geometry postgis_result2 = geomFromEWKT("POLYGON((-120.00898315284118 29.999999999999993,-120.00898315284118 49.99999999999999,-120.00881054407824 50.00112962562472,-120.0082993510474 50.00221581519921,-120.00746921861011 50.003216831392564,-120.00635204829044 50.004094212187034,-120.00499077231723 50.00481424796622,-120.00343770376273 50.00534927579437,-120.00175252618051 50.00567874132504,-119.99999999999999 50.00578998770771,-80 50.00578998770771,-79.99824747381949 50.00567874132504,-79.99656229623727 50.00534927579437,-79.99500922768276 50.00481424796622,-79.99364795170956 50.004094212187034,-79.99253078138987 50.003216831392564,-79.9917006489526 50.00221581519921,-79.99118945592174 50.00112962562472,-79.9910168471588 49.99999999999999,-79.9910168471588 29.999999999999993,-79.99118945592174 29.99847458440221,-79.9917006489526 29.997007767335962,-79.99253078138987 29.99565592159683,-79.99364795170956 29.99447100360229,-79.99500922768276 29.993498555861333,-79.99656229623727 29.992775955769954,-79.99824747381949 29.992330978187542,-80 29.992180727190387,-119.99999999999999 29.992180727190387,-120.00175252618051 29.992330978187542,-120.00343770376273 29.992775955769954,-120.00499077231723 29.993498555861333,-120.00635204829044 29.99447100360229,-120.00746921861011 29.99565592159683,-120.0082993510474 29.997007767335962,-120.00881054407824 29.99847458440221,-120.00898315284118 29.999999999999993))"); + Geometry postgis_result3 = geomFromEWKT("POLYGON((-179.99820962883618 59.99995929000264,-179.99825989222717 59.999785136194745,-179.9983770256619 59.99961923984817,-179.99855652693947 59.99946797609105,-179.99879149742534 59.99933715766465,-179.999072907238 59.99923181157932,-179.99938994227725 59.9991559859705,-179.99973041975517 59.999112594569745,179.9999187437317 59.999103304762926,179.99957102955932 59.999128473531854,179.9992397991585 59.99918713373882,179.99893778069907 59.999277031280386,179.99867658007986 59.99939471168456,179.99846623499656 59.99953565282769,179.99831482921377 59.99969443867636,179.99822818186144 59.999864967383445,179.998209623704 60.00004068574784,179.9982598689955 60.00021484103153,179.9983769878675 60.000380740460706,179.99855648033625 60.00053200843791,179.99879144910815 60.000662831579476,179.99907286456278 60.00076818215714,179.99938991174088 60.00084401135155,179.99973040600648 60.00088740488531,-179.9999187385995 60.0008966950478,-179.99957100632759 60.00087152480308,-179.9992397613641 60.0008128615135,-179.99893773409582 60.000722959751855,-179.99867653176275 60.00060527463255,-179.99846619232136 60.00046432899707,-179.99831479867737 60.00030553956281,-179.99822816811277 60.000135008722694,-179.99820962883618 59.99995929000264))"); + Geometry postgis_result4 = geomFromEWKT("POLYGON((-91.18706814560713 30.45193179814003,-91.1890681473133 30.453431798552806,-91.18908219578012 30.453444627151963,-91.18909308551447 30.453459583698564,-91.18910039802891 30.453476093421752,-91.18910385230598 30.453493521862622,-91.1891033155977 30.453511199256216,-91.18909880852735 30.453528446270074,-91.18909050429724 30.453544600110646,-91.18907872203292 30.453559039994175,-91.18906391451956 30.453571211003144,-91.18904665080186 30.453580645411602,-91.18902759431575 30.453586980659722,-91.189007477393 30.45358997328696,-91.1889870731177 30.453589508288072,-91.18896716561683 30.45358560353286,-91.18894851992636 30.453578409079352,-91.18893185259108 30.453568201407066,-91.18693185325601 30.45206820103894,-91.1849318540605 30.450568200775386,-91.18491780618884 30.450555372062745,-91.18490691698447 30.450540415432766,-91.18489960491243 30.45052390566114,-91.18489615096986 30.450506477209665,-91.18489668788781 30.450488799844198,-91.1849011950307 30.45047155289582,-91.18490949918969 30.45045539915463,-91.18492128123923 30.450440959399103,-91.18493608840117 30.45042878854006,-91.18495335164478 30.45041935429591,-91.18497240755428 30.450413019218534,-91.18499252382345 30.450410026760903,-91.18501292739734 30.450410491921236,-91.1850328341802 30.450414396823803,-91.18505147916741 30.450421591405906,-91.1850681458439 30.450431799184482,-91.18706814560713 30.45193179814003))"); + Geometry postgis_result5 = geomFromEWKT("POLYGON((-120.08983152841193 29.999999999999993,-120.08983152841193 49.99999999999999,-120.08810544078257 50.011295055230505,-120.082993510474 50.02215353089088,-120.07469218610122 50.032158574496115,-120.06352048290445 50.040926345165026,-120.04990772317234 50.04812066585569,-120.03437703762728 50.0534658262464,-120.01752526180509 50.05675706194147,-119.99999999999999 50.05786832496988,-80 50.05786832496988,-79.98247473819491 50.05675706194147,-79.9656229623727 50.0534658262464,-79.95009227682766 50.04812066585569,-79.93647951709556 50.040926345165026,-79.92530781389875 50.032158574496115,-79.91700648952599 50.02215353089088,-79.91189455921743 50.011295055230505,-79.91016847158805 49.99999999999999,-79.91016847158805 29.999999999999993,-79.91189455921743 29.984744778414523,-79.91700648952599 29.970073573593833,-79.92530781389875 29.956550575941442,-79.93647951709556 29.944696041122494,-79.95009227682766 29.934966209462644,-79.9656229623727 29.927735669850765,-79.98247473819491 29.923282861557286,-80 29.921779286758486,-119.99999999999999 29.921779286758486,-120.01752526180509 29.923282861557286,-120.03437703762728 29.927735669850765,-120.04990772317234 29.934966209462644,-120.06352048290445 29.944696041122494,-120.07469218610122 29.956550575941442,-120.082993510474 29.970073573593833,-120.08810544078257 29.984744778414523,-120.08983152841193 29.999999999999993))"); + + assertEquals(Functions.reducePrecision(expected1, 8), Functions.reducePrecision(result1, 8)); + assertEquals(4326, Functions.getSRID(result1)); + assertEquals(7.424558176442617E-8, comparePolygons(postgis_result1, result1), FP_TOLERANCE2); + assertEquals(Spheroid.area(postgis_result1), Spheroid.area(result1), FP_TOLERANCE2); + + assertEquals(Functions.reducePrecision(expected2, 8), Functions.reducePrecision(result2, 8)); + assertEquals(4269, Functions.getSRID(result2)); + assertEquals(Spheroid.area(postgis_result2), Spheroid.area(result2), 10); + assertEquals(7.424558176442617E-8, comparePolygons(postgis_result2, result2), FP_TOLERANCE2); + + assertEquals(Functions.reducePrecision(expected3, 8), Functions.reducePrecision(result3, 8)); + assertEquals(4269, Functions.getSRID(result3)); + assertEquals(Spheroid.area(postgis_result3), Spheroid.area(result3), FP_TOLERANCE2); + assertEquals(7.424558176442617E-8, comparePolygons(postgis_result3, result3), FP_TOLERANCE2); + + assertEquals(Functions.reducePrecision(expected4, 8), Functions.reducePrecision(result4, 8)); + assertEquals(4326, Functions.getSRID(result4)); + assertEquals(Spheroid.area(postgis_result4), Spheroid.area(result4), FP_TOLERANCE2); + assertEquals(7.424558176442617E-8, comparePolygons(postgis_result4, result4), FP_TOLERANCE2); + + assertEquals(Functions.reducePrecision(expected5, 8), Functions.reducePrecision(result5, 8)); + assertEquals(4269, Functions.getSRID(result5)); + assertEquals(Spheroid.area(postgis_result5), Spheroid.area(result5), 10); + assertEquals(7.424558176442617E-8, comparePolygons(postgis_result5, result5), FP_TOLERANCE2); + + assertEquals(Functions.reducePrecision(expected6, 8), Functions.reducePrecision(result6, 8)); + assertEquals(4326, Functions.getSRID(result6)); + + assertEquals(Functions.reducePrecision(expected7, 8), Functions.reducePrecision(result7, 8)); + assertEquals(4326, Functions.getSRID(result6)); + + assertEquals(Functions.reducePrecision(expected8, 8), Functions.reducePrecision(result8, 8)); + assertEquals(4326, Functions.getSRID(result6)); + + assertEquals(Functions.reducePrecision(expected9, 8), Functions.reducePrecision(result9, 8)); + assertEquals(4326, Functions.getSRID(result6)); + } + + @Test + public void testShiftLongitude() throws ParseException { + Geometry point = geomFromWKT("POINT(-175 10)", 4230); + Geometry linestring1 = geomFromEWKT("LINESTRING(179 10, -179 10)"); + Geometry linestring2 = geomFromEWKT("LINESTRING(179 10, 181 10)"); + Geometry polygon = geomFromEWKT("POLYGON((179 10, -179 10, -179 20, 179 20, 179 10))"); + Geometry multiPoint = geomFromEWKT("MULTIPOINT((179 10), (-179 10))"); + Geometry multiLineString = geomFromEWKT("MULTILINESTRING((179 10, -179 10), (179 20, 181 20))"); + Geometry multiPolygon = geomFromEWKT("MULTIPOLYGON(((179 10, -179 10, -179 20, 179 20, 179 10)), ((-185 10, -185 20, -175 20, -175 10, -185 10)))"); + Geometry geomCollection = geomFromEWKT("GEOMETRYCOLLECTION(POINT(190 10), LINESTRING(179 10, -179 10))"); + + Geometry expected1 = geomFromWKT("POINT (185 10)", 4230); + Geometry expected2 = geomFromEWKT("LINESTRING (179 10, 181 10)"); + Geometry expected3 = geomFromEWKT("LINESTRING (179 10, -179 10)"); + Geometry expected4 = geomFromEWKT("POLYGON ((179 10, 181 10, 181 20, 179 20, 179 10))"); + Geometry expected5 = geomFromEWKT("MULTIPOINT ((179 10), (181 10))"); + Geometry expected6 = geomFromEWKT("MULTILINESTRING ((179 10, 181 10), (179 20, -179 20))"); + Geometry expected7 = geomFromEWKT("MULTIPOLYGON (((179 10, 181 10, 181 20, 179 20, 179 10)), ((175 10, 175 20, 185 20, 185 10, 175 10)))"); + Geometry expected8 = geomFromEWKT("GEOMETRYCOLLECTION (POINT (-170 10), LINESTRING (179 10, 181 10))"); + + assertEquals(expected1, Functions.shiftLongitude(point)); + assertEquals(expected2, Functions.shiftLongitude(linestring1)); + assertEquals(expected3, Functions.shiftLongitude(linestring2)); + assertEquals(expected4, Functions.shiftLongitude(polygon)); + assertEquals(expected5, Functions.shiftLongitude(multiPoint)); + assertEquals(expected6, Functions.shiftLongitude(multiLineString)); + assertEquals(expected7, Functions.shiftLongitude(multiPolygon)); + assertEquals(expected8, Functions.shiftLongitude(geomCollection)); + } + + private static double comparePolygons(Geometry p1, Geometry p2) { + Coordinate[] coords1 = p1.getCoordinates(); + Coordinate[] coords2 = p2.getCoordinates(); + + double maxDistance = 0.0; + for (int i = 0; i < Math.min(coords1.length, coords2.length); i++) { + Geometry point1 = GEOMETRY_FACTORY.createPoint(coords1[i]); + Geometry point2 = GEOMETRY_FACTORY.createPoint(coords2[i]); + double distance = Spheroid.distance(point1,point2); + maxDistance = Math.max(maxDistance, distance); + } + return maxDistance; + } + + @Test + public void testBestSRID() throws ParseException { int[][] testCases_special = { {0, -70, 3409}, // EPSG:3409 (Antarctic Polar Stereographic) {0, 70, 3574}, // EPSG:3575 (North Pole LAEA Alaska) @@ -1211,6 +1336,7 @@ public void testBestSRID() { for (int[] testCase : testCases_special) { Geometry geom = GEOMETRY_FACTORY.createPoint(new Coordinate(testCase[0], testCase[1])); int actualEPSG = Functions.bestSRID(geom); + System.out.println("actualEPSG: "+actualEPSG); int expectedEPSG = testCase[2]; assertEquals("Failed at coordinates (" + testCase[0] + ", " + testCase[1] + ")", expectedEPSG, actualEPSG); } @@ -1243,39 +1369,6 @@ public void testBestSRID() { assertEquals("Expected World Mercator projection for wide range geometry", expectedEPSG, actualEPSG); } - @Test - public void testShiftLongitude() throws ParseException { - Geometry point = geomFromWKT("POINT(-175 10)", 4230); - Geometry linestring1 = geomFromEWKT("LINESTRING(179 10, -179 10)"); - Geometry linestring2 = geomFromEWKT("LINESTRING(179 10, 181 10)"); - Geometry polygon = geomFromEWKT("POLYGON((179 10, -179 10, -179 20, 179 20, 179 10))"); - Geometry multiPoint = geomFromEWKT("MULTIPOINT((179 10), (-179 10))"); - Geometry multiLineString = geomFromEWKT("MULTILINESTRING((179 10, -179 10), (179 20, 181 20))"); - Geometry multiPolygon = geomFromEWKT("MULTIPOLYGON(((179 10, -179 10, -179 20, 179 20, 179 10)), ((-185 10, -185 20, -175 20, -175 10, -185 10)))"); - Geometry geomCollection = geomFromEWKT("GEOMETRYCOLLECTION(POINT(190 10), LINESTRING(179 10, -179 10))"); - Geometry polygonRing = geomFromEWKT("POLYGON((-170 -20, 170 -20, 170 20, -170 20, -170 -20), (-175 -10, 175 -10, 175 10, -175 10, -175 -10))"); - - Geometry expected1 = geomFromWKT("POINT (185 10)", 4230); - Geometry expected2 = geomFromEWKT("LINESTRING (179 10, 181 10)"); - Geometry expected3 = geomFromEWKT("LINESTRING (179 10, -179 10)"); - Geometry expected4 = geomFromEWKT("POLYGON ((179 10, 181 10, 181 20, 179 20, 179 10))"); - Geometry expected5 = geomFromEWKT("MULTIPOINT ((179 10), (181 10))"); - Geometry expected6 = geomFromEWKT("MULTILINESTRING ((179 10, 181 10), (179 20, -179 20))"); - Geometry expected7 = geomFromEWKT("MULTIPOLYGON (((179 10, 181 10, 181 20, 179 20, 179 10)), ((175 10, 175 20, 185 20, 185 10, 175 10)))"); - Geometry expected8 = geomFromEWKT("GEOMETRYCOLLECTION (POINT (-170 10), LINESTRING (179 10, 181 10))"); - Geometry expected9 = geomFromEWKT("POLYGON ((190 -20, 170 -20, 170 20, 190 20, 190 -20), (185 -10, 175 -10, 175 10, 185 10, 185 -10))"); - - assertEquals(expected1, Functions.shiftLongitude(point)); - assertEquals(expected2, Functions.shiftLongitude(linestring1)); - assertEquals(expected3, Functions.shiftLongitude(linestring2)); - assertEquals(expected4, Functions.shiftLongitude(polygon)); - assertEquals(expected5, Functions.shiftLongitude(multiPoint)); - assertEquals(expected6, Functions.shiftLongitude(multiLineString)); - assertEquals(expected7, Functions.shiftLongitude(multiPolygon)); - assertEquals(expected8, Functions.shiftLongitude(geomCollection)); - assertEquals(expected9, Functions.shiftLongitude(polygonRing)); - } - @Test public void nRingsUnsupported() { LineString lineString = GEOMETRY_FACTORY.createLineString(coordArray3d(0, 1, 1, 1, 2, 1, 1, 2, 2)); diff --git a/docs/api/flink/Function.md b/docs/api/flink/Function.md index eebd3f9430..799a58c546 100644 --- a/docs/api/flink/Function.md +++ b/docs/api/flink/Function.md @@ -553,9 +553,28 @@ Output: `LINESTRING Z(-1 -1 0, 10 5 5)` ## ST_Buffer -Introduction: Returns a geometry/geography that represents all points whose distance from this Geometry/geography is less than or equal to distance. +Introduction: Returns a geometry/geography that represents all points whose distance from this Geometry/geography is less than or equal to distance. The function supports both Planar/Euclidean and Spheroidal/Geodesic buffering (Since v1.6.0). Spheroidal buffer also supports geometries crossing the International Date Line (IDL). -The optional third parameter controls the buffer accuracy and style. Buffer accuracy is specified by the number of line segments approximating a quarter circle, with a default of 8 segments. Buffer style can be set by providing blank-separated key=value pairs in a list format. +Mode of buffer calculation (Since: `v1.6.0`): + +The optional third parameter, `useSpheroid`, controls the mode of buffer calculation. + +- Planar Buffering (default): When `useSpheroid` is false, `ST_Buffer` performs standard planar buffering based on the provided parameters. +- Spheroidal Buffering: + - When `useSpheroid` is set to true, the function returns the spheroidal buffer polygon for more accurate representation over the Earth. In this mode, the unit of the buffer distance is interpreted as meters. + - ST_Buffer first determines the most appropriate Spatial Reference Identifier (SRID) for a given geometry, based on its spatial extent and location, using `ST_BestSRID`. + - The geometry is then transformed from its original SRID to the selected SRID. If the input geometry does not have a set SRID, `ST_Buffer` defaults to using WGS 84 (SRID 4326) as its original SRID. + - The standard planar buffer operation is then applied in this coordinate system. + - Finally, the buffered geometry is transformed back to its original SRID, or to WGS 84 if the original SRID was not set. + +!!!note + Spheroidal buffering only supports lon/lat coordinate systems and will throw an `IllegalArgumentException` for input geometries in meter based coordinate systems. +!!!note + Spheroidal buffering may not produce accurate output buffer for input geometries larger than a UTM zone. + +Buffer Style Parameters: + +The optional forth parameter controls the buffer accuracy and style. Buffer accuracy is specified by the number of line segments approximating a quarter circle, with a default of 8 segments. Buffer style can be set by providing blank-separated key=value pairs in a list format. - `quad_segs=#` : Number of line segments utilized to approximate a quarter circle (default is 8). - `endcap=round|flat|square` : End cap style (default is `round`). `butt` is an accepted synonym for `flat`. @@ -569,16 +588,22 @@ The optional third parameter controls the buffer accuracy and style. Buffer accu Format: ``` -ST_Buffer (A: Geometry, buffer: Double, bufferStyleParameters: String [Optional]) +ST_Buffer (A: Geometry, buffer: Double) +``` +``` +ST_Buffer (A: Geometry, buffer: Double, useSpheroid: Boolean) +``` +``` +ST_Buffer (A: Geometry, buffer: Double, useSpheroid: Boolean, bufferStyleParameters: String) ``` Since: `v1.5.1` -Example: +SQL Example: ```sql SELECT ST_Buffer(ST_GeomFromWKT('POINT(0 0)'), 10) -SELECT ST_Buffer(ST_GeomFromWKT('POINT(0 0)'), 10, 'quad_segs=2') +SELECT ST_Buffer(ST_GeomFromWKT('POINT(0 0)'), 10, false, 'quad_segs=2') ``` Output: @@ -588,10 +613,10 @@ Output: 8 Segments   2 Segments -Example: +SQL Example: ```sql -SELECT ST_Buffer(ST_GeomFromWKT('LINESTRING(0 0, 50 70, 100 100)'), 10, 'side=left') +SELECT ST_Buffer(ST_GeomFromWKT('LINESTRING(0 0, 50 70, 100 100)'), 10, false, 'side=left') ``` Output: diff --git a/docs/api/snowflake/vector-data/Function.md b/docs/api/snowflake/vector-data/Function.md index ffb49685d0..cee7b315d9 100644 --- a/docs/api/snowflake/vector-data/Function.md +++ b/docs/api/snowflake/vector-data/Function.md @@ -389,16 +389,79 @@ Output: `LINESTRING Z(-1 -1 0, 10 5 5)` ## ST_Buffer -Introduction: Returns a geometry/geography that represents all points whose distance from this Geometry/geography is less than or equal to distance. +Introduction: Returns a geometry/geography that represents all points whose distance from this Geometry/geography is less than or equal to distance. The function supports both Planar/Euclidean and Spheroidal/Geodesic buffering (Since v1.6.0). Spheroidal buffer also supports geometries crossing the International Date Line (IDL). -Format: `ST_Buffer (A:geometry, buffer: Double)` +Mode of buffer calculation (Since: `v1.6.0`): -SQL example: -```SQL -SELECT ST_Buffer(polygondf.countyshape, 1) -FROM polygondf +The optional third parameter, `useSpheroid`, controls the mode of buffer calculation. + +- Planar Buffering (default): When `useSpheroid` is false, `ST_Buffer` performs standard planar buffering based on the provided parameters. +- Spheroidal Buffering: + - When `useSpheroid` is set to true, the function returns the spheroidal buffer polygon for more accurate representation over the Earth. In this mode, the unit of the buffer distance is interpreted as meters. + - ST_Buffer first determines the most appropriate Spatial Reference Identifier (SRID) for a given geometry, based on its spatial extent and location, using `ST_BestSRID`. + - The geometry is then transformed from its original SRID to the selected SRID. If the input geometry does not have a set SRID, `ST_Buffer` defaults to using WGS 84 (SRID 4326) as its original SRID. + - The standard planar buffer operation is then applied in this coordinate system. + - Finally, the buffered geometry is transformed back to its original SRID, or to WGS 84 if the original SRID was not set. + +!!!note + Spheroidal buffering only supports lon/lat coordinate systems and will throw an `IllegalArgumentException` for input geometries in meter based coordinate systems. +!!!note + Spheroidal buffering may not produce accurate output buffer for input geometries larger than a UTM zone. + +Buffer Style Parameters: + +The optional forth parameter controls the buffer accuracy and style. Buffer accuracy is specified by the number of line segments approximating a quarter circle, with a default of 8 segments. Buffer style can be set by providing blank-separated key=value pairs in a list format. + +- `quad_segs=#` : Number of line segments utilized to approximate a quarter circle (default is 8). +- `endcap=round|flat|square` : End cap style (default is `round`). `butt` is an accepted synonym for `flat`. +- `join=round|mitre|bevel` : Join style (default is `round`). `miter` is an accepted synonym for `mitre`. +- `mitre_limit=#.#` : mitre ratio limit and it only affects mitred join style. `miter_limit` is an accepted synonym for `mitre_limit`. +- `side=both|left|right` : The option `left` or `right` enables a single-sided buffer operation on the geometry, with the buffered side aligned according to the direction of the line. This functionality is specific to LINESTRING geometry and has no impact on POINT or POLYGON geometries. By default, square end caps are applied. + +!!!note + `ST_Buffer` throws an `IllegalArgumentException` if the correct format, parameters, or options are not provided. + +Format: + +``` +ST_Buffer (A: Geometry, buffer: Double) +``` +``` +ST_Buffer (A: Geometry, buffer: Double, useSpheroid: Boolean) +``` +``` +ST_Buffer (A: Geometry, buffer: Double, useSpheroid: Boolean, bufferStyleParameters: String) ``` +Since: `v1.5.1` + +SQL Example: + +```sql +SELECT ST_Buffer(ST_GeomFromWKT('POINT(0 0)'), 10) +SELECT ST_Buffer(ST_GeomFromWKT('POINT(0 0)'), 10, false, 'quad_segs=2') +``` + +Output: + +Point buffer with 8 quadrant segments +Point buffer with 2 quadrant segments + +8 Segments   2 Segments + +SQL Example: + +```sql +SELECT ST_Buffer(ST_GeomFromWKT('LINESTRING(0 0, 50 70, 100 100)'), 10, false, 'side=left') +``` + +Output: + +Original Linestring +Original Linestring with buffer on the left side + +Original Linestring   Left side buffed Linestring + ## ST_BuildArea Introduction: Returns the areal geometry formed by the constituent linework of the input geometry. diff --git a/docs/api/sql/Function.md b/docs/api/sql/Function.md index 745ed73517..a5e8c4d2ed 100644 --- a/docs/api/sql/Function.md +++ b/docs/api/sql/Function.md @@ -551,9 +551,28 @@ Output: `LINESTRING Z(-1 -1 0, 10 5 5)` ## ST_Buffer -Introduction: Returns a geometry/geography that represents all points whose distance from this Geometry/geography is less than or equal to distance. +Introduction: Returns a geometry/geography that represents all points whose distance from this Geometry/geography is less than or equal to distance. The function supports both Planar/Euclidean and Spheroidal/Geodesic buffering (Since v1.6.0). Spheroidal buffer also supports geometries crossing the International Date Line (IDL). -The optional third parameter controls the buffer accuracy and style. Buffer accuracy is specified by the number of line segments approximating a quarter circle, with a default of 8 segments. Buffer style can be set by providing blank-separated key=value pairs in a list format. +Mode of buffer calculation (Since: `v1.6.0`): + +The optional third parameter, `useSpheroid`, controls the mode of buffer calculation. + +- Planar Buffering (default): When `useSpheroid` is false, `ST_Buffer` performs standard planar buffering based on the provided parameters. +- Spheroidal Buffering: + - When `useSpheroid` is set to true, the function returns the spheroidal buffer polygon for more accurate representation over the Earth. In this mode, the unit of the buffer distance is interpreted as meters. + - ST_Buffer first determines the most appropriate Spatial Reference Identifier (SRID) for a given geometry, based on its spatial extent and location, using `ST_BestSRID`. + - The geometry is then transformed from its original SRID to the selected SRID. If the input geometry does not have a set SRID, `ST_Buffer` defaults to using WGS 84 (SRID 4326) as its original SRID. + - The standard planar buffer operation is then applied in this coordinate system. + - Finally, the buffered geometry is transformed back to its original SRID, or to WGS 84 if the original SRID was not set. + +!!!note + As of now, spheroidal buffering only supports lon/lat coordinate systems and will throw an `IllegalArgumentException` for input geometries in meter based coordinate systems. +!!!note + Spheroidal buffering may not produce accurate output buffer for input geometries larger than a UTM zone. + +Buffer Style Parameters: + +The optional forth parameter controls the buffer accuracy and style. Buffer accuracy is specified by the number of line segments approximating a quarter circle, with a default of 8 segments. Buffer style can be set by providing blank-separated key=value pairs in a list format. - `quad_segs=#` : Number of line segments utilized to approximate a quarter circle (default is 8). - `endcap=round|flat|square` : End cap style (default is `round`). `butt` is an accepted synonym for `flat`. @@ -567,16 +586,22 @@ The optional third parameter controls the buffer accuracy and style. Buffer accu Format: ``` -ST_Buffer (A: Geometry, buffer: Double, bufferStyleParameters: String [Optional]) +ST_Buffer (A: Geometry, buffer: Double) +``` +``` +ST_Buffer (A: Geometry, buffer: Double, useSpheroid: Boolean) +``` +``` +ST_Buffer (A: Geometry, buffer: Double, useSpheroid: Boolean, bufferStyleParameters: String) ``` Since: `v1.5.1` -Spark SQL Example: +SQL Example: ```sql SELECT ST_Buffer(ST_GeomFromWKT('POINT(0 0)'), 10) -SELECT ST_Buffer(ST_GeomFromWKT('POINT(0 0)'), 10, 'quad_segs=2') +SELECT ST_Buffer(ST_GeomFromWKT('POINT(0 0)'), 10, false, 'quad_segs=2') ``` Output: @@ -586,10 +611,10 @@ Output: 8 Segments   2 Segments -Spark SQL Example: +SQL Example: ```sql -SELECT ST_Buffer(ST_GeomFromWKT('LINESTRING(0 0, 50 70, 100 100)'), 10, 'side=left') +SELECT ST_Buffer(ST_GeomFromWKT('LINESTRING(0 0, 50 70, 100 100)'), 10, false, 'side=left') ``` Output: 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 e690ea1780..65f066230a 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 @@ -13,6 +13,7 @@ */ package org.apache.sedona.flink.expressions; +import org.apache.calcite.runtime.Geometries; import org.apache.commons.lang3.tuple.Pair; import org.apache.flink.table.annotation.DataTypeHint; import org.apache.flink.table.annotation.InputGroup; @@ -20,6 +21,8 @@ import org.apache.sedona.common.FunctionsGeoTools; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.operation.buffer.BufferParameters; +import org.opengis.referencing.FactoryException; +import org.opengis.referencing.operation.TransformException; import java.util.Arrays; @@ -69,16 +72,23 @@ public Geometry eval(@DataTypeHint(value = "RAW", bridgedTo = org.locationtech.j public static class ST_Buffer 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("Double") Double radius) { + Object o, @DataTypeHint("Double") Double radius) throws FactoryException, TransformException { Geometry geom = (Geometry) o; return org.apache.sedona.common.Functions.buffer(geom, radius); } @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("Double") Double radius, @DataTypeHint("String") String params) { + Object o, @DataTypeHint("Double") Double radius, @DataTypeHint("Boolean") Boolean useSpheroid) throws FactoryException, TransformException { Geometry geom = (Geometry) o; - return org.apache.sedona.common.Functions.buffer(geom, radius, params); + return org.apache.sedona.common.Functions.buffer(geom, radius, useSpheroid); + } + + @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("Double") Double radius, @DataTypeHint("Boolean") Boolean useSpheroid, @DataTypeHint("String") String params) throws FactoryException, TransformException { + Geometry geom = (Geometry) o; + return org.apache.sedona.common.Functions.buffer(geom, radius, useSpheroid, params); } } 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 daa6459b1d..4a5129af5d 100644 --- a/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java +++ b/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java @@ -84,17 +84,25 @@ public void testBuffer() { Geometry result = (Geometry) first(bufferTable).getField(0); assert(result instanceof Polygon); - String actual = (String) first(tableEnv.sqlQuery("SELECT ST_AsText(ST_ReducePrecision(ST_Buffer(ST_GeomFromWKT('LINESTRING(0 0, 50 70, 100 100)'), 10, 'side=left'), 4))")).getField(0); + String actual = (String) first(tableEnv.sqlQuery("SELECT ST_AsText(ST_ReducePrecision(ST_Buffer(ST_GeomFromWKT('LINESTRING(0 0, 50 70, 100 100)'), 10, false, 'side=left'), 4))")).getField(0); String expected = "POLYGON ((50 70, 0 0, -8.1373 5.8124, 41.8627 75.8124, 43.2167 77.3476, 44.855 78.5749, 94.855 108.5749, 100 100, 50 70))"; assertEquals(expected, actual); - actual = (String) first(tableEnv.sqlQuery("SELECT ST_AsText(ST_ReducePrecision(ST_Buffer(ST_GeomFromWKT('LINESTRING(0 0, 50 70, 70 -3)'), 10, 'endcap=square'), 4))")).getField(0); + actual = (String) first(tableEnv.sqlQuery("SELECT ST_AsText(ST_ReducePrecision(ST_Buffer(ST_GeomFromWKT('LINESTRING(0 0, 50 70, 70 -3)'), 10, false, 'endcap=square'), 4))")).getField(0); expected = "POLYGON ((43.2156 77.3465, 44.8523 78.5733, 46.7044 79.4413, 48.6944 79.9144, 50.739 79.9727, 52.7527 79.6137, 54.6512 78.8525, 56.3552 77.7209, 57.7932 76.2663, 58.9052 74.5495, 59.6446 72.6424, 79.6446 -0.3576, 82.2869 -10.0022, 62.9978 -15.2869, 45.9128 47.0733, 8.1373 -5.8124, 2.325 -13.9497, -13.9497 -2.325, 41.8627 75.8124, 43.2156 77.3465))"; assertEquals(expected, actual); - actual = (String) first(tableEnv.sqlQuery("SELECT ST_AsText(ST_ReducePrecision(ST_Buffer(ST_Point(100, 90), 200, 'quad_segs=4'), 4))")).getField(0); + actual = (String) first(tableEnv.sqlQuery("SELECT ST_AsText(ST_ReducePrecision(ST_Buffer(ST_Point(100, 90), 200, false, 'quad_segs=4'), 4))")).getField(0); expected = "POLYGON ((284.7759 13.4633, 241.4214 -51.4214, 176.5367 -94.7759, 100 -110, 23.4633 -94.7759, -41.4214 -51.4214, -84.7759 13.4633, -100 90, -84.7759 166.5367, -41.4214 231.4214, 23.4633 274.7759, 100 290, 176.5367 274.7759, 241.4214 231.4214, 284.7759 166.5367, 300 90, 284.7759 13.4633))"; assertEquals(expected, actual); + + actual = (String) first(tableEnv.sqlQuery("SELECT ST_AsText(ST_ReducePrecision(ST_Buffer(ST_GeomFromWKT('LINESTRING(0 0, 50 70, 70 -3)'), 10, true, 'endcap=square'), 4))")).getField(0); + expected = "POLYGON ((50 70, 50.0001 70, 70.0001 -3, 70.0001 -3.0001, 69.9999 -3.0001, 50 69.9999, 0.0001 0, 0 -0.0001, -0.0001 0, 49.9999 70, 50 70))"; + assertEquals(expected, actual); + + actual = (String) first(tableEnv.sqlQuery("SELECT ST_AsText(ST_ReducePrecision(ST_Buffer(ST_GeomFromWKT('POLYGON((-120 30, -80 30, -80 50, -120 50, -120 30))'), 200, true, 'quad_segs=4'), 4))")).getField(0); + expected = "POLYGON ((-120.0018 50, -120.0017 50.0004, -120.0013 50.0008, -120.0007 50.0011, -120 50.0012, -80 50.0012, -79.9993 50.0011, -79.9987 50.0008, -79.9983 50.0004, -79.9982 50, -79.9982 30, -79.9983 29.9994, -79.9987 29.9989, -79.9993 29.9986, -80 29.9984, -120 29.9984, -120.0007 29.9986, -120.0013 29.9989, -120.0017 29.9994, -120.0018 30, -120.0018 50))"; + assertEquals(expected, actual); } @Test @@ -137,6 +145,8 @@ public void testClosestPoint() { Geometry result = (Geometry) first(table).getField(0); assertEquals("POINT (160 40)", result.toString()); } + + @Test public void testCentroid() { Table polygonTable = tableEnv.sqlQuery("SELECT ST_GeomFromText('POLYGON ((2 2, 0 0, 2 0, 0 2, 2 2))') as geom"); Table resultTable = polygonTable.select(call(Functions.ST_Centroid.class.getSimpleName(), $("geom"))); diff --git a/python/sedona/sql/st_functions.py b/python/sedona/sql/st_functions.py index 456b4dc03f..d6f31fa0d1 100644 --- a/python/sedona/sql/st_functions.py +++ b/python/sedona/sql/st_functions.py @@ -341,7 +341,7 @@ def ST_Boundary(geometry: ColumnOrName) -> Column: @validate_argument_types -def ST_Buffer(geometry: ColumnOrName, buffer: ColumnOrNameOrNumber, parameters: Optional[Union[ColumnOrName, str]] = None) -> Column: +def ST_Buffer(geometry: ColumnOrName, buffer: ColumnOrNameOrNumber, useSpheroid: Optional[Union[ColumnOrName, bool]] = None, parameters: Optional[Union[ColumnOrName, str]] = None) -> Column: """Calculate a geometry that represents all points whose distance from the input geometry column is equal to or less than a given amount. @@ -352,10 +352,12 @@ def ST_Buffer(geometry: ColumnOrName, buffer: ColumnOrNameOrNumber, parameters: :return: Buffered geometry as a geometry column. :rtype: Column """ - if parameters is None: + if parameters is None and useSpheroid is None: args = (geometry, buffer) + elif parameters is None: + args = (geometry, buffer, useSpheroid) else: - args = (geometry, buffer, parameters) + args = (geometry, buffer, useSpheroid, parameters) return _call_st_function("ST_Buffer", args) diff --git a/python/tests/sql/test_dataframe_api.py b/python/tests/sql/test_dataframe_api.py index 69802c13df..4469178a3f 100644 --- a/python/tests/sql/test_dataframe_api.py +++ b/python/tests/sql/test_dataframe_api.py @@ -74,6 +74,7 @@ (stf.ST_BestSRID, ("geom",), "triangle_geom", "", 3395), (stf.ST_Boundary, ("geom",), "triangle_geom", "", "LINESTRING (0 0, 1 0, 1 1, 0 0)"), (stf.ST_Buffer, ("point", 1.0), "point_geom", "ST_ReducePrecision(geom, 2)", "POLYGON ((0.98 0.8, 0.92 0.62, 0.83 0.44, 0.71 0.29, 0.56 0.17, 0.38 0.08, 0.2 0.02, 0 0, -0.2 0.02, -0.38 0.08, -0.56 0.17, -0.71 0.29, -0.83 0.44, -0.92 0.62, -0.98 0.8, -1 1, -0.98 1.2, -0.92 1.38, -0.83 1.56, -0.71 1.71, -0.56 1.83, -0.38 1.92, -0.2 1.98, 0 2, 0.2 1.98, 0.38 1.92, 0.56 1.83, 0.71 1.71, 0.83 1.56, 0.92 1.38, 0.98 1.2, 1 1, 0.98 0.8))"), + (stf.ST_Buffer, ("point", 1.0, True), "point_geom", "", "POLYGON ((0.0000089758113634 1.0000000082631704, 0.0000088049473096 0.9999982455016537, 0.0000082957180969 0.9999965501645043, 0.0000074676931201 0.9999949874025787, 0.0000063526929175 0.9999936172719434, 0.0000049935663218 0.9999924924259491, 0.000003442543808 0.9999916560917964, 0.0000017592303026 0.9999911404093366, 0.0000000083146005 0.999990965195956, -0.0000017429165916 0.9999911371850047, -0.0000034271644398 0.9999916497670388, -0.0000049797042467 0.9999924832438165, -0.0000063408727792 0.9999936055852924, -0.0000074583610959 0.9999949736605123, -0.0000082892247492 0.9999965348951139, -0.0000088015341156 0.999998229291728, -0.0000089756014341 0.9999999917356441, -0.0000088047373942 1.0000017544971334, -0.0000082955082053 1.000003449834261, -0.0000074674832591 1.000005012596174, -0.000006352483086 1.0000063827268086, -0.0000049933565169 1.000007507572813, -0.0000034423340222 1.0000083439069856, -0.0000017590205255 1.0000088595894723, -0.0000000081048183 1.0000090348028825, 0.0000017431263877 1.0000088628138615, 0.0000034273742602 1.0000083502318489, 0.000004979914097 1.0000075167550835, 0.0000063410826597 1.000006394413609, 0.000007458571003 1.0000050263383786, 0.000008289434675 1.000003465103757, 0.0000088017440489 1.0000017707071163, 0.0000089758113634 1.0000000082631704))"), (stf.ST_BuildArea, ("geom",), "multiline_geom", "ST_Normalize(geom)", "POLYGON ((0 0, 1 1, 1 0, 0 0))"), (stf.ST_BoundingDiagonal, ("geom",), "square_geom", "ST_BoundingDiagonal(geom)", "LINESTRING (1 0, 2 1)"), (stf.ST_Centroid, ("geom",), "triangle_geom", "ST_ReducePrecision(geom, 2)", "POINT (0.67 0.33)"), diff --git a/python/tests/sql/test_function.py b/python/tests/sql/test_function.py index 9f6035f012..2b1f674b1f 100644 --- a/python/tests/sql/test_function.py +++ b/python/tests/sql/test_function.py @@ -93,14 +93,40 @@ def test_st_buffer(self): polygon_df = self.spark.sql("select ST_GeomFromWKT(polygontable._c0) as countyshape from polygontable") polygon_df.createOrReplaceTempView("polygondf") polygon_df.show() + function_df = self.spark.sql("select ST_ReducePrecision(ST_Buffer(polygondf.countyshape, 1), 2) from polygondf") actual = function_df.take(1)[0][0].wkt assert actual == "POLYGON ((-98.02 41.77, -98.02 41.78, -98.02 41.8, -98.02 41.81, -98.02 41.82, -98.02 41.83, -98.02 41.84, -98.02 41.85, -98.02 41.86, -98.02 41.87, -98.02 41.89, -98.02 41.9, -98.02 41.91, -98.02 41.92, -98.02 41.93, -98.02 41.95, -98.02 41.98, -98.02 42, -98.02 42.01, -98.02 42.02, -98.02 42.04, -98.02 42.05, -98.02 42.07, -98.02 42.09, -98.02 42.11, -98.02 42.12, -97.99 42.31, -97.93 42.5, -97.84 42.66, -97.72 42.81, -97.57 42.93, -97.4 43.02, -97.21 43.07, -97.02 43.09, -97 43.09, -96.98 43.09, -96.97 43.09, -96.96 43.09, -96.95 43.09, -96.94 43.09, -96.92 43.09, -96.91 43.09, -96.89 43.09, -96.88 43.09, -96.86 43.09, -96.84 43.09, -96.83 43.09, -96.82 43.09, -96.81 43.09, -96.8 43.09, -96.79 43.09, -96.78 43.09, -96.76 43.09, -96.74 43.09, -96.73 43.09, -96.71 43.09, -96.7 43.09, -96.69 43.09, -96.68 43.09, -96.66 43.09, -96.65 43.09, -96.64 43.09, -96.63 43.09, -96.62 43.09, -96.61 43.09, -96.6 43.09, -96.59 43.09, -96.58 43.09, -96.57 43.09, -96.56 43.09, -96.55 43.09, -96.36 43.07, -96.17 43.01, -96 42.92, -95.86 42.8, -95.73 42.66, -95.64 42.49, -95.58 42.31, -95.56 42.12, -95.56 42.1, -95.56 42.09, -95.56 42.08, -95.56 42.07, -95.56 42.06, -95.56 42.04, -95.56 42, -95.56 41.99, -95.56 41.98, -95.56 41.97, -95.56 41.96, -95.56 41.95, -95.56 41.94, -95.56 41.93, -95.56 41.92, -95.56 41.91, -95.56 41.9, -95.56 41.89, -95.56 41.88, -95.56 41.87, -95.56 41.86, -95.56 41.85, -95.56 41.83, -95.56 41.82, -95.56 41.81, -95.56 41.8, -95.56 41.79, -95.56 41.78, -95.56 41.77, -95.56 41.76, -95.56 41.75, -95.56 41.74, -95.58 41.54, -95.63 41.36, -95.72 41.19, -95.85 41.03, -96 40.91, -96.17 40.82, -96.36 40.76, -96.55 40.74, -96.56 40.74, -96.57 40.74, -96.58 40.74, -96.59 40.74, -96.6 40.74, -96.62 40.74, -96.63 40.74, -96.64 40.74, -96.65 40.74, -96.67 40.74, -96.68 40.74, -96.69 40.74, -96.7 40.74, -96.71 40.74, -96.72 40.74, -96.73 40.74, -96.74 40.74, -96.75 40.74, -96.76 40.74, -96.77 40.74, -96.78 40.74, -96.79 40.74, -96.8 40.74, -96.81 40.74, -96.82 40.74, -96.83 40.74, -96.85 40.74, -96.86 40.74, -96.88 40.74, -96.9 40.74, -96.91 40.74, -96.92 40.74, -96.93 40.74, -96.94 40.74, -96.95 40.74, -96.97 40.74, -96.98 40.74, -96.99 40.74, -97.01 40.74, -97.02 40.74, -97.22 40.76, -97.4 40.82, -97.57 40.91, -97.72 41.03, -97.85 41.18, -97.94 41.35, -98 41.54, -98.02 41.73, -98.02 41.75, -98.02 41.76, -98.02 41.77))" - function_df = self.spark.sql("select ST_ReducePrecision(ST_Buffer(polygondf.countyshape, 10, 'endcap=square'), 2) from polygondf") + function_df = self.spark.sql("select ST_ReducePrecision(ST_Buffer(polygondf.countyshape, 1, true), 2) from polygondf") + actual = function_df.take(1)[0][0].wkt + assert actual == "POLYGON ((-97.02 42.01, -97.02 42.02, -97.02 42.03, -97.02 42.04, -97.02 42.05, -97.02 42.06, -97.02 42.07, -97.02 42.08, -97.02 42.09, -97.01 42.09, -97 42.09, -96.99 42.09, -96.98 42.09, -96.97 42.09, -96.96 42.09, -96.95 42.09, -96.94 42.09, -96.93 42.09, -96.92 42.09, -96.91 42.09, -96.9 42.09, -96.89 42.09, -96.88 42.09, -96.87 42.09, -96.86 42.09, -96.85 42.09, -96.84 42.09, -96.83 42.09, -96.82 42.09, -96.81 42.09, -96.8 42.09, -96.79 42.09, -96.78 42.09, -96.77 42.09, -96.76 42.09, -96.75 42.09, -96.74 42.09, -96.73 42.09, -96.72 42.09, -96.71 42.09, -96.7 42.09, -96.69 42.09, -96.68 42.09, -96.67 42.09, -96.66 42.09, -96.65 42.09, -96.64 42.09, -96.63 42.09, -96.62 42.09, -96.61 42.09, -96.6 42.09, -96.59 42.09, -96.58 42.09, -96.57 42.09, -96.56 42.09, -96.56 42.08, -96.56 42.07, -96.56 42.06, -96.56 42.05, -96.56 42.04, -96.56 42.03, -96.56 42.02, -96.55 42.02, -96.56 42, -96.56 41.99, -96.56 41.98, -96.56 41.97, -96.56 41.96, -96.56 41.95, -96.56 41.94, -96.56 41.93, -96.56 41.92, -96.56 41.91, -96.56 41.9, -96.56 41.89, -96.56 41.88, -96.56 41.87, -96.56 41.86, -96.56 41.85, -96.56 41.84, -96.56 41.83, -96.56 41.82, -96.56 41.81, -96.56 41.8, -96.56 41.79, -96.56 41.78, -96.56 41.77, -96.56 41.76, -96.56 41.75, -96.56 41.74, -96.57 41.74, -96.58 41.74, -96.59 41.74, -96.6 41.74, -96.61 41.74, -96.62 41.74, -96.63 41.74, -96.64 41.74, -96.65 41.74, -96.66 41.74, -96.67 41.74, -96.68 41.74, -96.69 41.74, -96.7 41.74, -96.71 41.74, -96.72 41.74, -96.73 41.74, -96.74 41.74, -96.75 41.74, -96.76 41.74, -96.77 41.74, -96.78 41.74, -96.79 41.74, -96.8 41.74, -96.81 41.74, -96.82 41.74, -96.83 41.74, -96.84 41.74, -96.85 41.74, -96.86 41.74, -96.87 41.74, -96.88 41.74, -96.89 41.74, -96.9 41.74, -96.91 41.74, -96.92 41.74, -96.93 41.74, -96.94 41.74, -96.95 41.74, -96.96 41.74, -96.97 41.74, -96.98 41.74, -96.99 41.74, -97 41.74, -97.01 41.74, -97.02 41.74, -97.02 41.75, -97.02 41.76, -97.02 41.77, -97.02 41.78, -97.02 41.79, -97.02 41.8, -97.02 41.81, -97.02 41.82, -97.02 41.83, -97.02 41.84, -97.02 41.85, -97.02 41.86, -97.02 41.87, -97.02 41.88, -97.02 41.89, -97.02 41.9, -97.02 41.91, -97.02 41.92, -97.02 41.93, -97.02 41.94, -97.02 41.95, -97.02 41.96, -97.02 41.97, -97.02 41.98, -97.02 41.99, -97.02 42, -97.02 42.01))" + + function_df = self.spark.sql("select ST_ReducePrecision(ST_Buffer(polygondf.countyshape, 10, false, 'endcap=square'), 2) from polygondf") actual = function_df.take(1)[0][0].wkt assert actual == "POLYGON ((-107.02 42.06, -107.02 42.07, -107.02 42.09, -107.02 42.11, -107.02 42.32, -107.02 42.33, -107.01 42.42, -107.01 42.43, -106.77 44.33, -106.16 46.15, -105.22 47.82, -103.98 49.27, -102.48 50.47, -100.78 51.36, -98.94 51.9, -97.04 52.09, -97.03 52.09, -97.01 52.09, -96.95 52.09, -96.9 52.09, -96.81 52.09, -96.7 52.09, -96.68 52.09, -96.65 52.09, -96.55 52.09, -96.54 52.09, -96.49 52.09, -96.48 52.09, -94.58 51.89, -92.74 51.33, -91.04 50.43, -89.55 49.23, -88.32 47.76, -87.39 46.08, -86.79 44.25, -86.56 42.35, -86.56 42.18, -86.56 42.17, -86.56 42.1, -86.55 41.99, -86.56 41.9, -86.56 41.78, -86.56 41.77, -86.56 41.75, -86.56 41.73, -86.56 41.7, -86.75 39.76, -87.33 37.89, -88.25 36.17, -89.49 34.66, -91 33.43, -92.72 32.51, -94.59 31.94, -96.53 31.74, -96.55 31.74, -96.56 31.74, -96.57 31.74, -96.58 31.74, -96.6 31.74, -96.69 31.74, -96.72 31.74, -96.75 31.74, -96.94 31.74, -97.02 31.74, -97.04 31.74, -97.06 31.74, -98.99 31.94, -100.85 32.5, -102.56 33.42, -104.07 34.65, -105.31 36.14, -106.23 37.85, -106.81 39.71, -107.02 41.64, -107.02 41.75, -107.02 41.94, -107.02 41.96, -107.02 41.99, -107.02 42.01, -107.02 42.02, -107.02 42.03, -107.02 42.06))" + function_df = self.spark.sql("select ST_ReducePrecision(ST_Buffer(polygondf.countyshape, 10, true, 'endcap=square'), 2) from polygondf") + actual = function_df.take(1)[0][0].wkt + assert actual == "POLYGON ((-97.02 42.01, -97.02 42.02, -97.02 42.03, -97.02 42.04, -97.02 42.05, -97.02 42.06, -97.02 42.07, -97.02 42.08, -97.02 42.09, -97.01 42.09, -97 42.09, -96.99 42.09, -96.98 42.09, -96.97 42.09, -96.96 42.09, -96.95 42.09, -96.94 42.09, -96.93 42.09, -96.92 42.09, -96.91 42.09, -96.9 42.09, -96.89 42.09, -96.88 42.09, -96.87 42.09, -96.86 42.09, -96.85 42.09, -96.84 42.09, -96.83 42.09, -96.82 42.09, -96.81 42.09, -96.8 42.09, -96.79 42.09, -96.78 42.09, -96.77 42.09, -96.76 42.09, -96.75 42.09, -96.74 42.09, -96.73 42.09, -96.72 42.09, -96.71 42.09, -96.7 42.09, -96.69 42.09, -96.68 42.09, -96.67 42.09, -96.66 42.09, -96.65 42.09, -96.64 42.09, -96.63 42.09, -96.62 42.09, -96.61 42.09, -96.6 42.09, -96.59 42.09, -96.58 42.09, -96.57 42.09, -96.56 42.09, -96.56 42.08, -96.56 42.07, -96.56 42.06, -96.56 42.05, -96.56 42.04, -96.56 42.03, -96.55 42.03, -96.55 42.02, -96.56 42, -96.56 41.99, -96.56 41.98, -96.56 41.97, -96.56 41.96, -96.56 41.95, -96.56 41.94, -96.56 41.93, -96.56 41.92, -96.56 41.91, -96.56 41.9, -96.56 41.89, -96.56 41.88, -96.56 41.87, -96.56 41.86, -96.56 41.85, -96.56 41.84, -96.56 41.83, -96.56 41.82, -96.56 41.81, -96.56 41.8, -96.56 41.79, -96.56 41.78, -96.56 41.77, -96.56 41.76, -96.56 41.75, -96.56 41.74, -96.57 41.74, -96.58 41.74, -96.59 41.74, -96.6 41.74, -96.61 41.74, -96.62 41.74, -96.63 41.74, -96.64 41.74, -96.65 41.74, -96.66 41.74, -96.67 41.74, -96.68 41.74, -96.69 41.74, -96.7 41.74, -96.71 41.74, -96.72 41.74, -96.73 41.74, -96.74 41.74, -96.75 41.74, -96.76 41.74, -96.77 41.74, -96.78 41.74, -96.79 41.74, -96.8 41.74, -96.81 41.74, -96.82 41.74, -96.83 41.74, -96.84 41.74, -96.85 41.74, -96.86 41.74, -96.87 41.74, -96.88 41.74, -96.89 41.74, -96.9 41.74, -96.91 41.74, -96.92 41.74, -96.93 41.74, -96.94 41.74, -96.95 41.74, -96.96 41.74, -96.97 41.74, -96.98 41.74, -96.99 41.74, -97 41.74, -97.01 41.74, -97.02 41.74, -97.02 41.75, -97.02 41.76, -97.02 41.77, -97.02 41.78, -97.02 41.79, -97.02 41.8, -97.02 41.81, -97.02 41.82, -97.02 41.83, -97.02 41.84, -97.02 41.85, -97.02 41.86, -97.02 41.87, -97.02 41.88, -97.02 41.89, -97.02 41.9, -97.02 41.91, -97.02 41.92, -97.02 41.93, -97.02 41.94, -97.02 41.95, -97.02 41.96, -97.02 41.97, -97.02 41.98, -97.02 41.99, -97.02 42, -97.02 42.01))" + + def test_st_bestsrid(self): + polygon_from_wkt = self.spark.read.format("csv"). \ + option("delimiter", "\t"). \ + option("header", "false"). \ + load(mixed_wkt_geometry_input_location) + + polygon_from_wkt.createOrReplaceTempView("polgontable") + polygon_from_wkt.show() + + polygon_df = self.spark.sql("select ST_GeomFromWKT(polygontable._c0) as countyshape from polygontable") + polygon_df.createOrReplaceTempView("polygondf") + polygon_df.show() + function_df = self.spark.sql("select ST_BestSRID(polygondf.countyshape) from polygondf") + function_df.show() + actual = function_df.take(1)[0][0] + assert actual == 3395 + def test_st_bestsrid(self): polygon_from_wkt = self.spark.read.format("csv"). \ option("delimiter", "\t"). \ diff --git a/python/tests/streaming/spark/test_constructor_functions.py b/python/tests/streaming/spark/test_constructor_functions.py index e72a4a5431..1be6d961b4 100644 --- a/python/tests/streaming/spark/test_constructor_functions.py +++ b/python/tests/streaming/spark/test_constructor_functions.py @@ -46,6 +46,11 @@ .with_arguments(["ST_GeomFromText('POINT (21 52)')", "1.0"]) .with_expected_result(3.1214451522580533) .with_transform("ST_AREA")), + (SuiteContainer.empty() + .with_function_name("ST_Buffer") + .with_arguments(["ST_GeomFromText('POINT (21 52)')", "100000", "true"]) + .with_expected_result(4.088135158017784) + .with_transform("ST_AREA")), (SuiteContainer.empty() .with_function_name("ST_Distance") .with_arguments(["ST_GeomFromText('POINT (21 52)')", "ST_GeomFromText('POINT (21 53)')"]) 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 901e7db00c..63c54ae993 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 @@ -190,6 +190,11 @@ public void test_ST_Buffer() { "select sedona.ST_AsText(sedona.ST_Buffer(sedona.ST_GeomFromText('POINT (0 1)'), 1))", "POLYGON ((1 1, 0.9807852804032304 0.8049096779838718, 0.9238795325112867 0.6173165676349102, 0.8314696123025452 0.4444297669803978, 0.7071067811865476 0.2928932188134525, 0.5555702330196023 0.1685303876974548, 0.3826834323650898 0.0761204674887133, 0.1950903220161283 0.0192147195967696, 0.0000000000000001 0, -0.1950903220161282 0.0192147195967696, -0.3826834323650897 0.0761204674887133, -0.555570233019602 0.1685303876974547, -0.7071067811865475 0.2928932188134524, -0.8314696123025453 0.4444297669803978, -0.9238795325112867 0.6173165676349102, -0.9807852804032304 0.8049096779838714, -1 0.9999999999999999, -0.9807852804032304 1.1950903220161284, -0.9238795325112868 1.3826834323650896, -0.8314696123025455 1.555570233019602, -0.7071067811865477 1.7071067811865475, -0.5555702330196022 1.8314696123025453, -0.3826834323650903 1.9238795325112865, -0.1950903220161287 1.9807852804032304, -0.0000000000000002 2, 0.1950903220161283 1.9807852804032304, 0.38268343236509 1.9238795325112865, 0.5555702330196018 1.8314696123025453, 0.7071067811865474 1.7071067811865477, 0.8314696123025452 1.5555702330196022, 0.9238795325112865 1.3826834323650905, 0.9807852804032303 1.1950903220161286, 1 1))" ); + registerUDF("ST_Buffer", byte[].class, double.class, boolean.class); + verifySqlSingleRes( + "select ST_AsText(ST_ReducePrecision(ST_GeomFromText(sedona.ST_AsText(sedona.ST_Buffer(sedona.ST_GeomFromText('LINESTRING(0.05 15, -0.05 15)'), 10000, true))), 8))", + "POLYGON((-0.06809794 14.91143929,-0.0855181 14.91657277,-0.10157458 14.92491089,-0.11565095 14.93613348,-0.12720661 14.94980963,-0.13579747 14.96541421,-0.14109307 14.98234798,-0.14288927 14.99996056,-0.14111623 15.01757534,-0.13584121 15.03451549,-0.12726608 15.05012991,-0.11571978 15.0638183,-0.10164567 15.07505424,-0.08558461 15.0834055,-0.06815417 15.08855069,-0.05002481 15.09029174,0.0500174 15.09029949,0.06814888 15.08856105,0.08558242 15.08341775,0.10164727 15.07506734,0.11572543 15.06383098,0.12727556 15.05014087,0.13585382 15.03452355,0.1411309 15.01757961,0.14290466 14.99996055,0.14110774 14.98234371,0.13581008 14.96540615,0.12721607 14.94979867,0.11565658 14.93612079,0.10157617 14.92489779,0.08551591 14.91656051,0.06809265 14.91142893,0.04997533 14.90969989,-0.04998274 14.90970765,-0.06809794 14.91143929))" + ); } @Test 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 64104bd014..90517b7f42 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 @@ -213,6 +213,11 @@ public void test_ST_Buffer() { "select ST_AsText(sedona.ST_Buffer(ST_GeometryFromWKT('POINT (0 1)'), 1))", "POLYGON((1 1,0.9807852804 0.804909678,0.9238795325 0.6173165676,0.8314696123 0.444429767,0.7071067812 0.2928932188,0.555570233 0.1685303877,0.3826834324 0.07612046749,0.195090322 0.0192147196,6.123233996e-17 0,-0.195090322 0.0192147196,-0.3826834324 0.07612046749,-0.555570233 0.1685303877,-0.7071067812 0.2928932188,-0.8314696123 0.444429767,-0.9238795325 0.6173165676,-0.9807852804 0.804909678,-1 1,-0.9807852804 1.195090322,-0.9238795325 1.382683432,-0.8314696123 1.555570233,-0.7071067812 1.707106781,-0.555570233 1.831469612,-0.3826834324 1.923879533,-0.195090322 1.98078528,-1.836970199e-16 2,0.195090322 1.98078528,0.3826834324 1.923879533,0.555570233 1.831469612,0.7071067812 1.707106781,0.8314696123 1.555570233,0.9238795325 1.382683432,0.9807852804 1.195090322,1 1))" ); + registerUDFV2("ST_Buffer", String.class, double.class, boolean.class); + verifySqlSingleRes( + "select ST_AsText(ST_ReducePrecision(sedona.ST_Buffer(ST_GeometryFromWKT('LINESTRING(0.05 15, -0.05 15)'), 10000, true), 8))", + "POLYGON((-0.06809794 14.91143929,-0.0855181 14.91657277,-0.10157458 14.92491089,-0.11565095 14.93613348,-0.12720661 14.94980963,-0.13579747 14.96541421,-0.14109307 14.98234798,-0.14288927 14.99996056,-0.14111623 15.01757534,-0.13584121 15.03451549,-0.12726608 15.05012991,-0.11571978 15.0638183,-0.10164567 15.07505424,-0.08558461 15.0834055,-0.06815417 15.08855069,-0.05002481 15.09029174,0.0500174 15.09029949,0.06814888 15.08856105,0.08558242 15.08341775,0.10164727 15.07506734,0.11572543 15.06383098,0.12727556 15.05014087,0.13585382 15.03452355,0.1411309 15.01757961,0.14290466 14.99996055,0.14110774 14.98234371,0.13581008 14.96540615,0.12721607 14.94979867,0.11565658 14.93612079,0.10157617 14.92489779,0.08551591 14.91656051,0.06809265 14.91142893,0.04997533 14.90969989,-0.04998274 14.90970765,-0.06809794 14.91143929))" + ); } @Test public void test_ST_BuildArea() { 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 d7b853306b..10685687e2 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 @@ -159,7 +159,7 @@ public static byte[] ST_BoundingDiagonal(byte[] geometry) { } @UDFAnnotations.ParamMeta(argNames = {"geometry", "radius"}) - public static byte[] ST_Buffer(byte[] geometry, double radius) { + public static byte[] ST_Buffer(byte[] geometry, double radius) throws IllegalArgumentException { return GeometrySerde.serialize( Functions.buffer( GeometrySerde.deserialize(geometry), @@ -168,6 +168,29 @@ public static byte[] ST_Buffer(byte[] geometry, double radius) { ); } + @UDFAnnotations.ParamMeta(argNames = {"geometry", "radius", "useSpheroid"}) + public static byte[] ST_Buffer(byte[] geometry, double radius, boolean useSpheroid) throws IllegalArgumentException { + return GeometrySerde.serialize( + Functions.buffer( + GeometrySerde.deserialize(geometry), + radius, + useSpheroid + ) + ); + } + + @UDFAnnotations.ParamMeta(argNames = {"geometry", "radius", "useSpheroid", "parameters"}) + public static byte[] ST_Buffer(byte[] geometry, double radius, boolean useSpheroid, String parameters) throws IllegalArgumentException { + return GeometrySerde.serialize( + Functions.buffer( + GeometrySerde.deserialize(geometry), + radius, + useSpheroid, + parameters + ) + ); + } + @UDFAnnotations.ParamMeta(argNames = {"geometry"}) public static int ST_BestSRID(byte[] geometry) { return Functions.bestSRID( 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 a13deef46e..be6c09c6bc 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 @@ -188,7 +188,7 @@ public static String ST_ShiftLongitude(String geometry) { } @UDFAnnotations.ParamMeta(argNames = {"geometry", "radius"}, argTypes = {"Geometry", "double"}, returnTypes = "Geometry") - public static String ST_Buffer(String geometry, double radius) { + public static String ST_Buffer(String geometry, double radius) throws IllegalArgumentException { return GeometrySerde.serGeoJson( Functions.buffer( GeometrySerde.deserGeoJson(geometry), @@ -197,6 +197,29 @@ public static String ST_Buffer(String geometry, double radius) { ); } + @UDFAnnotations.ParamMeta(argNames = {"geometry", "radius", "useSpheroid"}, argTypes = {"Geometry", "double", "boolean"}, returnTypes = "Geometry") + public static String ST_Buffer(String geometry, double radius, boolean useSpheroid) throws IllegalArgumentException { + return GeometrySerde.serGeoJson( + Functions.buffer( + GeometrySerde.deserGeoJson(geometry), + radius, + useSpheroid + ) + ); + } + + @UDFAnnotations.ParamMeta(argNames = {"geometry", "radius", "useSpheroid", "parameters"}, argTypes = {"Geometry", "double", "boolean", "String"}, returnTypes = "Geometry") + public static String ST_Buffer(String geometry, double radius, boolean useSpheroid, String parameters) throws IllegalArgumentException { + return GeometrySerde.serGeoJson( + Functions.buffer( + GeometrySerde.deserGeoJson(geometry), + radius, + useSpheroid, + parameters + ) + ); + } + @UDFAnnotations.ParamMeta(argNames = {"geometry"}, argTypes = {"Geometry"}, returnTypes = "Geometry") public static String ST_BuildArea(String geometry) { return GeometrySerde.serGeoJson( 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 73006d5a05..50dab461e2 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 @@ -161,7 +161,7 @@ case class ST_NDims(inputExpressions: Seq[Expression]) * @param inputExpressions */ case class ST_Buffer(inputExpressions: Seq[Expression]) - extends InferredExpression(inferrableFunction2(Functions.buffer), inferrableFunction3(Functions.buffer)) { + extends InferredExpression(inferrableFunction2(Functions.buffer), inferrableFunction3(Functions.buffer), inferrableFunction4(Functions.buffer)) { protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]) = { copy(inputExpressions = newChildren) 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 bf9aebc8f3..b0ef539d8e 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 @@ -66,8 +66,10 @@ object st_functions extends DataFrameAPI { def ST_Buffer(geometry: Column, buffer: Column): Column = wrapExpression[ST_Buffer](geometry, buffer) def ST_Buffer(geometry: String, buffer: Double): Column = wrapExpression[ST_Buffer](geometry, buffer) - def ST_Buffer(geometry: Column, buffer: Column, parameters: Column): Column = wrapExpression[ST_Buffer](geometry, buffer, parameters) - def ST_Buffer(geometry: String, buffer: Double, parameters: String): Column = wrapExpression[ST_Buffer](geometry, buffer, parameters) + def ST_Buffer(geometry: Column, buffer: Column, useSpheroid: Column): Column = wrapExpression[ST_Buffer](geometry, buffer, useSpheroid) + def ST_Buffer(geometry: String, buffer: Double, useSpheroid: Boolean): Column = wrapExpression[ST_Buffer](geometry, buffer, useSpheroid) + def ST_Buffer(geometry: Column, buffer: Column, useSpheroid: Column, parameters: Column): Column = wrapExpression[ST_Buffer](geometry, buffer, useSpheroid, parameters) + def ST_Buffer(geometry: String, buffer: Double, useSpheroid: Boolean, parameters: String): Column = wrapExpression[ST_Buffer](geometry, buffer, useSpheroid, parameters) def ST_BestSRID(geometry: Column): Column = wrapExpression[ST_BestSRID](geometry) def ST_BestSRID(geometry: String): Column = wrapExpression[ST_BestSRID](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 bf19c20059..e5006261c8 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 @@ -198,22 +198,34 @@ class dataFrameAPITestScala extends TestBaseScala { assertEquals(expected, actual) var linestringDf = sparkSession.sql("SELECT ST_GeomFromWKT('LINESTRING(0 0, 50 70, 100 100)') AS geom, 'side=left' as params") - var dfLine = linestringDf.select(ST_Buffer("geom", 10, "params").as("geom")).selectExpr("ST_ReducePrecision(geom, 2)") + var dfLine = linestringDf.select(ST_Buffer("geom", 10, false, "params").as("geom")).selectExpr("ST_ReducePrecision(geom, 2)") actual = dfLine.take(1)(0).get(0).asInstanceOf[Geometry].toText() expected = "POLYGON ((50 70, 0 0, -8.14 5.81, 41.86 75.81, 43.22 77.35, 44.86 78.57, 94.86 108.57, 100 100, 50 70))" assertEquals(expected, actual) linestringDf = sparkSession.sql("SELECT ST_GeomFromWKT('LINESTRING(0 0, 50 70, 70 -3)') AS geom, 'endcap=square' AS params") - dfLine = linestringDf.select(ST_Buffer("geom", 10, "params").as("geom")).selectExpr("ST_ReducePrecision(geom, 2)") + dfLine = linestringDf.select(ST_Buffer("geom", 10, false, "params").as("geom")).selectExpr("ST_ReducePrecision(geom, 2)") actual = dfLine.take(1)(0).get(0).asInstanceOf[Geometry].toText() expected = "POLYGON ((43.22 77.35, 44.85 78.57, 46.7 79.44, 48.69 79.91, 50.74 79.97, 52.75 79.61, 54.65 78.85, 56.36 77.72, 57.79 76.27, 58.91 74.55, 59.64 72.64, 79.64 -0.36, 82.29 -10, 63 -15.29, 45.91 47.07, 8.14 -5.81, 2.32 -13.95, -13.95 -2.32, 41.86 75.81, 43.22 77.35))" assertEquals(expected, actual) - val pointDf = sparkSession.sql("SELECT ST_Point(100, 90) AS geom, 'quad_segs=4' as params") - val dfPoint = pointDf.select(ST_Buffer("geom", 200, "params").as("geom")).selectExpr("ST_ReducePrecision(geom, 2)") + linestringDf = sparkSession.sql("SELECT ST_GeomFromWKT('LINESTRING(0 0, 50 70, 70 -3)') AS geom, 'endcap=square' AS params") + dfLine = linestringDf.select(ST_Buffer("geom", 10, true, "params").as("geom")).selectExpr("ST_ReducePrecision(geom, 4)") + actual = dfLine.take(1)(0).get(0).asInstanceOf[Geometry].toText() + expected = "POLYGON ((50 70, 50.0001 70, 70.0001 -3, 70.0001 -3.0001, 69.9999 -3.0001, 50 69.9999, 0.0001 0, 0 -0.0001, -0.0001 0, 49.9999 70, 50 70))" + assertEquals(expected, actual) + + var pointDf = sparkSession.sql("SELECT ST_Point(100, 90) AS geom, 'quad_segs=4' as params") + var dfPoint = pointDf.select(ST_Buffer("geom", 200, false, "params").as("geom")).selectExpr("ST_ReducePrecision(geom, 2)") actual = dfPoint.take(1)(0).get(0).asInstanceOf[Geometry].toText() expected = "POLYGON ((284.78 13.46, 241.42 -51.42, 176.54 -94.78, 100 -110, 23.46 -94.78, -41.42 -51.42, -84.78 13.46, -100 90, -84.78 166.54, -41.42 231.42, 23.46 274.78, 100 290, 176.54 274.78, 241.42 231.42, 284.78 166.54, 300 90, 284.78 13.46))" assertEquals(expected, actual) + + pointDf = sparkSession.sql("SELECT ST_Point(-180, 60) AS geom, 'quad_segs=4' as params") + dfPoint = pointDf.select(ST_Buffer("geom", 200, true, "params").as("geom")).selectExpr("ST_ReducePrecision(geom, 6)") + actual = dfPoint.take(1)(0).get(0).asInstanceOf[Geometry].toText() + expected = "POLYGON ((-179.99663 60.000611, -179.997353 60.001211, -179.998479 60.001626, -179.999837 60.001793, 179.99878 60.001688, 179.997583 60.001326, 179.996754 60.000761, 179.996419 60.000081, 179.99663 59.999389, 179.997353 59.998789, 179.99848 59.998374, 179.999837 59.998207, -179.99878 59.998312, -179.997583 59.998674, -179.996754 59.999238, -179.996419 59.999919, -179.99663 60.000611))" + assertEquals(expected, actual) } it("Passed ST_BestSRID") { 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 87fea2a25f..0efa67ee4a 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 @@ -86,6 +86,19 @@ class functionTestScala extends TestBaseScala with Matchers with GeometrySample assert(functionDf.count() > 0); } + it("Passed ST_Buffer Spheroid") { + val polygonWktDf = sparkSession.read.format("csv").option("delimiter", "\t").option("header", "false").load(mixedWktGeometryInputLocation) + polygonWktDf.createOrReplaceTempView("polygontable") + val polygonDf = sparkSession.sql("select ST_GeomFromWKT(polygontable._c0) as countyshape from polygontable") + polygonDf.createOrReplaceTempView("polygondf") + + var functionDf = sparkSession.sql("select ST_Buffer(polygondf.countyshape, 1, true) from polygondf") + assert(functionDf.count() > 0); + + functionDf = sparkSession.sql("select ST_Buffer(polygondf.countyshape, 1, true, 'quad_segs=2') from polygondf") + assert(functionDf.count() > 0); + } + it("Passed ST_BestSRID") { val polygonWktDf = sparkSession.read.format("csv").option("delimiter", "\t").option("header", "false").load(mixedWktGeometryInputLocation) polygonWktDf.createOrReplaceTempView("polygontable")