Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

geo/geomfn: implement ST_Buffer #49722

Merged
merged 1 commit into from
Jun 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 63 additions & 1 deletion docs/generated/sql/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -744,6 +744,69 @@ has no relationship with the commit order of concurrent transactions.</p>
</span></td></tr>
<tr><td><a name="st_astext"></a><code>st_astext(geometry: geometry) &rarr; <a href="string.html">string</a></code></td><td><span class="funcdesc"><p>Returns the WKT representation of a given Geometry.</p>
</span></td></tr>
<tr><td><a name="st_buffer"></a><code>st_buffer(geometry: geometry, distance: <a href="float.html">float</a>) &rarr; geometry</code></td><td><span class="funcdesc"><p>Returns a Geometry that represents all points whose distance is less than or equal to the given distance
from the given Geometry.</p>
<p>This function utilizes the GEOS module.</p>
</span></td></tr>
<tr><td><a name="st_buffer"></a><code>st_buffer(geometry: geometry, distance: <a href="float.html">float</a>, buffer_style_params: <a href="string.html">string</a>) &rarr; geometry</code></td><td><span class="funcdesc"><p>Returns a Geometry that represents all points whose distance is less than or equal to the given distance from the
given Geometry.</p>
<p>This variant takes in a space separate parameter string, which will augment the buffer styles. Valid parameters are:</p>
<ul>
<li>quad_segs=&lt;int&gt;, default 8</li>
<li>endcap=&lt;round|flat|butt|square&gt;, default round</li>
<li>join=&lt;round|mitre|miter|bevel&gt;, default round</li>
<li>side=&lt;both|left|right&gt;, default both</li>
<li>mitre_limit=&lt;float&gt;, default 5.0</li>
</ul>
<p>This function utilizes the GEOS module.</p>
</span></td></tr>
<tr><td><a name="st_buffer"></a><code>st_buffer(geometry: geometry, distance: <a href="float.html">float</a>, quad_segs: <a href="int.html">int</a>) &rarr; geometry</code></td><td><span class="funcdesc"><p>Returns a Geometry that represents all points whose distance is less than or equal to the given distance from the
given Geometry.</p>
<p>This variant approximates the circle into quad_seg segments per line (the default is 8).</p>
<p>This function utilizes the GEOS module.</p>
</span></td></tr>
<tr><td><a name="st_buffer"></a><code>st_buffer(geometry_str: <a href="string.html">string</a>, distance: <a href="decimal.html">decimal</a>) &rarr; geometry</code></td><td><span class="funcdesc"><p>Returns a Geometry that represents all points whose distance is less than or equal to the given distance
from the given Geometry.</p>
<p>This function utilizes the GEOS module.</p>
</span></td></tr>
<tr><td><a name="st_buffer"></a><code>st_buffer(geometry_str: <a href="string.html">string</a>, distance: <a href="decimal.html">decimal</a>, buffer_style_params: <a href="string.html">string</a>) &rarr; geometry</code></td><td><span class="funcdesc"><p>Returns a Geometry that represents all points whose distance is less than or equal to the given distance from the
given Geometry.</p>
<p>This variant takes in a space separate parameter string, which will augment the buffer styles. Valid parameters are:</p>
<ul>
<li>quad_segs=&lt;int&gt;, default 8</li>
<li>endcap=&lt;round|flat|butt|square&gt;, default round</li>
<li>join=&lt;round|mitre|miter|bevel&gt;, default round</li>
<li>side=&lt;both|left|right&gt;, default both</li>
<li>mitre_limit=&lt;float&gt;, default 5.0</li>
</ul>
<p>This function utilizes the GEOS module.</p>
</span></td></tr>
<tr><td><a name="st_buffer"></a><code>st_buffer(geometry_str: <a href="string.html">string</a>, distance: <a href="decimal.html">decimal</a>, quad_segs: <a href="int.html">int</a>) &rarr; geometry</code></td><td><span class="funcdesc"><p>Returns a Geometry that represents all points whose distance is less than or equal to the given distance from the
given Geometry.</p>
<p>This variant approximates the circle into quad_seg segments per line (the default is 8).</p>
<p>This function utilizes the GEOS module.</p>
</span></td></tr>
<tr><td><a name="st_buffer"></a><code>st_buffer(geometry_str: <a href="string.html">string</a>, distance: <a href="float.html">float</a>) &rarr; geometry</code></td><td><span class="funcdesc"><p>Returns a Geometry that represents all points whose distance is less than or equal to the given distance
from the given Geometry.</p>
<p>This function utilizes the GEOS module.</p>
</span></td></tr>
<tr><td><a name="st_buffer"></a><code>st_buffer(geometry_str: <a href="string.html">string</a>, distance: <a href="float.html">float</a>, buffer_style_params: <a href="string.html">string</a>) &rarr; geometry</code></td><td><span class="funcdesc"><p>Returns a Geometry that represents all points whose distance is less than or equal to the given distance from the
given Geometry.</p>
<p>This variant takes in a space separate parameter string, which will augment the buffer styles. Valid parameters are:</p>
<ul>
<li>quad_segs=&lt;int&gt;, default 8</li>
<li>endcap=&lt;round|flat|butt|square&gt;, default round</li>
<li>join=&lt;round|mitre|miter|bevel&gt;, default round</li>
<li>side=&lt;both|left|right&gt;, default both</li>
<li>mitre_limit=&lt;float&gt;, default 5.0</li>
</ul>
<p>This function utilizes the GEOS module.</p>
</span></td></tr>
<tr><td><a name="st_buffer"></a><code>st_buffer(geometry_str: <a href="string.html">string</a>, distance: <a href="float.html">float</a>, quad_segs: <a href="int.html">int</a>) &rarr; geometry</code></td><td><span class="funcdesc"><p>Returns a Geometry that represents all points whose distance is less than or equal to the given distance from the
given Geometry.</p>
<p>This variant approximates the circle into quad_seg segments per line (the default is 8).</p>
<p>This function utilizes the GEOS module.</p>
</span></td></tr>
<tr><td><a name="st_centroid"></a><code>st_centroid(geometry: geometry) &rarr; geometry</code></td><td><span class="funcdesc"><p>Returns the centroid of the given geometry.</p>
<p>This function utilizes the GEOS module.</p>
</span></td></tr>
Expand Down Expand Up @@ -1039,7 +1102,6 @@ has no relationship with the commit order of concurrent transactions.</p>
<tr><td><a name="st_segmentize"></a><code>st_segmentize(geography: geography, max_segment_length_meters: <a href="float.html">float</a>) &rarr; geography</code></td><td><span class="funcdesc"><p>Returns a modified Geography having no segment longer than the given max_segment_length meters.</p>
<p>The calculations are done on a sphere.</p>
<p>This function utilizes the S2 library for spherical calculations.</p>
<p>This function will automatically use any available index.</p>
</span></td></tr>
<tr><td><a name="st_setsrid"></a><code>st_setsrid(geography: geography, srid: <a href="int.html">int</a>) &rarr; geography</code></td><td><span class="funcdesc"><p>Sets a Geography to a new SRID without transforming the coordinates.</p>
</span></td></tr>
Expand Down
121 changes: 121 additions & 0 deletions pkg/geo/geomfn/buffer.go
Original file line number Diff line number Diff line change
@@ -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)
}
86 changes: 86 additions & 0 deletions pkg/geo/geomfn/buffer_test.go
Original file line number Diff line number Diff line change
@@ -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)
})
}
}
64 changes: 64 additions & 0 deletions pkg/geo/geos/geos.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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)();
Expand All @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
//
Expand Down
Loading