From d756a9ab870f043946147672158d3b2c9daf8935 Mon Sep 17 00:00:00 2001 From: Furqaan Khan <46216254+furqaankhan@users.noreply.github.com> Date: Fri, 3 Nov 2023 04:27:30 +0530 Subject: [PATCH] [SEDONA-413] Add buffer parameters to ST_Buffer (#1066) --- .../org/apache/sedona/common/Functions.java | 100 +++++++++++++++++- .../apache/sedona/common/FunctionsTest.java | 23 ++++ docs/api/flink/Function.md | 40 ++++++- docs/api/sql/Function.md | 40 ++++++- docs/image/linestring-left-side.png | Bin 0 -> 18154 bytes docs/image/linestring-og.png | Bin 0 -> 7310 bytes docs/image/point-buffer-quad-2.png | Bin 0 -> 14297 bytes docs/image/point-buffer-quad-8.png | Bin 0 -> 16128 bytes .../sedona/flink/expressions/Functions.java | 7 ++ .../org/apache/sedona/flink/FunctionTest.java | 15 ++- python/sedona/sql/st_functions.py | 9 +- python/tests/sql/test_function.py | 9 +- .../sedona_sql/expressions/Functions.scala | 2 +- .../sedona_sql/expressions/st_functions.scala | 2 + .../sedona/sql/dataFrameAPITestScala.scala | 24 ++++- 15 files changed, 250 insertions(+), 21 deletions(-) create mode 100644 docs/image/linestring-left-side.png create mode 100644 docs/image/linestring-og.png create mode 100644 docs/image/point-buffer-quad-2.png create mode 100644 docs/image/point-buffer-quad-8.png 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 a5ce8585b1..bd4731e449 100644 --- a/common/src/main/java/org/apache/sedona/common/Functions.java +++ b/common/src/main/java/org/apache/sedona/common/Functions.java @@ -43,6 +43,8 @@ import org.locationtech.jts.io.gml2.GMLWriter; import org.locationtech.jts.io.kml.KMLWriter; import org.locationtech.jts.linearref.LengthIndexedLine; +import org.locationtech.jts.operation.buffer.BufferOp; +import org.locationtech.jts.operation.buffer.BufferParameters; import org.locationtech.jts.operation.distance.DistanceOp; import org.locationtech.jts.operation.distance3d.Distance3DOp; import org.locationtech.jts.operation.linemerge.LineMerger; @@ -92,7 +94,103 @@ public static Geometry boundary(Geometry geometry) { } public static Geometry buffer(Geometry geometry, double radius) { - return geometry.buffer(radius); + return buffer(geometry, radius, ""); + } + + public static Geometry buffer(Geometry geometry, double radius, String params) { + if (params.isEmpty()) { + return BufferOp.bufferOp(geometry, radius); + } + + BufferParameters 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); + } + + private static BufferParameters parseBufferParams(String params) { + + String[] listBufferParameters = {"quad_segs", "endcap", "join", "mitre_limit", "miter_limit", "side"}; + String[] endcapOptions = {"round", "flat", "butt", "square"}; + String[] joinOptions = {"round", "mitre", "miter", "bevel"}; + String[] sideOptions = {"both", "left", "right"}; + + BufferParameters bufferParameters = new BufferParameters(); + String[] listParams = params.split(" "); + + for (String param: listParams) { + String[] singleParam = param.split("="); + + if (singleParam.length != 2) { + throw new IllegalArgumentException(String.format("%s is not the valid format. The valid format is key=value, for example `endcap=butt quad_segs=4`.", param)); + } + + // Set quadrant segment + if (singleParam[0].equalsIgnoreCase(listBufferParameters[0])) { + try { + bufferParameters.setQuadrantSegments(Integer.parseInt(singleParam[1])); + } catch (NumberFormatException e) { + throw new IllegalArgumentException(String.format("%1$s is not an integer. Quadrant segment should be an integer.", singleParam[1])); + } + } + // Set end cap style + else if (singleParam[0].equalsIgnoreCase(listBufferParameters[1])) { + if (singleParam[1].equalsIgnoreCase(endcapOptions[0])) { + bufferParameters.setEndCapStyle(BufferParameters.CAP_ROUND); + } else if (singleParam[1].equalsIgnoreCase(endcapOptions[1]) || singleParam[1].equalsIgnoreCase(endcapOptions[2])) { + bufferParameters.setEndCapStyle(BufferParameters.CAP_FLAT); + } else if (singleParam[1].equalsIgnoreCase(endcapOptions[3])) { + bufferParameters.setEndCapStyle(BufferParameters.CAP_SQUARE); + } else { + throw new IllegalArgumentException(String.format("%s is not a valid option. Accepted options are %s.", singleParam[1], Arrays.toString(endcapOptions))); + } + } + // Set join style + else if (singleParam[0].equalsIgnoreCase(listBufferParameters[2])) { + if (singleParam[1].equalsIgnoreCase(joinOptions[0])) { + bufferParameters.setJoinStyle(BufferParameters.JOIN_ROUND); + } else if (singleParam[1].equalsIgnoreCase(joinOptions[1]) || singleParam[1].equalsIgnoreCase(joinOptions[2])) { + bufferParameters.setJoinStyle(BufferParameters.JOIN_MITRE); + } else if (singleParam[1].equalsIgnoreCase(joinOptions[3])) { + bufferParameters.setJoinStyle(BufferParameters.JOIN_BEVEL); + } else { + throw new IllegalArgumentException(String.format("%s is not a valid option. Accepted options are %s", singleParam[1], Arrays.toString(joinOptions))); + } + } + // Set mitre ratio limit + else if (singleParam[0].equalsIgnoreCase(listBufferParameters[3]) || singleParam[0].equalsIgnoreCase(listBufferParameters[4])) { + try { + bufferParameters.setMitreLimit(Double.parseDouble(singleParam[1])); + } catch (NumberFormatException e) { + throw new IllegalArgumentException(String.format("%1$s is not a double. Mitre limit should be a double.", singleParam[1])); + } + continue; + } + // Set side to add buffer + else if (singleParam[0].equalsIgnoreCase(listBufferParameters[5])) { + if (singleParam[1].equalsIgnoreCase(sideOptions[0])) { + // It defaults to square end cap style when side is specified + bufferParameters.setEndCapStyle(BufferParameters.CAP_SQUARE); + continue; + } else if (singleParam[1].equalsIgnoreCase(sideOptions[1]) || singleParam[1].equalsIgnoreCase(sideOptions[2])) { + bufferParameters.setSingleSided(true); + } else { + throw new IllegalArgumentException(String.format("%s is not a valid option. Accepted options are %s ", singleParam[1], Arrays.toString(sideOptions))); + } + } + // everything else + else { + throw new IllegalArgumentException(String.format("%s is not a valid style parameter. Accepted style parameters are %s.", singleParam[0], Arrays.toString(listBufferParameters))); + } + } + return bufferParameters; } public static Geometry envelope(Geometry geometry) { 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 a6e355e1d4..e4a72fa8b4 100644 --- a/common/src/test/java/org/apache/sedona/common/FunctionsTest.java +++ b/common/src/test/java/org/apache/sedona/common/FunctionsTest.java @@ -1164,6 +1164,29 @@ public void nRingsMultiPolygonMixed() throws Exception { assertEquals(expected, actual); } + @Test + public void testBuffer() { + Polygon polygon = GEOMETRY_FACTORY.createPolygon(coordArray(50, 50, 50, 150, 150, 150, 150, 50, 50, 50)); + String actual = Functions.asWKT(Functions.buffer(polygon, 15)); + String expected = "POLYGON ((50 35, 47.07364516975807 35.288220793951545, 44.25974851452364 36.1418070123307, 41.666446504705966 37.52795581546182, 39.39339828220179 39.39339828220179, 37.527955815461816 41.66644650470597, 36.141807012330695 44.25974851452366, 35.288220793951545 47.07364516975807, 35 50, 35 150, 35.288220793951545 152.92635483024193, 36.1418070123307 155.74025148547634, 37.52795581546182 158.33355349529404, 39.39339828220179 160.6066017177982, 41.66644650470597 162.4720441845382, 44.25974851452365 163.8581929876693, 47.07364516975808 164.71177920604845, 50 165, 150 165, 152.92635483024193 164.71177920604845, 155.74025148547634 163.8581929876693, 158.33355349529404 162.4720441845382, 160.6066017177982 160.6066017177982, 162.4720441845382 158.33355349529404, 163.8581929876693 155.74025148547634, 164.71177920604845 152.92635483024193, 165 150, 165 50, 164.71177920604845 47.07364516975807, 163.8581929876693 44.25974851452365, 162.4720441845382 41.666446504705966, 160.6066017177982 39.39339828220179, 158.33355349529404 37.52795581546182, 155.74025148547634 36.1418070123307, 152.92635483024193 35.288220793951545, 150 35, 50 35))"; + assertEquals(expected, actual); + + LineString lineString = GEOMETRY_FACTORY.createLineString(coordArray(0, 0, 50, 70, 100, 100)); + actual = Functions.asWKT(Functions.buffer(lineString, 10, "side=left")); + expected = "POLYGON ((100 100, 50 70, 0 0, -8.137334712067348 5.812381937190963, 41.86266528793265 75.81238193719096, 43.21673095875923 77.34760240582902, 44.855042445724735 78.57492925712545, 94.85504244572473 108.57492925712545, 100 100))"; + assertEquals(expected, actual); + + lineString = GEOMETRY_FACTORY.createLineString(coordArray(0, 0, 50, 70, 70, -3)); + actual = Functions.asWKT(Functions.buffer(lineString, 10, "endcap=square")); + expected = "POLYGON ((41.86266528793265 75.81238193719096, 43.21555008457904 77.3465120530184, 44.85228625762473 78.57327494173381, 46.70439518001618 79.44134465372912, 48.69438734657371 79.914402432785, 50.73900442057982 79.9726562392556, 52.75270263976913 79.6136688198111, 54.65123184115194 78.8524596785218, 56.355160363552315 77.72087668296376, 57.79319835113832 76.26626359641972, 58.90518041582699 74.54947928466231, 59.64458286836891 72.642351470786, 79.64458286836891 -0.3576485292139977, 82.28693433915491 -10.002231397582907, 62.997768602417096 -15.28693433915491, 45.912786454208465 47.07325050180659, 8.137334712067348 -5.812381937190963, 2.324952774876386 -13.949716649258315, -13.94971664925831 -2.3249527748763885, 41.86266528793265 75.81238193719096))"; + assertEquals(expected, actual); + + Point point = GEOMETRY_FACTORY.createPoint(new Coordinate(100, 90)); + actual = Functions.asWKT(Functions.buffer(point, 10, "quad_segs=2")); + expected = "POLYGON ((110 90, 107.07106781186548 82.92893218813452, 100 80, 92.92893218813452 82.92893218813452, 90 90, 92.92893218813452 97.07106781186548, 100 100, 107.07106781186548 97.07106781186548, 110 90))"; + assertEquals(expected, actual); + } + @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 81d82542aa..6bcd213e1d 100644 --- a/docs/api/flink/Function.md +++ b/docs/api/flink/Function.md @@ -530,21 +530,51 @@ Output: `LINESTRING Z(-1 -1 0, 10 5 5)` Introduction: Returns a geometry/geography that represents all points whose distance from this Geometry/geography is less than or equal to distance. -Format: `ST_Buffer (A: Geometry, buffer: Double)` +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. -Since: `v1.2.0` +- `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, bufferStyleParameters: String [Optional]) +``` + +Since: `v1.5.1` Example: ```sql -SELECT ST_Buffer(ST_GeomFromWKT("POINT(0 0)"), 1) +SELECT ST_Buffer(ST_GeomFromWKT('POINT(0 0)'), 10) +SELECT ST_Buffer(ST_GeomFromWKT('POINT(0 0)'), 10, 'quad_segs=2') ``` Output: +Point buffer with 8 quadrant segments +Point buffer with 2 quadrant segments + +8 Segments   2 Segments + +Example: + +```sql +SELECT ST_Buffer(ST_GeomFromWKT('LINESTRING(0 0, 50 70, 100 100)'), 10, 'side=left') ``` -POLYGON ((1 0, 0.9807852804032304 -0.1950903220161282, 0.9238795325112867 -0.3826834323650898, 0.8314696123025452 -0.5555702330196022, 0.7071067811865476 -0.7071067811865475, 0.5555702330196023 -0.8314696123025452, 0.3826834323650898 -0.9238795325112867, 0.1950903220161283 -0.9807852804032304, 0.0000000000000001 -1, -0.1950903220161282 -0.9807852804032304, -0.3826834323650897 -0.9238795325112867, -0.555570233019602 -0.8314696123025453, -0.7071067811865475 -0.7071067811865476, -0.8314696123025453 -0.5555702330196022, -0.9238795325112867 -0.3826834323650899, -0.9807852804032304 -0.1950903220161286, -1 -0.0000000000000001, -0.9807852804032304 0.1950903220161284, -0.9238795325112868 0.3826834323650897, -0.8314696123025455 0.555570233019602, -0.7071067811865477 0.7071067811865475, -0.5555702330196022 0.8314696123025452, -0.3826834323650903 0.9238795325112865, -0.1950903220161287 0.9807852804032303, -0.0000000000000002 1, 0.1950903220161283 0.9807852804032304, 0.38268343236509 0.9238795325112866, 0.5555702330196018 0.8314696123025455, 0.7071067811865474 0.7071067811865477, 0.8314696123025452 0.5555702330196022, 0.9238795325112865 0.3826834323650904, 0.9807852804032303 0.1950903220161287, 1 0)) -``` + +Output: + +Original Linestring +Original Linestring with buffer on the left side + +Original Linestring   Left side buffed Linestring ## ST_BuildArea diff --git a/docs/api/sql/Function.md b/docs/api/sql/Function.md index d41eeaf6a5..ec1117a371 100644 --- a/docs/api/sql/Function.md +++ b/docs/api/sql/Function.md @@ -529,21 +529,51 @@ Output: `LINESTRING Z(-1 -1 0, 10 5 5)` Introduction: Returns a geometry/geography that represents all points whose distance from this Geometry/geography is less than or equal to distance. -Format: `ST_Buffer (A: Geometry, buffer: Double)` +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. -Since: `v1.0.0` +- `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, bufferStyleParameters: String [Optional]) +``` + +Since: `v1.5.1` Spark SQL Example: ```sql -SELECT ST_Buffer(ST_GeomFromWKT("POINT(0 0)"), 1) +SELECT ST_Buffer(ST_GeomFromWKT('POINT(0 0)'), 10) +SELECT ST_Buffer(ST_GeomFromWKT('POINT(0 0)'), 10, 'quad_segs=2') ``` Output: +Point buffer with 8 quadrant segments +Point buffer with 2 quadrant segments + +8 Segments   2 Segments + +Spark SQL Example: + +```sql +SELECT ST_Buffer(ST_GeomFromWKT('LINESTRING(0 0, 50 70, 100 100)'), 10, 'side=left') ``` -POLYGON ((1 0, 0.9807852804032304 -0.1950903220161282, 0.9238795325112867 -0.3826834323650898, 0.8314696123025452 -0.5555702330196022, 0.7071067811865476 -0.7071067811865475, 0.5555702330196023 -0.8314696123025452, 0.3826834323650898 -0.9238795325112867, 0.1950903220161283 -0.9807852804032304, 0.0000000000000001 -1, -0.1950903220161282 -0.9807852804032304, -0.3826834323650897 -0.9238795325112867, -0.555570233019602 -0.8314696123025453, -0.7071067811865475 -0.7071067811865476, -0.8314696123025453 -0.5555702330196022, -0.9238795325112867 -0.3826834323650899, -0.9807852804032304 -0.1950903220161286, -1 -0.0000000000000001, -0.9807852804032304 0.1950903220161284, -0.9238795325112868 0.3826834323650897, -0.8314696123025455 0.555570233019602, -0.7071067811865477 0.7071067811865475, -0.5555702330196022 0.8314696123025452, -0.3826834323650903 0.9238795325112865, -0.1950903220161287 0.9807852804032303, -0.0000000000000002 1, 0.1950903220161283 0.9807852804032304, 0.38268343236509 0.9238795325112866, 0.5555702330196018 0.8314696123025455, 0.7071067811865474 0.7071067811865477, 0.8314696123025452 0.5555702330196022, 0.9238795325112865 0.3826834323650904, 0.9807852804032303 0.1950903220161287, 1 0)) -``` + +Output: + +Original Linestring +Original Linestring with buffer on the left side + +Original Linestring   Left side buffed Linestring ## ST_BuildArea diff --git a/docs/image/linestring-left-side.png b/docs/image/linestring-left-side.png new file mode 100644 index 0000000000000000000000000000000000000000..160eb07b9cd415a534563476dac551b5ef40fd07 GIT binary patch literal 18154 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 literal 0 HcmV?d00001 diff --git a/docs/image/linestring-og.png b/docs/image/linestring-og.png new file mode 100644 index 0000000000000000000000000000000000000000..5f6f9c264fc898c1adb23f4db21d6ebf1d96eadb GIT binary patch literal 7310 zcmZ`;3s{V4-=CQZGrN=h+6HTidHK9;p#z!iNH&zHF&QMGLD?ElXVpr~?lN{;Du>eA zYGX=K5t=EHFmG9}SXRfG!U$0vt{4gFD}IjxU2@Xof#tBMk6Fw`XrF{xMuX+j};HaU$iTmr^5qW=8vZe91U=&8!2z zH-$+2V*I=pI;{;StflM1*92L|6A>82V7SCP;a4IkMk<~qU8dy&9BJSs>aw6?XjoiLLl5C~kN)&)EH&Y3qj9iLn$d=(QD;bdbI7Z+z8 zH{CisYQ2q}qobpZ?KGQd)23p?)ab9nVx;j?!=fkBFM0ExIYH5Dqe3ELLc+rYXdb{L1Q~lnZI%C?qQ{VaO!>LQ6gQDES2`m~jbMe}!AT0h>c+BAB zcfTB*=wd^j{T(c5psA-odM3xkW{9E92mJ@|~sd)jPKfSW^L041p<@SANJFnet-Cp|mlJ3r}c5Qsof3DxY75Gb^W70rG z{?nHGx|kzR4xI0-yVKgy-Pm}ywW>jXu}NQk!7A1|5AHN?R{nSL$7@=X6}0ZgZlC5 zn{AmF{-~_Gd${IdLeYP=mh{X?eHC=*FZ&+`rhot1pzb<6@tN=ZIkP44+OF1)lPedG zQHVqCebU@vW|C+>`;!TCMjuW6yn7%ix9Ie>*F6%;flKAvo?SXRJubWUc?)2B|L3L6 zj+=Z7(N)Gv&Gykg*{6d>1XgV7zGt&FgEhuFYu54&%*@l9BM0pJZZI7xq7nz}1D@

x=^(gGCnY#|{`Y?dqKt5U753c4Yz4J>=HI% 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 literal 0 HcmV?d00001 diff --git a/docs/image/point-buffer-quad-2.png b/docs/image/point-buffer-quad-2.png new file mode 100644 index 0000000000000000000000000000000000000000..52d0b628224066082470675dbd6d43fcafb99204 GIT binary patch literal 14297 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 literal 0 HcmV?d00001 diff --git a/docs/image/point-buffer-quad-8.png b/docs/image/point-buffer-quad-8.png new file mode 100644 index 0000000000000000000000000000000000000000..ce16d1e9b65ee9f4c405dc822b571f0478de250f GIT binary patch literal 16128 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 s Column: @validate_argument_types -def ST_Buffer(geometry: ColumnOrName, buffer: ColumnOrNameOrNumber) -> Column: +def ST_Buffer(geometry: ColumnOrName, buffer: ColumnOrNameOrNumber, 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. @@ -326,7 +326,12 @@ def ST_Buffer(geometry: ColumnOrName, buffer: ColumnOrNameOrNumber) -> Column: :return: Buffered geometry as a geometry column. :rtype: Column """ - return _call_st_function("ST_Buffer", (geometry, buffer)) + if parameters is None: + args = (geometry, buffer) + else: + args = (geometry, buffer, parameters) + + return _call_st_function("ST_Buffer", args) @validate_argument_types diff --git a/python/tests/sql/test_function.py b/python/tests/sql/test_function.py index 948972c428..ea958eb4ec 100644 --- a/python/tests/sql/test_function.py +++ b/python/tests/sql/test_function.py @@ -93,8 +93,13 @@ 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_Buffer(polygondf.countyshape, 1) from polygondf") - function_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") + 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))" def test_st_envelope(self): polygon_from_wkt = self.spark.read.format("csv"). \ 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 30ae7a32c7..cd5366fda0 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 @@ -153,7 +153,7 @@ case class ST_NDims(inputExpressions: Seq[Expression]) * @param inputExpressions */ case class ST_Buffer(inputExpressions: Seq[Expression]) - extends InferredExpression(Functions.buffer _) { + extends InferredExpression(inferrableFunction2(Functions.buffer), inferrableFunction3(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 e5497699c5..4da9579704 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,6 +66,8 @@ 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_BuildArea(geometry: Column): Column = wrapExpression[ST_BuildArea](geometry) def ST_BuildArea(geometry: String): Column = wrapExpression[ST_BuildArea](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 b7b837698b..f00da2ff44 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 @@ -193,9 +193,27 @@ class dataFrameAPITestScala extends TestBaseScala { it("Passed ST_Buffer") { val polygonDf = sparkSession.sql("SELECT ST_Point(1.0, 1.0) AS geom") val df = polygonDf.select(ST_Buffer("geom", 1.0).as("geom")).selectExpr("ST_ReducePrecision(geom, 2)") - val actualResult = df.take(1)(0).get(0).asInstanceOf[Geometry].toText() - val expectedResult = "POLYGON ((1.98 0.8, 1.92 0.62, 1.83 0.44, 1.71 0.29, 1.56 0.17, 1.38 0.08, 1.2 0.02, 1 0, 0.8 0.02, 0.62 0.08, 0.44 0.17, 0.29 0.29, 0.17 0.44, 0.08 0.62, 0.02 0.8, 0 1, 0.02 1.2, 0.08 1.38, 0.17 1.56, 0.29 1.71, 0.44 1.83, 0.62 1.92, 0.8 1.98, 1 2, 1.2 1.98, 1.38 1.92, 1.56 1.83, 1.71 1.71, 1.83 1.56, 1.92 1.38, 1.98 1.2, 2 1, 1.98 0.8))" - assert(actualResult == expectedResult) + var actual = df.take(1)(0).get(0).asInstanceOf[Geometry].toText() + var expected = "POLYGON ((1.98 0.8, 1.92 0.62, 1.83 0.44, 1.71 0.29, 1.56 0.17, 1.38 0.08, 1.2 0.02, 1 0, 0.8 0.02, 0.62 0.08, 0.44 0.17, 0.29 0.29, 0.17 0.44, 0.08 0.62, 0.02 0.8, 0 1, 0.02 1.2, 0.08 1.38, 0.17 1.56, 0.29 1.71, 0.44 1.83, 0.62 1.92, 0.8 1.98, 1 2, 1.2 1.98, 1.38 1.92, 1.56 1.83, 1.71 1.71, 1.83 1.56, 1.92 1.38, 1.98 1.2, 2 1, 1.98 0.8))" + 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)") + 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)") + 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)") + 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) } it("Passed ST_Envelope") {