diff --git a/docs/generated/sql/functions.md b/docs/generated/sql/functions.md index 0b81187ed19a..aa7e9e1e7be0 100644 --- a/docs/generated/sql/functions.md +++ b/docs/generated/sql/functions.md @@ -744,6 +744,69 @@ 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 +from the given Geometry.

+

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 from the +given Geometry.

+

This variant takes in a space separate parameter string, which will augment the buffer styles. Valid parameters are:

+ +

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 from the +given Geometry.

+

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 +from the given Geometry.

+

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 from the +given Geometry.

+

This variant takes in a space separate parameter string, which will augment the buffer styles. Valid parameters are:

+ +

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 from the +given Geometry.

+

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 +from the given Geometry.

+

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 from the +given Geometry.

+

This variant takes in a space separate parameter string, which will augment the buffer styles. Valid parameters are:

+ +

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 from the +given Geometry.

+

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 +1102,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 2853834d4e22..ccc566e3835a 100644 --- a/pkg/geo/geos/geos.cc +++ b/pkg/geo/geos/geos.cc @@ -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); @@ -106,6 +122,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; @@ -167,6 +192,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); @@ -314,6 +347,37 @@ 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 // diff --git a/pkg/geo/geos/geos.go b/pkg/geo/geos/geos.go index 0e0002216f3e..a72ee407b5dc 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 cedb7791e369..9229ad1c417a 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,13 +58,16 @@ 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. @@ -92,6 +104,7 @@ CR_GEOS_Status CR_GEOS_Within(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_Slice b, ch 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_RelatePattern(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_Slice b, CR_GEOS_Slice pattern, char* 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 940037548597..e59ce610726c 100644 --- a/pkg/sql/logictest/testdata/logic_test/geospatial +++ b/pkg/sql/logictest/testdata/logic_test/geospatial @@ -757,6 +757,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 4860e943ee1a..bd52b2232c74 100644 --- a/pkg/sql/sem/builtins/geo_builtins.go +++ b/pkg/sql/sem/builtins/geo_builtins.go @@ -318,6 +318,33 @@ 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 +from the given Geometry.`, + libraryUsage: usesGEOS, +} + +var stBufferWithParamsInfoBuilder = infoBuilder{ + info: `Returns a Geometry that represents all points whose distance is less than or equal to the given distance from the +given Geometry. + +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 from the +given Geometry. + +This variant approximates the circle into quad_seg segments per line (the default is 8).`, + libraryUsage: usesGEOS, +} + var geoBuiltins = map[string]builtinDefinition{ // // Input (Geometry) @@ -1936,11 +1963,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 62ba0455f84a..5de11f73b268 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 }