This is an automated email from the ASF dual-hosted git repository.

jiayu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sedona.git


The following commit(s) were added to refs/heads/master by this push:
     new d756a9ab8 [SEDONA-413] Add buffer parameters to ST_Buffer (#1066)
d756a9ab8 is described below

commit d756a9ab870f043946147672158d3b2c9daf8935
Author: Furqaan Khan <[email protected]>
AuthorDate: Fri Nov 3 04:27:30 2023 +0530

    [SEDONA-413] Add buffer parameters to ST_Buffer (#1066)
---
 .../java/org/apache/sedona/common/Functions.java   | 100 ++++++++++++++++++++-
 .../org/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
 .../apache/sedona/flink/expressions/Functions.java |   7 ++
 .../java/org/apache/sedona/flink/FunctionTest.java |  15 +++-
 python/sedona/sql/st_functions.py                  |   9 +-
 python/tests/sql/test_function.py                  |   9 +-
 .../sql/sedona_sql/expressions/Functions.scala     |   2 +-
 .../sql/sedona_sql/expressions/st_functions.scala  |   2 +
 .../apache/sedona/sql/dataFrameAPITestScala.scala  |  24 ++++-
 15 files changed, 250 insertions(+), 21 deletions(-)

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 a5ce8585b..bd4731e44 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.geom.util.GeometryFixer;
 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 class Functions {
     }
 
     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 a6e355e1d..e4a72fa8b 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 class FunctionsTest {
         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 1 [...]
+        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.3576 [...]
+        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 81d82542a..6bcd213e1 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:
 
+<img alt="Point buffer with 8 quadrant segments" 
src="../../../image/point-buffer-quad-8.png" width="100" height=""/>
+<img alt="Point buffer with 2 quadrant segments" 
src="../../../image/point-buffer-quad-2.png" width="100" height=""/>
+
+8 Segments &ensp; 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.83146961 [...]
-```
+
+Output:
+
+<img alt="Original Linestring" src="../../../image/linestring-og.png" 
width="150"/>
+<img alt="Original Linestring with buffer on the left side" 
src="../../../image/linestring-left-side.png" width="150"/>
+
+Original Linestring &emsp; Left side buffed Linestring
 
 ## ST_BuildArea
 
diff --git a/docs/api/sql/Function.md b/docs/api/sql/Function.md
index d41eeaf6a..ec1117a37 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:
 
+<img alt="Point buffer with 8 quadrant segments" 
src="../../../image/point-buffer-quad-8.png" width="100" height=""/>
+<img alt="Point buffer with 2 quadrant segments" 
src="../../../image/point-buffer-quad-2.png" width="100" height=""/>
+
+8 Segments &ensp; 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.83146961 [...]
-```
+
+Output:
+
+<img alt="Original Linestring" src="../../../image/linestring-og.png" 
width="150"/>
+<img alt="Original Linestring with buffer on the left side" 
src="../../../image/linestring-left-side.png" width="150"/>
+
+Original Linestring &emsp; 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 000000000..160eb07b9
Binary files /dev/null and b/docs/image/linestring-left-side.png differ
diff --git a/docs/image/linestring-og.png b/docs/image/linestring-og.png
new file mode 100644
index 000000000..5f6f9c264
Binary files /dev/null and b/docs/image/linestring-og.png differ
diff --git a/docs/image/point-buffer-quad-2.png 
b/docs/image/point-buffer-quad-2.png
new file mode 100644
index 000000000..52d0b6282
Binary files /dev/null and b/docs/image/point-buffer-quad-2.png differ
diff --git a/docs/image/point-buffer-quad-8.png 
b/docs/image/point-buffer-quad-8.png
new file mode 100644
index 000000000..ce16d1e9b
Binary files /dev/null and b/docs/image/point-buffer-quad-8.png differ
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 701e7eeb3..9ea03ce6a 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
@@ -73,6 +73,13 @@ public class Functions {
             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) {
+            Geometry geom = (Geometry) o;
+            return org.apache.sedona.common.Functions.buffer(geom, radius, 
params);
+        }
     }
 
     public static class ST_ClosestPoint extends ScalarFunction {
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 7a098b403..59485e51b 100644
--- a/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java
+++ b/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java
@@ -37,8 +37,7 @@ import java.util.Objects;
 import java.util.stream.Collectors;
 
 import static junit.framework.TestCase.assertNull;
-import static org.apache.flink.table.api.Expressions.$;
-import static org.apache.flink.table.api.Expressions.call;
+import static org.apache.flink.table.api.Expressions.*;
 import static org.junit.Assert.*;
 
 public class FunctionTest extends TestBase{
@@ -86,6 +85,18 @@ public class FunctionTest extends TestBase{
         Table bufferTable = 
pointTable.select(call(Functions.ST_Buffer.class.getSimpleName(), 
$(pointColNames[0]), 1));
         Geometry result = (Geometry) first(bufferTable).getField(0);
         assert(result instanceof Polygon);
+
+        String actual = (String) first(tableEnv.sqlQuery("SELECT 
ST_AsText(ST_Buffer(ST_GeomFromWKT('LINESTRING(0 0, 50 70, 100 100)'), 10, 
'side=left'))")).getField(0);
+        String 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);
+
+        actual = (String) first(tableEnv.sqlQuery("SELECT 
ST_AsText(ST_Buffer(ST_GeomFromWKT('LINESTRING(0 0, 50 70, 70 -3)'), 10, 
'endcap=square'))")).getField(0);
+        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.3576 [...]
+        assertEquals(expected, actual);
+
+        actual = (String) first(tableEnv.sqlQuery("SELECT 
ST_AsText(ST_Buffer(ST_Point(100, 90), 200, 'quad_segs=4'))")).getField(0);
+        expected = "POLYGON ((300 90, 284.7759065022574 13.463313526982049, 
241.4213562373095 -51.42135623730948, 176.53668647301797 -94.77590650225736, 
100.00000000000001 -110, 23.46331352698205 -94.77590650225736, 
-41.42135623730948 -51.42135623730951, -84.77590650225736 13.46331352698202, 
-100 89.99999999999997, -84.77590650225736 166.53668647301794, 
-41.42135623730954 231.42135623730948, 23.463313526981935 274.77590650225727, 
99.99999999999996 290, 176.536686473018 274.7759065022573, [...]
+        assertEquals(expected, actual);
     }
 
     @Test
diff --git a/python/sedona/sql/st_functions.py 
b/python/sedona/sql/st_functions.py
index 0f5ad4a43..64fe6c286 100644
--- a/python/sedona/sql/st_functions.py
+++ b/python/sedona/sql/st_functions.py
@@ -315,7 +315,7 @@ def ST_Boundary(geometry: ColumnOrName) -> 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 948972c42..ea958eb4e 100644
--- a/python/tests/sql/test_function.py
+++ b/python/tests/sql/test_function.py
@@ -93,8 +93,13 @@ class TestPredicateJoin(TestBase):
         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, - [...]
+
+        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.2 [...]
 
     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 30ae7a32c..cd5366fda 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 e5497699c..4da957970 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 b7b837698..f00da2ff4 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") {

Reply via email to