From 0cd25d6080a7fb5b65956ecc5edb6d9ba9a10f6c Mon Sep 17 00:00:00 2001
From: Oliver Tan
Date: Fri, 29 May 2020 20:49:45 -0700
Subject: [PATCH] geo/geomfn: implement ST_Buffer
Implemented ST_Buffer. Had to write our own parsing logic to make it
compatible with C.
Also ran clang-format and fixed up a few bad infos.
Release note (sql change): Implement ST_Buffer for geometry and string
variants.
---
docs/generated/sql/functions.md | 55 +++-
pkg/geo/geomfn/buffer.go | 121 ++++++++
pkg/geo/geomfn/buffer_test.go | 86 +++++
pkg/geo/geos/geos.cc | 111 +++++--
pkg/geo/geos/geos.go | 53 ++++
pkg/geo/geos/geos.h | 46 ++-
.../logictest/testdata/logic_test/geospatial | 29 ++
pkg/sql/sem/builtins/geo_builtins.go | 293 +++++++++++++++++-
pkg/testutils/lint/lint_test.go | 1 +
9 files changed, 754 insertions(+), 41 deletions(-)
create mode 100644 pkg/geo/geomfn/buffer.go
create mode 100644 pkg/geo/geomfn/buffer_test.go
diff --git a/docs/generated/sql/functions.md b/docs/generated/sql/functions.md
index bb293139c499..34e41f4e6a0c 100644
--- a/docs/generated/sql/functions.md
+++ b/docs/generated/sql/functions.md
@@ -744,6 +744,60 @@ has no relationship with the commit order of concurrent transactions.
st_astext(geometry: geometry) → string | Returns the WKT representation of a given Geometry.
|
+st_buffer(geometry: geometry, distance: float) → geometry | Returns a Geometry that represents all points whose distance is less than or equal to the given distance.
+This function utilizes the GEOS module.
+ |
+st_buffer(geometry: geometry, distance: float, buffer_style_params: string) → geometry | Returns a Geometry that represents all points whose distance is less than or equal to the given distance.
+This variant takes in a space separate parameter string, which will augment the buffer styles. Valid parameters are:
+
+- quad_segs=<int>, default 8
+- endcap=<round|flat|butt|square>, default round
+- join=<round|mitre|miter|bevel>, default round
+- side=<both|left|right>, default both
+- mitre_limit=<float>, default 5.0
+
+This function utilizes the GEOS module.
+ |
+st_buffer(geometry: geometry, distance: float, quad_segs: int) → geometry | Returns a Geometry that represents all points whose distance is less than or equal to the given distance.
+This variant approximates the circle into quad_seg segments per line (the default is 8).
+This function utilizes the GEOS module.
+ |
+st_buffer(geometry_str: string, distance: decimal) → geometry | Returns a Geometry that represents all points whose distance is less than or equal to the given distance.
+This function utilizes the GEOS module.
+ |
+st_buffer(geometry_str: string, distance: decimal, buffer_style_params: string) → geometry | Returns a Geometry that represents all points whose distance is less than or equal to the given distance.
+This variant takes in a space separate parameter string, which will augment the buffer styles. Valid parameters are:
+
+- quad_segs=<int>, default 8
+- endcap=<round|flat|butt|square>, default round
+- join=<round|mitre|miter|bevel>, default round
+- side=<both|left|right>, default both
+- mitre_limit=<float>, default 5.0
+
+This function utilizes the GEOS module.
+ |
+st_buffer(geometry_str: string, distance: decimal, quad_segs: int) → geometry | Returns a Geometry that represents all points whose distance is less than or equal to the given distance.
+This variant approximates the circle into quad_seg segments per line (the default is 8).
+This function utilizes the GEOS module.
+ |
+st_buffer(geometry_str: string, distance: float) → geometry | Returns a Geometry that represents all points whose distance is less than or equal to the given distance.
+This function utilizes the GEOS module.
+ |
+st_buffer(geometry_str: string, distance: float, buffer_style_params: string) → geometry | Returns a Geometry that represents all points whose distance is less than or equal to the given distance.
+This variant takes in a space separate parameter string, which will augment the buffer styles. Valid parameters are:
+
+- quad_segs=<int>, default 8
+- endcap=<round|flat|butt|square>, default round
+- join=<round|mitre|miter|bevel>, default round
+- side=<both|left|right>, default both
+- mitre_limit=<float>, default 5.0
+
+This function utilizes the GEOS module.
+ |
+st_buffer(geometry_str: string, distance: float, quad_segs: int) → geometry | Returns a Geometry that represents all points whose distance is less than or equal to the given distance.
+This variant approximates the circle into quad_seg segments per line (the default is 8).
+This function utilizes the GEOS module.
+ |
st_centroid(geometry: geometry) → geometry | Returns the centroid of the given geometry.
This function utilizes the GEOS module.
|
@@ -1039,7 +1093,6 @@ has no relationship with the commit order of concurrent transactions.
st_segmentize(geography: geography, max_segment_length_meters: float) → geography | Returns a modified Geography having no segment longer than the given max_segment_length meters.
The calculations are done on a sphere.
This function utilizes the S2 library for spherical calculations.
-This function will automatically use any available index.
|
st_setsrid(geography: geography, srid: int) → geography | Sets a Geography to a new SRID without transforming the coordinates.
|
diff --git a/pkg/geo/geomfn/buffer.go b/pkg/geo/geomfn/buffer.go
new file mode 100644
index 000000000000..ca930bfbb2ce
--- /dev/null
+++ b/pkg/geo/geomfn/buffer.go
@@ -0,0 +1,121 @@
+// Copyright 2020 The Cockroach Authors.
+//
+// Use of this software is governed by the Business Source License
+// included in the file licenses/BSL.txt.
+//
+// As of the Change Date specified in that file, in accordance with
+// the Business Source License, use of this software will be governed
+// by the Apache License, Version 2.0, included in the file
+// licenses/APL.txt.
+
+package geomfn
+
+import (
+ "strconv"
+ "strings"
+
+ "github.com/cockroachdb/cockroach/pkg/geo"
+ "github.com/cockroachdb/cockroach/pkg/geo/geos"
+ "github.com/cockroachdb/errors"
+)
+
+// BufferParams is a wrapper around the geos.BufferParams.
+type BufferParams struct {
+ p geos.BufferParams
+}
+
+// MakeDefaultBufferParams returns the default BufferParams/
+func MakeDefaultBufferParams() BufferParams {
+ return BufferParams{
+ p: geos.BufferParams{
+ EndCapStyle: geos.BufferParamsEndCapStyleRound,
+ JoinStyle: geos.BufferParamsJoinStyleRound,
+ SingleSided: false,
+ QuadrantSegments: 8,
+ MitreLimit: 5.0,
+ },
+ }
+}
+
+// WithQuadrantSegments returns a copy of the BufferParams with the quadrantSegments set.
+func (b BufferParams) WithQuadrantSegments(quadrantSegments int) BufferParams {
+ ret := b
+ ret.p.QuadrantSegments = quadrantSegments
+ return ret
+}
+
+// ParseBufferParams parses the given buffer params from a SQL string into
+// the BufferParams form.
+// The string must be of the same format as specified by https://postgis.net/docs/ST_Buffer.html.
+// Returns the BufferParams, as well as the modified distance.
+func ParseBufferParams(s string, distance float64) (BufferParams, float64, error) {
+ p := MakeDefaultBufferParams()
+ fields := strings.Fields(s)
+ for _, field := range fields {
+ fParams := strings.Split(field, "=")
+ if len(fParams) != 2 {
+ return BufferParams{}, 0, errors.Newf("unknown buffer parameter: %s", fParams)
+ }
+ f, val := fParams[0], fParams[1]
+ switch strings.ToLower(f) {
+ case "quad_segs":
+ valInt, err := strconv.ParseInt(val, 10, 64)
+ if err != nil {
+ return BufferParams{}, 0, errors.Wrapf(err, "invalid int for %s: %s", f, val)
+ }
+ p.p.QuadrantSegments = int(valInt)
+ case "endcap":
+ switch strings.ToLower(val) {
+ case "round":
+ p.p.EndCapStyle = geos.BufferParamsEndCapStyleRound
+ case "flat", "butt":
+ p.p.EndCapStyle = geos.BufferParamsEndCapStyleFlat
+ case "square":
+ p.p.EndCapStyle = geos.BufferParamsEndCapStyleSquare
+ default:
+ return BufferParams{}, 0, errors.Newf("unknown endcap: %s (accepted: round, flat, square)", val)
+ }
+ case "join":
+ switch strings.ToLower(val) {
+ case "round":
+ p.p.JoinStyle = geos.BufferParamsJoinStyleRound
+ case "mitre", "miter":
+ p.p.JoinStyle = geos.BufferParamsJoinStyleMitre
+ case "bevel":
+ p.p.JoinStyle = geos.BufferParamsJoinStyleBevel
+ default:
+ return BufferParams{}, 0, errors.Newf("unknown join: %s (accepted: round, mitre, bevel)", val)
+ }
+ case "mitre_limit", "miter_limit":
+ valFloat, err := strconv.ParseFloat(val, 64)
+ if err != nil {
+ return BufferParams{}, 0, errors.Wrapf(err, "invalid float for %s: %s", f, val)
+ }
+ p.p.MitreLimit = valFloat
+ case "side":
+ switch strings.ToLower(val) {
+ case "both":
+ p.p.SingleSided = false
+ case "left":
+ p.p.SingleSided = true
+ case "right":
+ p.p.SingleSided = true
+ distance *= -1
+ default:
+ return BufferParams{}, 0, errors.Newf("unknown side: %s (accepted: both, left, right)", val)
+ }
+ default:
+ return BufferParams{}, 0, errors.Newf("unknown field: %s (accepted fields: quad_segs, endcap, join, mitre_limit, side)", f)
+ }
+ }
+ return p, distance, nil
+}
+
+// Buffer buffers a given Geometry by the supplied parameters.
+func Buffer(g *geo.Geometry, params BufferParams, distance float64) (*geo.Geometry, error) {
+ bufferedGeom, err := geos.Buffer(g.EWKB(), params.p, distance)
+ if err != nil {
+ return nil, err
+ }
+ return geo.ParseGeometryFromEWKB(bufferedGeom)
+}
diff --git a/pkg/geo/geomfn/buffer_test.go b/pkg/geo/geomfn/buffer_test.go
new file mode 100644
index 000000000000..eb7313e9ec3e
--- /dev/null
+++ b/pkg/geo/geomfn/buffer_test.go
@@ -0,0 +1,86 @@
+// Copyright 2020 The Cockroach Authors.
+//
+// Use of this software is governed by the Business Source License
+// included in the file licenses/BSL.txt.
+//
+// As of the Change Date specified in that file, in accordance with
+// the Business Source License, use of this software will be governed
+// by the Apache License, Version 2.0, included in the file
+// licenses/APL.txt.
+
+package geomfn
+
+import (
+ "testing"
+
+ "github.com/cockroachdb/cockroach/pkg/geo/geos"
+ "github.com/stretchr/testify/require"
+)
+
+func TestParseBufferParams(t *testing.T) {
+ testCases := []struct {
+ s string
+ d float64
+
+ expected BufferParams
+ expectedD float64
+ }{
+ {
+ s: "",
+ d: 100,
+ expected: BufferParams{p: geos.BufferParams{
+ EndCapStyle: geos.BufferParamsEndCapStyleRound,
+ JoinStyle: geos.BufferParamsJoinStyleRound,
+ SingleSided: false,
+ QuadrantSegments: 8,
+ MitreLimit: 5.0,
+ }},
+ expectedD: 100,
+ },
+ {
+ s: "endcap=flat join=mitre quad_segs=4",
+ d: 100,
+ expected: BufferParams{p: geos.BufferParams{
+ EndCapStyle: geos.BufferParamsEndCapStyleFlat,
+ JoinStyle: geos.BufferParamsJoinStyleMitre,
+ SingleSided: false,
+ QuadrantSegments: 4,
+ MitreLimit: 5.0,
+ }},
+ expectedD: 100,
+ },
+ {
+ s: "side=left",
+ d: 100,
+ expected: BufferParams{p: geos.BufferParams{
+ EndCapStyle: geos.BufferParamsEndCapStyleRound,
+ JoinStyle: geos.BufferParamsJoinStyleRound,
+ SingleSided: true,
+ QuadrantSegments: 8,
+ MitreLimit: 5.0,
+ }},
+ expectedD: 100,
+ },
+ {
+ s: "side=right",
+ d: 100,
+ expected: BufferParams{p: geos.BufferParams{
+ EndCapStyle: geos.BufferParamsEndCapStyleRound,
+ JoinStyle: geos.BufferParamsJoinStyleRound,
+ SingleSided: true,
+ QuadrantSegments: 8,
+ MitreLimit: 5.0,
+ }},
+ expectedD: -100,
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.s, func(t *testing.T) {
+ s, d, err := ParseBufferParams(tc.s, tc.d)
+ require.NoError(t, err)
+ require.Equal(t, tc.expected, s)
+ require.Equal(t, tc.expectedD, d)
+ })
+ }
+}
diff --git a/pkg/geo/geos/geos.cc b/pkg/geo/geos/geos.cc
index da8781c633da..b859108dba5d 100644
--- a/pkg/geo/geos/geos.cc
+++ b/pkg/geo/geos/geos.cc
@@ -14,7 +14,7 @@
#include
#else
#include
-#endif // #if _WIN32
+#endif // #if _WIN32
#include
#include
#include
@@ -22,14 +22,14 @@
#include "geos.h"
#if _WIN32
-#define dlopen(x,y) LoadLibrary(x)
+#define dlopen(x, y) LoadLibrary(x)
#define dlsym GetProcAddress
#define dlclose FreeLibrary
-#define dlerror() ((char*) "failed to execute dlsym")
+#define dlerror() ((char*)"failed to execute dlsym")
typedef HMODULE dlhandle;
#else
typedef void* dlhandle;
-#endif // #if _WIN32
+#endif // #if _WIN32
#define CR_GEOS_NO_ERROR_DEFINED_MESSAGE "geos: returned invalid result but error not populated"
@@ -41,6 +41,7 @@ typedef void (*CR_GEOS_MessageHandler)(const char*, void*);
typedef void* CR_GEOS_WKTReader;
typedef void* CR_GEOS_WKBReader;
typedef void* CR_GEOS_WKBWriter;
+typedef void* CR_GEOS_BufferParams;
// Function declarations from `capi/geos_c.h.in` in GEOS.
typedef CR_GEOS_Handle (*CR_GEOS_init_r)();
@@ -63,6 +64,21 @@ typedef CR_GEOS_Geometry (*CR_GEOS_WKBReader_read_r)(CR_GEOS_Handle, CR_GEOS_WKB
size_t);
typedef void (*CR_GEOS_WKBReader_destroy_r)(CR_GEOS_Handle, CR_GEOS_WKBReader);
+typedef CR_GEOS_BufferParams (*CR_GEOS_BufferParams_create_r)(CR_GEOS_Handle);
+typedef void (*CR_GEOS_GEOSBufferParams_destroy_r)(CR_GEOS_Handle, CR_GEOS_BufferParams);
+typedef int (*CR_GEOS_BufferParams_setEndCapStyle_r)(CR_GEOS_Handle, CR_GEOS_BufferParams,
+ int endCapStyle);
+typedef int (*CR_GEOS_BufferParams_setJoinStyle_r)(CR_GEOS_Handle, CR_GEOS_BufferParams,
+ int joinStyle);
+typedef int (*CR_GEOS_BufferParams_setMitreLimit_r)(CR_GEOS_Handle, CR_GEOS_BufferParams,
+ double mitreLimit);
+typedef int (*CR_GEOS_BufferParams_setQuadrantSegments_r)(CR_GEOS_Handle, CR_GEOS_BufferParams,
+ int quadrantSegments);
+typedef int (*CR_GEOS_BufferParams_setSingleSided_r)(CR_GEOS_Handle, CR_GEOS_BufferParams,
+ int singleSided);
+typedef CR_GEOS_Geometry (*CR_GEOS_BufferWithParams_r)(CR_GEOS_Handle, CR_GEOS_Geometry,
+ CR_GEOS_BufferParams, double width);
+
typedef int (*CR_GEOS_Area_r)(CR_GEOS_Handle, CR_GEOS_Geometry, double*);
typedef int (*CR_GEOS_Length_r)(CR_GEOS_Handle, CR_GEOS_Geometry, double*);
typedef CR_GEOS_Geometry (*CR_GEOS_Centroid_r)(CR_GEOS_Handle, CR_GEOS_Geometry);
@@ -104,6 +120,15 @@ struct CR_GEOS {
CR_GEOS_Context_setErrorMessageHandler_r GEOSContext_setErrorMessageHandler_r;
CR_GEOS_Free_r GEOSFree_r;
+ CR_GEOS_BufferParams_create_r GEOSBufferParams_create_r;
+ CR_GEOS_GEOSBufferParams_destroy_r GEOSBufferParams_destroy_r;
+ CR_GEOS_BufferParams_setEndCapStyle_r GEOSBufferParams_setEndCapStyle_r;
+ CR_GEOS_BufferParams_setJoinStyle_r GEOSBufferParams_setJoinStyle_r;
+ CR_GEOS_BufferParams_setMitreLimit_r GEOSBufferParams_setMitreLimit_r;
+ CR_GEOS_BufferParams_setQuadrantSegments_r GEOSBufferParams_setQuadrantSegments_r;
+ CR_GEOS_BufferParams_setSingleSided_r GEOSBufferParams_setSingleSided_r;
+ CR_GEOS_BufferWithParams_r GEOSBufferWithParams_r;
+
CR_GEOS_SetSRID_r GEOSSetSRID_r;
CR_GEOS_GetSRID_r GEOSGetSRID_r;
CR_GEOS_GeomDestroy_r GEOSGeom_destroy_r;
@@ -164,6 +189,14 @@ struct CR_GEOS {
INIT(GEOSFree_r);
INIT(GEOSContext_setErrorMessageHandler_r);
INIT(GEOSGeom_destroy_r);
+ INIT(GEOSBufferParams_create_r);
+ INIT(GEOSBufferParams_destroy_r);
+ INIT(GEOSBufferParams_setEndCapStyle_r);
+ INIT(GEOSBufferParams_setJoinStyle_r);
+ INIT(GEOSBufferParams_setMitreLimit_r);
+ INIT(GEOSBufferParams_setQuadrantSegments_r);
+ INIT(GEOSBufferParams_setSingleSided_r);
+ INIT(GEOSBufferWithParams_r);
INIT(GEOSSetSRID_r);
INIT(GEOSGetSRID_r);
INIT(GEOSArea_r);
@@ -251,7 +284,8 @@ CR_GEOS_Handle initHandleWithErrorBuffer(CR_GEOS* lib, std::string* buffer) {
return handle;
}
-CR_GEOS_Geometry CR_GEOS_GeometryFromSlice(CR_GEOS* lib, CR_GEOS_Handle handle, CR_GEOS_Slice slice) {
+CR_GEOS_Geometry CR_GEOS_GeometryFromSlice(CR_GEOS* lib, CR_GEOS_Handle handle,
+ CR_GEOS_Slice slice) {
auto wkbReader = lib->GEOSWKBReader_create_r(handle);
auto geom = lib->GEOSWKBReader_read_r(handle, wkbReader, slice.data, slice.len);
lib->GEOSWKBReader_destroy_r(handle, wkbReader);
@@ -309,12 +343,43 @@ CR_GEOS_Status CR_GEOS_ClipEWKBByRect(CR_GEOS* lib, CR_GEOS_Slice ewkb, double x
return toGEOSString(error.data(), error.length());
}
+CR_GEOS_Status CR_GEOS_Buffer(CR_GEOS* lib, CR_GEOS_Slice ewkb, CR_GEOS_BufferParamsInput params,
+ double distance, CR_GEOS_String* ret) {
+ std::string error;
+ auto handle = initHandleWithErrorBuffer(lib, &error);
+ *ret = {.data = NULL, .len = 0};
+
+ auto geom = CR_GEOS_GeometryFromSlice(lib, handle, ewkb);
+ if (geom != nullptr) {
+ auto gParams = lib->GEOSBufferParams_create_r(handle);
+ if (gParams != nullptr) {
+ if (lib->GEOSBufferParams_setEndCapStyle_r(handle, gParams, params.endCapStyle) &&
+ lib->GEOSBufferParams_setJoinStyle_r(handle, gParams, params.joinStyle) &&
+ lib->GEOSBufferParams_setMitreLimit_r(handle, gParams, params.mitreLimit) &&
+ lib->GEOSBufferParams_setQuadrantSegments_r(handle, gParams, params.quadrantSegments) &&
+ lib->GEOSBufferParams_setSingleSided_r(handle, gParams, params.singleSided)) {
+ auto bufferedGeom = lib->GEOSBufferWithParams_r(handle, geom, gParams, distance);
+ if (bufferedGeom != nullptr) {
+ auto srid = lib->GEOSGetSRID_r(handle, geom);
+ CR_GEOS_writeGeomToEWKB(lib, handle, bufferedGeom, ret, srid);
+ lib->GEOSGeom_destroy_r(handle, bufferedGeom);
+ }
+ }
+ lib->GEOSBufferParams_destroy_r(handle, gParams);
+ }
+ lib->GEOSGeom_destroy_r(handle, geom);
+ }
+
+ lib->GEOS_finish_r(handle);
+ return toGEOSString(error.data(), error.length());
+}
+
//
// Unary operators
//
template
-CR_GEOS_Status CR_GEOS_UnaryOperator(CR_GEOS* lib, T fn, CR_GEOS_Slice a, R *ret) {
+CR_GEOS_Status CR_GEOS_UnaryOperator(CR_GEOS* lib, T fn, CR_GEOS_Slice a, R* ret) {
std::string error;
auto handle = initHandleWithErrorBuffer(lib, &error);
auto geom = CR_GEOS_GeometryFromSlice(lib, handle, a);
@@ -333,7 +398,8 @@ CR_GEOS_Status CR_GEOS_UnaryOperator(CR_GEOS* lib, T fn, CR_GEOS_Slice a, R *ret
}
template
-CR_GEOS_Status CR_GEOS_BinaryOperator(CR_GEOS* lib, T fn, CR_GEOS_Slice a, CR_GEOS_Slice b, R *ret) {
+CR_GEOS_Status CR_GEOS_BinaryOperator(CR_GEOS* lib, T fn, CR_GEOS_Slice a, CR_GEOS_Slice b,
+ R* ret) {
std::string error;
auto handle = initHandleWithErrorBuffer(lib, &error);
auto wkbReader = lib->GEOSWKBReader_create_r(handle);
@@ -359,15 +425,15 @@ CR_GEOS_Status CR_GEOS_BinaryOperator(CR_GEOS* lib, T fn, CR_GEOS_Slice a, CR_GE
return toGEOSString(error.data(), error.length());
}
-CR_GEOS_Status CR_GEOS_Area(CR_GEOS* lib, CR_GEOS_Slice a, double *ret) {
+CR_GEOS_Status CR_GEOS_Area(CR_GEOS* lib, CR_GEOS_Slice a, double* ret) {
return CR_GEOS_UnaryOperator(lib, lib->GEOSArea_r, a, ret);
}
-CR_GEOS_Status CR_GEOS_Length(CR_GEOS* lib, CR_GEOS_Slice a, double *ret) {
+CR_GEOS_Status CR_GEOS_Length(CR_GEOS* lib, CR_GEOS_Slice a, double* ret) {
return CR_GEOS_UnaryOperator(lib, lib->GEOSLength_r, a, ret);
}
-CR_GEOS_Status CR_GEOS_Centroid(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_String *centroidEWKB) {
+CR_GEOS_Status CR_GEOS_Centroid(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_String* centroidEWKB) {
std::string error;
auto handle = initHandleWithErrorBuffer(lib, &error);
auto geom = CR_GEOS_GeometryFromSlice(lib, handle, a);
@@ -385,7 +451,7 @@ CR_GEOS_Status CR_GEOS_Centroid(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_String *c
return toGEOSString(error.data(), error.length());
}
-CR_GEOS_Status CR_GEOS_Distance(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_Slice b, double *ret) {
+CR_GEOS_Status CR_GEOS_Distance(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_Slice b, double* ret) {
return CR_GEOS_BinaryOperator(lib, lib->GEOSDistance_r, a, b, ret);
}
@@ -394,7 +460,8 @@ CR_GEOS_Status CR_GEOS_Distance(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_Slice b,
//
template
-CR_GEOS_Status CR_GEOS_BinaryPredicate(CR_GEOS* lib, T fn, CR_GEOS_Slice a, CR_GEOS_Slice b, char *ret) {
+CR_GEOS_Status CR_GEOS_BinaryPredicate(CR_GEOS* lib, T fn, CR_GEOS_Slice a, CR_GEOS_Slice b,
+ char* ret) {
std::string error;
auto handle = initHandleWithErrorBuffer(lib, &error);
@@ -424,39 +491,39 @@ CR_GEOS_Status CR_GEOS_BinaryPredicate(CR_GEOS* lib, T fn, CR_GEOS_Slice a, CR_G
return toGEOSString(error.data(), error.length());
}
-CR_GEOS_Status CR_GEOS_Covers(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_Slice b, char *ret) {
+CR_GEOS_Status CR_GEOS_Covers(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_Slice b, char* ret) {
return CR_GEOS_BinaryPredicate(lib, lib->GEOSCovers_r, a, b, ret);
}
-CR_GEOS_Status CR_GEOS_CoveredBy(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_Slice b, char *ret) {
+CR_GEOS_Status CR_GEOS_CoveredBy(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_Slice b, char* ret) {
return CR_GEOS_BinaryPredicate(lib, lib->GEOSCoveredBy_r, a, b, ret);
}
-CR_GEOS_Status CR_GEOS_Contains(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_Slice b, char *ret) {
+CR_GEOS_Status CR_GEOS_Contains(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_Slice b, char* ret) {
return CR_GEOS_BinaryPredicate(lib, lib->GEOSContains_r, a, b, ret);
}
-CR_GEOS_Status CR_GEOS_Crosses(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_Slice b, char *ret) {
+CR_GEOS_Status CR_GEOS_Crosses(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_Slice b, char* ret) {
return CR_GEOS_BinaryPredicate(lib, lib->GEOSCrosses_r, a, b, ret);
}
-CR_GEOS_Status CR_GEOS_Equals(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_Slice b, char *ret) {
+CR_GEOS_Status CR_GEOS_Equals(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_Slice b, char* ret) {
return CR_GEOS_BinaryPredicate(lib, lib->GEOSEquals_r, a, b, ret);
}
-CR_GEOS_Status CR_GEOS_Intersects(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_Slice b, char *ret) {
+CR_GEOS_Status CR_GEOS_Intersects(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_Slice b, char* ret) {
return CR_GEOS_BinaryPredicate(lib, lib->GEOSIntersects_r, a, b, ret);
}
-CR_GEOS_Status CR_GEOS_Overlaps(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_Slice b, char *ret) {
+CR_GEOS_Status CR_GEOS_Overlaps(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_Slice b, char* ret) {
return CR_GEOS_BinaryPredicate(lib, lib->GEOSOverlaps_r, a, b, ret);
}
-CR_GEOS_Status CR_GEOS_Touches(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_Slice b, char *ret) {
+CR_GEOS_Status CR_GEOS_Touches(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_Slice b, char* ret) {
return CR_GEOS_BinaryPredicate(lib, lib->GEOSTouches_r, a, b, ret);
}
-CR_GEOS_Status CR_GEOS_Within(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_Slice b, char *ret) {
+CR_GEOS_Status CR_GEOS_Within(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_Slice b, char* ret) {
return CR_GEOS_BinaryPredicate(lib, lib->GEOSWithin_r, a, b, ret);
}
@@ -465,7 +532,7 @@ CR_GEOS_Status CR_GEOS_Within(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_Slice b, ch
// See: https://en.wikipedia.org/wiki/DE-9IM.
//
-CR_GEOS_Status CR_GEOS_Relate(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_Slice b, CR_GEOS_String *ret) {
+CR_GEOS_Status CR_GEOS_Relate(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_Slice b, CR_GEOS_String* ret) {
std::string error;
auto handle = initHandleWithErrorBuffer(lib, &error);
diff --git a/pkg/geo/geos/geos.go b/pkg/geo/geos/geos.go
index 673928ea95af..53dffb844071 100644
--- a/pkg/geo/geos/geos.go
+++ b/pkg/geo/geos/geos.go
@@ -228,6 +228,59 @@ func WKTToEWKB(wkt geopb.WKT, srid geopb.SRID) (geopb.EWKB, error) {
return cStringToSafeGoBytes(cEWKB), nil
}
+// BufferParamsJoinStyle maps to the GEOSBufJoinStyles enum in geos_c.h.in.
+type BufferParamsJoinStyle int
+
+// These should be kept in sync with the geos_c.h.in corresponding enum definition.
+const (
+ BufferParamsJoinStyleRound = 1
+ BufferParamsJoinStyleMitre = 2
+ BufferParamsJoinStyleBevel = 3
+)
+
+// BufferParamsEndCapStyle maps to the GEOSBufCapStyles enum in geos_c.h.in.
+type BufferParamsEndCapStyle int
+
+// These should be kept in sync with the geos_c.h.in corresponding enum definition.
+const (
+ BufferParamsEndCapStyleRound = 1
+ BufferParamsEndCapStyleFlat = 2
+ BufferParamsEndCapStyleSquare = 3
+)
+
+// BufferParams are parameters to provide into the GEOS buffer function.
+type BufferParams struct {
+ JoinStyle BufferParamsJoinStyle
+ EndCapStyle BufferParamsEndCapStyle
+ SingleSided bool
+ QuadrantSegments int
+ MitreLimit float64
+}
+
+// Buffer buffers the given geometry by the given distance and params.
+func Buffer(ewkb geopb.EWKB, params BufferParams, distance float64) (geopb.EWKB, error) {
+ g, err := ensureInitInternal()
+ if err != nil {
+ return nil, err
+ }
+ singleSided := 0
+ if params.SingleSided {
+ singleSided = 1
+ }
+ cParams := C.CR_GEOS_BufferParamsInput{
+ endCapStyle: C.int(params.EndCapStyle),
+ joinStyle: C.int(params.JoinStyle),
+ singleSided: C.int(singleSided),
+ quadrantSegments: C.int(params.QuadrantSegments),
+ mitreLimit: C.double(params.MitreLimit),
+ }
+ var cEWKB C.CR_GEOS_String
+ if err := statusToError(C.CR_GEOS_Buffer(g, goToCSlice(ewkb), cParams, C.double(distance), &cEWKB)); err != nil {
+ return nil, err
+ }
+ return cStringToSafeGoBytes(cEWKB), nil
+}
+
// Area returns the area of an EWKB.
func Area(ewkb geopb.EWKB) (float64, error) {
g, err := ensureInitInternal()
diff --git a/pkg/geo/geos/geos.h b/pkg/geo/geos/geos.h
index d07529f2fc18..675c8c8676fc 100644
--- a/pkg/geo/geos/geos.h
+++ b/pkg/geo/geos/geos.h
@@ -37,6 +37,15 @@ typedef struct {
size_t len;
} CR_GEOS_String;
+// CR_GEOS_BufferParams are parameters that will be passed to buffer.
+typedef struct {
+ int endCapStyle;
+ int joinStyle;
+ int singleSided;
+ int quadrantSegments;
+ double mitreLimit;
+} CR_GEOS_BufferParamsInput;
+
typedef CR_GEOS_String CR_GEOS_Status;
// CR_GEOS contains all the functions loaded by GEOS.
@@ -49,47 +58,50 @@ typedef struct CR_GEOS CR_GEOS;
// The error returned does not need to be freed (see comment for CR_GEOS_Slice).
CR_GEOS_Slice CR_GEOS_Init(CR_GEOS_Slice loc, CR_GEOS** lib);
-// CR_GEOS_WKTToWKB converts a given WKT into it's WKB form. The wkt slice must be
+// CR_GEOS_WKTToWKB converts a given WKT into it's EWKB form. The wkt slice must be
// convertible to a NUL character terminated C string.
CR_GEOS_Status CR_GEOS_WKTToEWKB(CR_GEOS* lib, CR_GEOS_Slice wkt, int srid, CR_GEOS_String* ewkb);
-// CR_GEOS_ClipEWKBByRect clips a given WKB by the given rectangle.
-CR_GEOS_Status CR_GEOS_ClipEWKBByRect(CR_GEOS* lib, CR_GEOS_Slice wkb, double xmin, double ymin,
+// CR_GEOS_ClipEWKBByRect clips a given EWKB by the given rectangle.
+CR_GEOS_Status CR_GEOS_ClipEWKBByRect(CR_GEOS* lib, CR_GEOS_Slice ewkb, double xmin, double ymin,
double xmax, double ymax, CR_GEOS_String* clippedEWKB);
+// CR_GEOS_Buffer buffers a given EWKB by the given distance and params.
+CR_GEOS_Status CR_GEOS_Buffer(CR_GEOS* lib, CR_GEOS_Slice ewkb, CR_GEOS_BufferParamsInput params,
+ double distance, CR_GEOS_String* ret);
//
// Unary operators.
//
-CR_GEOS_Status CR_GEOS_Area(CR_GEOS* lib, CR_GEOS_Slice a, double *ret);
-CR_GEOS_Status CR_GEOS_Length(CR_GEOS* lib, CR_GEOS_Slice a, double *ret);
-CR_GEOS_Status CR_GEOS_Centroid(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_String *centroidEWKB);
+CR_GEOS_Status CR_GEOS_Area(CR_GEOS* lib, CR_GEOS_Slice a, double* ret);
+CR_GEOS_Status CR_GEOS_Length(CR_GEOS* lib, CR_GEOS_Slice a, double* ret);
+CR_GEOS_Status CR_GEOS_Centroid(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_String* centroidEWKB);
//
// Binary operators.
//
-CR_GEOS_Status CR_GEOS_Distance(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_Slice b, double *ret);
+CR_GEOS_Status CR_GEOS_Distance(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_Slice b, double* ret);
//
// Binary predicates.
//
-CR_GEOS_Status CR_GEOS_Covers(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_Slice b, char *ret);
-CR_GEOS_Status CR_GEOS_CoveredBy(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_Slice b, char *ret);
-CR_GEOS_Status CR_GEOS_Contains(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_Slice b, char *ret);
-CR_GEOS_Status CR_GEOS_Crosses(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_Slice b, char *ret);
-CR_GEOS_Status CR_GEOS_Equals(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_Slice b, char *ret);
-CR_GEOS_Status CR_GEOS_Intersects(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_Slice b, char *ret);
-CR_GEOS_Status CR_GEOS_Overlaps(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_Slice b, char *ret);
-CR_GEOS_Status CR_GEOS_Touches(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_Slice b, char *ret);
-CR_GEOS_Status CR_GEOS_Within(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_Slice b, char *ret);
+CR_GEOS_Status CR_GEOS_Covers(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_Slice b, char* ret);
+CR_GEOS_Status CR_GEOS_CoveredBy(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_Slice b, char* ret);
+CR_GEOS_Status CR_GEOS_Contains(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_Slice b, char* ret);
+CR_GEOS_Status CR_GEOS_Crosses(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_Slice b, char* ret);
+CR_GEOS_Status CR_GEOS_Equals(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_Slice b, char* ret);
+CR_GEOS_Status CR_GEOS_Intersects(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_Slice b, char* ret);
+CR_GEOS_Status CR_GEOS_Overlaps(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_Slice b, char* ret);
+CR_GEOS_Status CR_GEOS_Touches(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_Slice b, char* ret);
+CR_GEOS_Status CR_GEOS_Within(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_Slice b, char* ret);
//
// DE-9IM related
//
-CR_GEOS_Status CR_GEOS_Relate(CR_GEOS *lib, CR_GEOS_Slice a, CR_GEOS_Slice b, CR_GEOS_String *ret);
+CR_GEOS_Status CR_GEOS_Relate(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_Slice b, CR_GEOS_String* ret);
#ifdef __cplusplus
} // extern "C"
#endif
diff --git a/pkg/sql/logictest/testdata/logic_test/geospatial b/pkg/sql/logictest/testdata/logic_test/geospatial
index aa2a9905404f..0a5f54c14d63 100644
--- a/pkg/sql/logictest/testdata/logic_test/geospatial
+++ b/pkg/sql/logictest/testdata/logic_test/geospatial
@@ -737,6 +737,35 @@ Square overlapping left and right square Square (left)
Square overlapping left and right square Square (right) true false
Square overlapping left and right square Square overlapping left and right square true false
+# Buffer -- unfortunately due to floating point precision, these results can be off by small
+# epsilon across operating systems. Until ST_AsEWKT with precision is implemented, we'll have to
+# verify that it works by checking another statistic for now.
+query TIII
+SELECT
+ a.dsc,
+ ST_NPoints(ST_Buffer(a.geom, 10)),
+ ST_NPoints(ST_Buffer(a.geom, 10, 2)),
+ ST_NPoints(ST_Buffer(a.geom, 10, 'quad_segs=4 endcap=flat'))
+FROM geom_operators_test a
+ORDER BY a.dsc
+----
+Empty GeometryCollection 0 0 0
+Empty LineString 0 0 0
+Faraway point 33 9 0
+Line going through left and right square 35 11 5
+NULL NULL NULL NULL
+Point middle of Left Square 33 9 0
+Point middle of Right Square 33 9 0
+Square (left) 37 13 21
+Square (right) 37 13 21
+Square overlapping left and right square 37 13 21
+
+# Test raw string with ST_Buffer
+query I
+SELECT ST_NPoints(ST_Buffer('SRID=4326;POINT(0 0)', 10.0))
+----
+33
+
# DE-9IM relations
query TTTB
SELECT
diff --git a/pkg/sql/sem/builtins/geo_builtins.go b/pkg/sql/sem/builtins/geo_builtins.go
index a0a0231bd05d..4f7195111fc0 100644
--- a/pkg/sql/sem/builtins/geo_builtins.go
+++ b/pkg/sql/sem/builtins/geo_builtins.go
@@ -318,6 +318,30 @@ var areaOverloadGeometry1 = geometryOverload1(
tree.VolatilityImmutable,
)
+var stBufferInfoBuilder = infoBuilder{
+ info: `Returns a Geometry that represents all points whose distance is less than or equal to the given distance.`,
+ libraryUsage: usesGEOS,
+}
+
+var stBufferWithParamsInfoBuilder = infoBuilder{
+ info: `Returns a Geometry that represents all points whose distance is less than or equal to the given distance.
+
+This variant takes in a space separate parameter string, which will augment the buffer styles. Valid parameters are:
+* quad_segs=, default 8
+* endcap=, default round
+* join=, default round
+* side=, default both
+* mitre_limit=, default 5.0`,
+ libraryUsage: usesGEOS,
+}
+
+var stBufferWithQuadSegInfoBuilder = infoBuilder{
+ info: `Returns a Geometry that represents all points whose distance is less than or equal to the given distance.
+
+This variant approximates the circle into quad_seg segments per line (the default is 8).`,
+ libraryUsage: usesGEOS,
+}
+
var geoBuiltins = map[string]builtinDefinition{
//
// Input (Geometry)
@@ -1881,11 +1905,278 @@ Note ST_Perimeter is only valid for Polygon - use ST_Length for LineString.`,
The calculations are done on a sphere.`,
libraryUsage: usesS2,
- canUseIndex: true,
}.String(),
Volatility: tree.VolatilityImmutable,
},
),
+ "st_buffer": makeBuiltin(
+ defProps(),
+ tree.Overload{
+ Types: tree.ArgTypes{
+ {"geometry", types.Geometry},
+ {"distance", types.Float},
+ },
+ ReturnType: tree.FixedReturnType(types.Geometry),
+ Fn: func(_ *tree.EvalContext, args tree.Datums) (tree.Datum, error) {
+ g := args[0].(*tree.DGeometry)
+ distance := *args[1].(*tree.DFloat)
+
+ ret, err := geomfn.Buffer(g.Geometry, geomfn.MakeDefaultBufferParams(), float64(distance))
+ if err != nil {
+ return nil, err
+ }
+ return tree.NewDGeometry(ret), nil
+ },
+ Info: stBufferInfoBuilder.String(),
+ Volatility: tree.VolatilityImmutable,
+ },
+ tree.Overload{
+ Types: tree.ArgTypes{
+ {"geometry", types.Geometry},
+ {"distance", types.Float},
+ {"quad_segs", types.Int},
+ },
+ ReturnType: tree.FixedReturnType(types.Geometry),
+ Fn: func(_ *tree.EvalContext, args tree.Datums) (tree.Datum, error) {
+ g := args[0].(*tree.DGeometry)
+ distance := *args[1].(*tree.DFloat)
+ quadSegs := *args[2].(*tree.DInt)
+
+ ret, err := geomfn.Buffer(
+ g.Geometry,
+ geomfn.MakeDefaultBufferParams().WithQuadrantSegments(int(quadSegs)),
+ float64(distance),
+ )
+ if err != nil {
+ return nil, err
+ }
+ return tree.NewDGeometry(ret), nil
+ },
+ Info: stBufferWithQuadSegInfoBuilder.String(),
+ Volatility: tree.VolatilityImmutable,
+ },
+ tree.Overload{
+ Types: tree.ArgTypes{
+ {"geometry", types.Geometry},
+ {"distance", types.Float},
+ {"buffer_style_params", types.String},
+ },
+ ReturnType: tree.FixedReturnType(types.Geometry),
+ Fn: func(_ *tree.EvalContext, args tree.Datums) (tree.Datum, error) {
+ g := args[0].(*tree.DGeometry)
+ distance := *args[1].(*tree.DFloat)
+ paramsString := *args[2].(*tree.DString)
+
+ params, modifiedDistance, err := geomfn.ParseBufferParams(string(paramsString), float64(distance))
+ if err != nil {
+ return nil, err
+ }
+
+ ret, err := geomfn.Buffer(
+ g.Geometry,
+ params,
+ modifiedDistance,
+ )
+ if err != nil {
+ return nil, err
+ }
+ return tree.NewDGeometry(ret), nil
+ },
+ Info: stBufferWithParamsInfoBuilder.String(),
+ Volatility: tree.VolatilityImmutable,
+ },
+ tree.Overload{
+ Types: tree.ArgTypes{
+ {"geometry_str", types.String},
+ {"distance", types.Float},
+ },
+ ReturnType: tree.FixedReturnType(types.Geometry),
+ Fn: func(_ *tree.EvalContext, args tree.Datums) (tree.Datum, error) {
+ gStr := *args[0].(*tree.DString)
+ distance := *args[1].(*tree.DFloat)
+
+ g, err := geo.ParseGeometry(string(gStr))
+ if err != nil {
+ return nil, err
+ }
+ ret, err := geomfn.Buffer(g, geomfn.MakeDefaultBufferParams(), float64(distance))
+ if err != nil {
+ return nil, err
+ }
+ return tree.NewDGeometry(ret), nil
+ },
+ Info: stBufferInfoBuilder.String(),
+ Volatility: tree.VolatilityImmutable,
+ },
+ tree.Overload{
+ Types: tree.ArgTypes{
+ {"geometry_str", types.String},
+ // This should be float, but for this to work equivalently to the psql type system,
+ // we have to make a decimal definition.
+ {"distance", types.Decimal},
+ },
+ ReturnType: tree.FixedReturnType(types.Geometry),
+ Fn: func(_ *tree.EvalContext, args tree.Datums) (tree.Datum, error) {
+ gStr := *args[0].(*tree.DString)
+ distance, err := args[1].(*tree.DDecimal).Float64()
+ if err != nil {
+ return nil, err
+ }
+
+ g, err := geo.ParseGeometry(string(gStr))
+ if err != nil {
+ return nil, err
+ }
+ ret, err := geomfn.Buffer(g, geomfn.MakeDefaultBufferParams(), distance)
+ if err != nil {
+ return nil, err
+ }
+ return tree.NewDGeometry(ret), nil
+ },
+ Info: stBufferInfoBuilder.String(),
+ Volatility: tree.VolatilityImmutable,
+ },
+ tree.Overload{
+ Types: tree.ArgTypes{
+ {"geometry_str", types.String},
+ {"distance", types.Float},
+ {"quad_segs", types.Int},
+ },
+ ReturnType: tree.FixedReturnType(types.Geometry),
+ Fn: func(_ *tree.EvalContext, args tree.Datums) (tree.Datum, error) {
+ gStr := *args[0].(*tree.DString)
+ distance := *args[1].(*tree.DFloat)
+ quadSegs := *args[2].(*tree.DInt)
+
+ g, err := geo.ParseGeometry(string(gStr))
+ if err != nil {
+ return nil, err
+ }
+
+ ret, err := geomfn.Buffer(
+ g,
+ geomfn.MakeDefaultBufferParams().WithQuadrantSegments(int(quadSegs)),
+ float64(distance),
+ )
+ if err != nil {
+ return nil, err
+ }
+ return tree.NewDGeometry(ret), nil
+ },
+ Info: stBufferWithQuadSegInfoBuilder.String(),
+ Volatility: tree.VolatilityImmutable,
+ },
+ tree.Overload{
+ Types: tree.ArgTypes{
+ {"geometry_str", types.String},
+ // This should be float, but for this to work equivalently to the psql type system,
+ // we have to make a decimal definition.
+ {"distance", types.Decimal},
+ {"quad_segs", types.Int},
+ },
+ ReturnType: tree.FixedReturnType(types.Geometry),
+ Fn: func(_ *tree.EvalContext, args tree.Datums) (tree.Datum, error) {
+ gStr := *args[0].(*tree.DString)
+ distance, err := args[1].(*tree.DDecimal).Float64()
+ if err != nil {
+ return nil, err
+ }
+ quadSegs := *args[2].(*tree.DInt)
+
+ g, err := geo.ParseGeometry(string(gStr))
+ if err != nil {
+ return nil, err
+ }
+
+ ret, err := geomfn.Buffer(
+ g,
+ geomfn.MakeDefaultBufferParams().WithQuadrantSegments(int(quadSegs)),
+ distance,
+ )
+ if err != nil {
+ return nil, err
+ }
+ return tree.NewDGeometry(ret), nil
+ },
+ Info: stBufferWithQuadSegInfoBuilder.String(),
+ Volatility: tree.VolatilityImmutable,
+ },
+ tree.Overload{
+ Types: tree.ArgTypes{
+ {"geometry_str", types.String},
+ {"distance", types.Float},
+ {"buffer_style_params", types.String},
+ },
+ ReturnType: tree.FixedReturnType(types.Geometry),
+ Fn: func(_ *tree.EvalContext, args tree.Datums) (tree.Datum, error) {
+ gStr := *args[0].(*tree.DString)
+ distance := *args[1].(*tree.DFloat)
+ paramsString := *args[2].(*tree.DString)
+
+ g, err := geo.ParseGeometry(string(gStr))
+ if err != nil {
+ return nil, err
+ }
+
+ params, modifiedDistance, err := geomfn.ParseBufferParams(string(paramsString), float64(distance))
+ if err != nil {
+ return nil, err
+ }
+
+ ret, err := geomfn.Buffer(
+ g,
+ params,
+ modifiedDistance,
+ )
+ if err != nil {
+ return nil, err
+ }
+ return tree.NewDGeometry(ret), nil
+ },
+ Info: stBufferWithParamsInfoBuilder.String(),
+ Volatility: tree.VolatilityImmutable,
+ },
+ tree.Overload{
+ Types: tree.ArgTypes{
+ {"geometry_str", types.String},
+ // This should be float, but for this to work equivalently to the psql type system,
+ // we have to make a decimal definition.
+ {"distance", types.Decimal},
+ {"buffer_style_params", types.String},
+ },
+ ReturnType: tree.FixedReturnType(types.Geometry),
+ Fn: func(_ *tree.EvalContext, args tree.Datums) (tree.Datum, error) {
+ gStr := *args[0].(*tree.DString)
+ distance, err := args[1].(*tree.DDecimal).Float64()
+ if err != nil {
+ return nil, err
+ }
+ paramsString := *args[2].(*tree.DString)
+
+ g, err := geo.ParseGeometry(string(gStr))
+ if err != nil {
+ return nil, err
+ }
+
+ params, modifiedDistance, err := geomfn.ParseBufferParams(string(paramsString), distance)
+ if err != nil {
+ return nil, err
+ }
+
+ ret, err := geomfn.Buffer(
+ g,
+ params,
+ modifiedDistance,
+ )
+ if err != nil {
+ return nil, err
+ }
+ return tree.NewDGeometry(ret), nil
+ },
+ Info: stBufferWithParamsInfoBuilder.String(),
+ Volatility: tree.VolatilityImmutable,
+ },
+ ),
//
// Schema changes
diff --git a/pkg/testutils/lint/lint_test.go b/pkg/testutils/lint/lint_test.go
index 9a32604f8d1f..52eb2aac9c22 100644
--- a/pkg/testutils/lint/lint_test.go
+++ b/pkg/testutils/lint/lint_test.go
@@ -1069,6 +1069,7 @@ func TestLint(t *testing.T) {
ignoredRules := []string{
"licence",
+ "mitre", // PostGIS commands spell these as mitre.
"analyse", // required by SQL grammar
}