Commit 64964031 authored by Oliver Tan's avatar Oliver Tan

geo/geomfn: implement Intersection, PointOnSurface, Union

The last of the topology operators up to Chapter 20.

Release note (sql change): Implements the ST_Intersection,
ST_PointOnSurface and ST_Union builtin functions.
parent 82f172ab
......@@ -931,6 +931,9 @@ given Geometry.</p>
</span></td></tr>
<tr><td><a name="st_interiorringn"></a><code>st_interiorringn(geometry: geometry, n: <a href="int.html">int</a>) &rarr; geometry</code></td><td><span class="funcdesc"><p>Returns the n-th (1-indexed) interior ring of a Polygon as a LineString. Returns NULL if the shape is not a Polygon, or the ring does not exist.</p>
</span></td></tr>
<tr><td><a name="st_intersection"></a><code>st_intersection(geometry_a: geometry, geometry_b: geometry) &rarr; geometry</code></td><td><span class="funcdesc"><p>Returns the point intersections of the given geometries.</p>
<p>This function utilizes the GEOS module.</p>
</span></td></tr>
<tr><td><a name="st_intersects"></a><code>st_intersects(geography_a: geography, geography_b: geography) &rarr; <a href="bool.html">bool</a></code></td><td><span class="funcdesc"><p>Returns true if geography_a shares any portion of space with geography_b.</p>
<p>The calculations performed are have a precision of 1cm.</p>
<p>This function utilizes the S2 library for spherical calculations.</p>
......@@ -1088,6 +1091,9 @@ given Geometry.</p>
</span></td></tr>
<tr><td><a name="st_pointn"></a><code>st_pointn(geometry: geometry, n: <a href="int.html">int</a>) &rarr; geometry</code></td><td><span class="funcdesc"><p>Returns the n-th Point of a LineString (1-indexed). Returns NULL if out of bounds or not a LineString.</p>
</span></td></tr>
<tr><td><a name="st_pointonsurface"></a><code>st_pointonsurface(geometry: geometry) &rarr; geometry</code></td><td><span class="funcdesc"><p>Returns a point that intersects with the given Geometry.</p>
<p>This function utilizes the GEOS module.</p>
</span></td></tr>
<tr><td><a name="st_polyfromtext"></a><code>st_polyfromtext(str: <a href="string.html">string</a>, srid: <a href="int.html">int</a>) &rarr; geometry</code></td><td><span class="funcdesc"><p>Returns the Geometry from a WKT or EWKT representation with an SRID. If the shape underneath is not Polygon, NULL is returned. If the SRID is present in both the EWKT and the argument, the argument value is used.</p>
</span></td></tr>
<tr><td><a name="st_polyfromtext"></a><code>st_polyfromtext(val: <a href="string.html">string</a>) &rarr; geometry</code></td><td><span class="funcdesc"><p>Returns the Geometry from a WKT or EWKT representation. If the shape underneath is not Polygon, NULL is returned.</p>
......@@ -1160,6 +1166,9 @@ given Geometry.</p>
<tr><td><a name="st_transform"></a><code>st_transform(geometry: geometry, to_proj_text: <a href="string.html">string</a>) &rarr; geometry</code></td><td><span class="funcdesc"><p>Transforms a geometry into the coordinate reference system referenced by the projection text by projecting its coordinates.</p>
<p>This function utilizes the PROJ library for coordinate projections.</p>
</span></td></tr>
<tr><td><a name="st_union"></a><code>st_union(geometry_a: geometry, geometry_b: geometry) &rarr; geometry</code></td><td><span class="funcdesc"><p>Returns the union of the given geometries as a single Geometry object.</p>
<p>This function utilizes the GEOS module.</p>
</span></td></tr>
<tr><td><a name="st_within"></a><code>st_within(geometry_a: geometry, geometry_b: geometry) &rarr; <a href="bool.html">bool</a></code></td><td><span class="funcdesc"><p>Returns true if geometry_a is completely inside geometry_b.</p>
<p>This function utilizes the GEOS module.</p>
<p>This function will automatically use any available index.</p>
......
......@@ -241,6 +241,12 @@ func (g *Geometry) BoundingBoxIntersects(o *Geometry) bool {
return g.SpatialObject.BoundingBox.Intersects(o.SpatialObject.BoundingBox)
}
// Layout returns the geom layout of the given geometry.
func (g *Geometry) Layout() geom.Layout {
// We are currently always 2D.
return geom.XY
}
//
// Geography
//
......
// 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 (
"github.com/cockroachdb/cockroach/pkg/geo"
"github.com/cockroachdb/cockroach/pkg/geo/geos"
"github.com/twpayne/go-geom"
)
// Centroid returns the Centroid of a given Geometry.
func Centroid(g *geo.Geometry) (*geo.Geometry, error) {
if g.Empty() {
return geo.NewGeometryFromGeom(geom.NewPointEmpty(g.Layout()))
}
centroidEWKB, err := geos.Centroid(g.EWKB())
if err != nil {
return nil, err
}
return geo.ParseGeometryFromEWKB(centroidEWKB)
}
// PointOnSurface returns the PointOnSurface of a given Geometry.
func PointOnSurface(g *geo.Geometry) (*geo.Geometry, error) {
if g.Empty() {
return geo.NewGeometryFromGeom(geom.NewPointEmpty(g.Layout()))
}
pointOnSurfaceEWKB, err := geos.PointOnSurface(g.EWKB())
if err != nil {
return nil, err
}
return geo.ParseGeometryFromEWKB(pointOnSurfaceEWKB)
}
// Intersection returns the geometries of intersection between A and B.
func Intersection(a *geo.Geometry, b *geo.Geometry) (*geo.Geometry, error) {
if a.SRID() != b.SRID() {
return nil, geo.NewMismatchingSRIDsError(a, b)
}
retEWKB, err := geos.Intersection(a.EWKB(), b.EWKB())
if err != nil {
return nil, err
}
return geo.ParseGeometryFromEWKB(retEWKB)
}
// Union returns the geometries of intersection between A and B.
func Union(a *geo.Geometry, b *geo.Geometry) (*geo.Geometry, error) {
if a.SRID() != b.SRID() {
return nil, geo.NewMismatchingSRIDsError(a, b)
}
retEWKB, err := geos.Union(a.EWKB(), b.EWKB())
if err != nil {
return nil, err
}
return geo.ParseGeometryFromEWKB(retEWKB)
}
// 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 (
"fmt"
"testing"
"github.com/cockroachdb/cockroach/pkg/geo"
"github.com/stretchr/testify/require"
"github.com/twpayne/go-geom"
)
func TestCentroid(t *testing.T) {
testCases := []struct {
wkt string
expected string
}{
{"POINT(1.0 1.0)", "POINT (1.0 1.0)"},
{"SRID=4326;POINT(1.0 1.0)", "SRID=4326;POINT (1.0 1.0)"},
{"LINESTRING(1.0 1.0, 2.0 2.0, 3.0 3.0)", "POINT (2.0 2.0)"},
{"POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 0.0))", "POINT (0.666666666666667 0.333333333333333)"},
{"POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 0.0), (0.1 0.1, 0.2 0.1, 0.2 0.2, 0.1 0.1))", "POINT (0.671717171717172 0.335353535353535)"},
{"MULTIPOINT((1.0 1.0), (2.0 2.0))", "POINT (1.5 1.5)"},
{"MULTILINESTRING((1.0 1.0, 2.0 2.0, 3.0 3.0), (6.0 6.0, 7.0 6.0))", "POINT (3.17541743733684 3.04481549985497)"},
{"MULTIPOLYGON(((3.0 3.0, 4.0 3.0, 4.0 4.0, 3.0 3.0)), ((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 0.0), (0.1 0.1, 0.2 0.1, 0.2 0.2, 0.1 0.1)))", "POINT (2.17671691792295 1.84187604690117)"},
{"GEOMETRYCOLLECTION (POINT (40 10),LINESTRING (10 10, 20 20, 10 40),POLYGON ((40 40, 20 45, 45 30, 40 40)))", "POINT (35 38.3333333333333)"},
}
for _, tc := range testCases {
t.Run(tc.wkt, func(t *testing.T) {
g, err := geo.ParseGeometry(tc.wkt)
require.NoError(t, err)
ret, err := Centroid(g)
require.NoError(t, err)
retAsGeomT, err := ret.AsGeomT()
require.NoError(t, err)
expected, err := geo.ParseGeometry(tc.expected)
require.NoError(t, err)
expectedAsGeomT, err := expected.AsGeomT()
require.NoError(t, err)
// Ensure points are close in terms of precision.
require.InEpsilon(t, expectedAsGeomT.(*geom.Point).X(), retAsGeomT.(*geom.Point).X(), 2e-10)
require.InEpsilon(t, expectedAsGeomT.(*geom.Point).Y(), retAsGeomT.(*geom.Point).Y(), 2e-10)
require.Equal(t, expected.SRID(), ret.SRID())
})
}
}
func TestPointOnSurface(t *testing.T) {
testCases := []struct {
wkt string
expected string
}{
{"POINT(1.0 1.0)", "POINT (1.0 1.0)"},
{"SRID=4326;POINT(1.0 1.0)", "SRID=4326;POINT (1.0 1.0)"},
{"LINESTRING(1.0 1.0, 2.0 2.0, 3.0 3.0)", "POINT (2.0 2.0)"},
{"POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 0.0))", "POINT(0.75 0.5)"},
{"POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 0.0), (0.1 0.1, 0.2 0.1, 0.2 0.2, 0.1 0.1))", "POINT(0.8 0.6)"},
{"MULTIPOINT((1.0 1.0), (2.0 2.0))", "POINT (1 1)"},
{"MULTILINESTRING((1.0 1.0, 2.0 2.0, 3.0 3.0), (6.0 6.0, 7.0 6.0))", "POINT (2 2)"},
{"MULTIPOLYGON(((3.0 3.0, 4.0 3.0, 4.0 4.0, 3.0 3.0)), ((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 0.0), (0.1 0.1, 0.2 0.1, 0.2 0.2, 0.1 0.1)))", "POINT(3.75 3.5)"},
{"GEOMETRYCOLLECTION (POINT (40 10),LINESTRING (10 10, 20 20, 10 40),POLYGON ((40 40, 20 45, 45 30, 40 40)))", "POINT(39.5833333333333 35)"},
}
for _, tc := range testCases {
t.Run(tc.wkt, func(t *testing.T) {
g, err := geo.ParseGeometry(tc.wkt)
require.NoError(t, err)
ret, err := PointOnSurface(g)
require.NoError(t, err)
retAsGeomT, err := ret.AsGeomT()
require.NoError(t, err)
expected, err := geo.ParseGeometry(tc.expected)
require.NoError(t, err)
expectedAsGeomT, err := expected.AsGeomT()
require.NoError(t, err)
// Ensure points are close in terms of precision.
require.InEpsilon(t, expectedAsGeomT.(*geom.Point).X(), retAsGeomT.(*geom.Point).X(), 2e-10)
require.InEpsilon(t, expectedAsGeomT.(*geom.Point).Y(), retAsGeomT.(*geom.Point).Y(), 2e-10)
require.Equal(t, expected.SRID(), ret.SRID())
})
}
}
func TestIntersection(t *testing.T) {
testCases := []struct {
a *geo.Geometry
b *geo.Geometry
expected *geo.Geometry
}{
{rightRect, rightRect, geo.MustParseGeometry("POLYGON ((1 0, 0 0, 0 1, 1 1, 1 0))")},
{rightRect, rightRectPoint, rightRectPoint},
{rightRectPoint, rightRectPoint, rightRectPoint},
}
for i, tc := range testCases {
t.Run(fmt.Sprintf("tc:%d", i), func(t *testing.T) {
g, err := Intersection(tc.a, tc.b)
require.NoError(t, err)
require.Equal(t, tc.expected, g)
})
}
t.Run("errors if SRIDs mismatch", func(t *testing.T) {
_, err := Intersection(mismatchingSRIDGeometryA, mismatchingSRIDGeometryB)
requireMismatchingSRIDError(t, err)
})
}
func TestUnion(t *testing.T) {
testCases := []struct {
a *geo.Geometry
b *geo.Geometry
expected *geo.Geometry
}{
{rightRect, rightRect, geo.MustParseGeometry("POLYGON ((1 0, 0 0, 0 1, 1 1, 1 0))")},
{rightRect, rightRectPoint, geo.MustParseGeometry("POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))")},
{rightRectPoint, rightRectPoint, rightRectPoint},
{leftRect, rightRect, geo.MustParseGeometry("POLYGON ((0 0, -1 0, -1 1, 0 1, 1 1, 1 0, 0 0))")},
}
for i, tc := range testCases {
t.Run(fmt.Sprintf("tc:%d", i), func(t *testing.T) {
g, err := Union(tc.a, tc.b)
require.NoError(t, err)
require.Equal(t, tc.expected, g)
})
}
t.Run("errors if SRIDs mismatch", func(t *testing.T) {
_, err := Union(mismatchingSRIDGeometryA, mismatchingSRIDGeometryB)
requireMismatchingSRIDError(t, err)
})
}
......@@ -19,25 +19,6 @@ import (
"github.com/twpayne/go-geom/encoding/ewkb"
)
// Centroid returns the Centroid of a given Geometry.
func Centroid(g *geo.Geometry) (*geo.Geometry, error) {
// Empty geometries do not react well in GEOS, so we have to
// convert and check beforehand.
// Remove after #49209 is resolved.
t, err := g.AsGeomT()
if err != nil {
return nil, err
}
if t.Empty() {
return geo.NewGeometryFromGeom(geom.NewPointEmpty(geom.XY))
}
centroidEWKB, err := geos.Centroid(g.EWKB())
if err != nil {
return nil, err
}
return geo.ParseGeometryFromEWKB(centroidEWKB)
}
// Length returns the length of a given Geometry.
// Note only (MULTI)LINESTRING objects have a length.
// (MULTI)POLYGON objects should use Perimeter.
......
......@@ -15,48 +15,8 @@ import (
"github.com/cockroachdb/cockroach/pkg/geo"
"github.com/stretchr/testify/require"
"github.com/twpayne/go-geom"
)
func TestCentroid(t *testing.T) {
testCases := []struct {
wkt string
expected string
}{
{"POINT(1.0 1.0)", "POINT (1.0 1.0)"},
{"SRID=4326;POINT(1.0 1.0)", "SRID=4326;POINT (1.0 1.0)"},
{"LINESTRING(1.0 1.0, 2.0 2.0, 3.0 3.0)", "POINT (2.0 2.0)"},
{"POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 0.0))", "POINT (0.666666666666667 0.333333333333333)"},
{"POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 0.0), (0.1 0.1, 0.2 0.1, 0.2 0.2, 0.1 0.1))", "POINT (0.671717171717172 0.335353535353535)"},
{"MULTIPOINT((1.0 1.0), (2.0 2.0))", "POINT (1.5 1.5)"},
{"MULTILINESTRING((1.0 1.0, 2.0 2.0, 3.0 3.0), (6.0 6.0, 7.0 6.0))", "POINT (3.17541743733684 3.04481549985497)"},
{"MULTIPOLYGON(((3.0 3.0, 4.0 3.0, 4.0 4.0, 3.0 3.0)), ((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 0.0), (0.1 0.1, 0.2 0.1, 0.2 0.2, 0.1 0.1)))", "POINT (2.17671691792295 1.84187604690117)"},
{"GEOMETRYCOLLECTION (POINT (40 10),LINESTRING (10 10, 20 20, 10 40),POLYGON ((40 40, 20 45, 45 30, 40 40)))", "POINT (35 38.3333333333333)"},
}
for _, tc := range testCases {
t.Run(tc.wkt, func(t *testing.T) {
g, err := geo.ParseGeometry(tc.wkt)
require.NoError(t, err)
ret, err := Centroid(g)
require.NoError(t, err)
retAsGeomT, err := ret.AsGeomT()
require.NoError(t, err)
expected, err := geo.ParseGeometry(tc.expected)
require.NoError(t, err)
expectedAsGeomT, err := expected.AsGeomT()
require.NoError(t, err)
// Ensure points are close in terms of precision.
require.InEpsilon(t, expectedAsGeomT.(*geom.Point).X(), retAsGeomT.(*geom.Point).X(), 2e-10)
require.InEpsilon(t, expectedAsGeomT.(*geom.Point).Y(), retAsGeomT.(*geom.Point).Y(), 2e-10)
require.Equal(t, expected.SRID(), ret.SRID())
})
}
}
func TestLength(t *testing.T) {
testCases := []struct {
wkt string
......
......@@ -81,7 +81,12 @@ typedef CR_GEOS_Geometry (*CR_GEOS_BufferWithParams_r)(CR_GEOS_Handle, CR_GEOS_G
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);
typedef CR_GEOS_Geometry (*CR_GEOS_Union_r)(CR_GEOS_Handle, CR_GEOS_Geometry, CR_GEOS_Geometry);
typedef CR_GEOS_Geometry (*CR_GEOS_Intersection_r)(CR_GEOS_Handle, CR_GEOS_Geometry,
CR_GEOS_Geometry);
typedef CR_GEOS_Geometry (*CR_GEOS_PointOnSurface_r)(CR_GEOS_Handle, CR_GEOS_Geometry);
typedef CR_GEOS_Geometry (*CR_GEOS_Interpolate_r)(CR_GEOS_Handle, CR_GEOS_Geometry, double);
......@@ -147,7 +152,11 @@ struct CR_GEOS {
CR_GEOS_Area_r GEOSArea_r;
CR_GEOS_Length_r GEOSLength_r;
CR_GEOS_Centroid_r GEOSGetCentroid_r;
CR_GEOS_Union_r GEOSUnion_r;
CR_GEOS_PointOnSurface_r GEOSPointOnSurface_r;
CR_GEOS_Intersection_r GEOSIntersection_r;
CR_GEOS_Interpolate_r GEOSInterpolate_r;
......@@ -209,6 +218,9 @@ struct CR_GEOS {
INIT(GEOSArea_r);
INIT(GEOSLength_r);
INIT(GEOSGetCentroid_r);
INIT(GEOSUnion_r);
INIT(GEOSPointOnSurface_r);
INIT(GEOSIntersection_r);
INIT(GEOSInterpolate_r);
INIT(GEOSDistance_r);
INIT(GEOSCovers_r);
......@@ -442,6 +454,10 @@ 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);
}
//
// Topology operators.
//
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);
......@@ -460,34 +476,107 @@ 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_Union(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_Slice b,
CR_GEOS_String* unionEWKB) {
std::string error;
auto handle = initHandleWithErrorBuffer(lib, &error);
*unionEWKB = {.data = NULL, .len = 0};
auto geomA = CR_GEOS_GeometryFromSlice(lib, handle, a);
auto geomB = CR_GEOS_GeometryFromSlice(lib, handle, b);
if (geomA != nullptr && geomB != nullptr) {
auto unionGeom = lib->GEOSUnion_r(handle, geomA, geomB);
if (unionGeom != nullptr) {
auto srid = lib->GEOSGetSRID_r(handle, geomA);
CR_GEOS_writeGeomToEWKB(lib, handle, unionGeom, unionEWKB, srid);
lib->GEOSGeom_destroy_r(handle, unionGeom);
}
}
if (geomA != nullptr) {
lib->GEOSGeom_destroy_r(handle, geomA);
}
if (geomB != nullptr) {
lib->GEOSGeom_destroy_r(handle, geomB);
}
lib->GEOS_finish_r(handle);
return toGEOSString(error.data(), error.length());
}
CR_GEOS_Status CR_GEOS_PointOnSurface(CR_GEOS* lib, CR_GEOS_Slice a,
CR_GEOS_String* pointOnSurfaceEWKB) {
std::string error;
auto handle = initHandleWithErrorBuffer(lib, &error);
auto geom = CR_GEOS_GeometryFromSlice(lib, handle, a);
*pointOnSurfaceEWKB = {.data = NULL, .len = 0};
if (geom != nullptr) {
auto pointOnSurfaceGeom = lib->GEOSPointOnSurface_r(handle, geom);
if (pointOnSurfaceGeom != nullptr) {
auto srid = lib->GEOSGetSRID_r(handle, geom);
CR_GEOS_writeGeomToEWKB(lib, handle, pointOnSurfaceGeom, pointOnSurfaceEWKB, srid);
lib->GEOSGeom_destroy_r(handle, pointOnSurfaceGeom);
}
lib->GEOSGeom_destroy_r(handle, geom);
}
lib->GEOS_finish_r(handle);
return toGEOSString(error.data(), error.length());
}
CR_GEOS_Status CR_GEOS_Intersection(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_Slice b,
CR_GEOS_String* intersectionEWKB) {
std::string error;
auto handle = initHandleWithErrorBuffer(lib, &error);
*intersectionEWKB = {.data = NULL, .len = 0};
auto geomA = CR_GEOS_GeometryFromSlice(lib, handle, a);
auto geomB = CR_GEOS_GeometryFromSlice(lib, handle, b);
if (geomA != nullptr && geomB != nullptr) {
auto intersectionGeom = lib->GEOSIntersection_r(handle, geomA, geomB);
if (intersectionGeom != nullptr) {
auto srid = lib->GEOSGetSRID_r(handle, geomA);
CR_GEOS_writeGeomToEWKB(lib, handle, intersectionGeom, intersectionEWKB, srid);
lib->GEOSGeom_destroy_r(handle, intersectionGeom);
}
}
if (geomA != nullptr) {
lib->GEOSGeom_destroy_r(handle, geomA);
}
if (geomB != nullptr) {
lib->GEOSGeom_destroy_r(handle, geomB);
}
lib->GEOS_finish_r(handle);
return toGEOSString(error.data(), error.length());
}
//
// Linear Reference
//
CR_GEOS_Status CR_GEOS_Interpolate(CR_GEOS* lib, CR_GEOS_Slice a, double distance,
CR_GEOS_String* interpolatedPointEWKB) {
std::string error;
auto handle = initHandleWithErrorBuffer(lib, &error);
auto geom = CR_GEOS_GeometryFromSlice(lib, handle, a);
*interpolatedPointEWKB = {.data = NULL, .len = 0};
if (geom != nullptr) {
auto interpolatedPoint = lib->GEOSInterpolate_r(handle, geom, distance);
if (interpolatedPoint != nullptr) {
auto srid = lib->GEOSGetSRID_r(handle, geom);
CR_GEOS_writeGeomToEWKB(lib, handle, interpolatedPoint, interpolatedPointEWKB, srid);
lib->GEOSGeom_destroy_r(handle, interpolatedPoint);
}
lib->GEOSGeom_destroy_r(handle, geom);
}
lib->GEOS_finish_r(handle);
return toGEOSString(error.data(), error.length());
std::string error;
auto handle = initHandleWithErrorBuffer(lib, &error);
auto geom = CR_GEOS_GeometryFromSlice(lib, handle, a);
*interpolatedPointEWKB = {.data = NULL, .len = 0};
if (geom != nullptr) {
auto interpolatedPoint = lib->GEOSInterpolate_r(handle, geom, distance);
if (interpolatedPoint != nullptr) {
auto srid = lib->GEOSGetSRID_r(handle, geom);
CR_GEOS_writeGeomToEWKB(lib, handle, interpolatedPoint, interpolatedPointEWKB, srid);
lib->GEOSGeom_destroy_r(handle, interpolatedPoint);
}
lib->GEOSGeom_destroy_r(handle, geom);
}
lib->GEOS_finish_r(handle);
return toGEOSString(error.data(), error.length());
}
//
// 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) {
return CR_GEOS_BinaryOperator(lib, lib->GEOSDistance_r, a, b, ret);
}
......
......@@ -320,6 +320,45 @@ func Centroid(ewkb geopb.EWKB) (geopb.EWKB, error) {
return cStringToSafeGoBytes(cEWKB), nil
}
// PointOnSurface returns an EWKB with a point that is on the surface of the given EWKB.
func PointOnSurface(ewkb geopb.EWKB) (geopb.EWKB, error) {
g, err := ensureInitInternal()
if err != nil {
return nil, err
}
var cEWKB C.CR_GEOS_String
if err := statusToError(C.CR_GEOS_PointOnSurface(g, goToCSlice(ewkb), &cEWKB)); err != nil {
return nil, err
}
return cStringToSafeGoBytes(cEWKB), nil
}
// Intersection returns an EWKB which contains the geometries of intersection between A and B.
func Intersection(a geopb.EWKB, b geopb.EWKB) (geopb.EWKB, error) {
g, err := ensureInitInternal()
if err != nil {
return nil, err
}
var cEWKB C.CR_GEOS_String
if err := statusToError(C.CR_GEOS_Intersection(g, goToCSlice(a), goToCSlice(b), &cEWKB)); err != nil {
return nil, err
}
return cStringToSafeGoBytes(cEWKB), nil
}
// Union returns an EWKB which is a union of shapes A and B.
func Union(a geopb.EWKB, b geopb.EWKB) (geopb.EWKB, error) {
g, err := ensureInitInternal()
if err != nil {
return nil, err
}
var cEWKB C.CR_GEOS_String
if err := statusToError(C.CR_GEOS_Union(g, goToCSlice(a), goToCSlice(b), &cEWKB)); err != nil {
return nil, err
}
return cStringToSafeGoBytes(cEWKB), nil
}
// InterpolateLine returns the point along the given LineString which is at
// a given distance from starting point.
// Note: For distance less than 0 it returns start point similarly for distance
......
......@@ -75,13 +75,24 @@ CR_GEOS_Status CR_GEOS_Buffer(CR_GEOS* lib, CR_GEOS_Slice ewkb, CR_GEOS_BufferPa
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);
//
// Topology operators.
//
CR_GEOS_Status CR_GEOS_Centroid(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_String* centroidEWKB);
CR_GEOS_Status CR_GEOS_Union(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_Slice b,
CR_GEOS_String* unionEWKB);
CR_GEOS_Status CR_GEOS_PointOnSurface(CR_GEOS* lib, CR_GEOS_Slice a,
CR_GEOS_String* pointOnSurfaceEWKB);
CR_GEOS_Status CR_GEOS_Intersection(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_Slice b,
CR_GEOS_String* intersectionEWKB);
//
// Linear reference.
//
CR_GEOS_Status CR_GEOS_Interpolate(CR_GEOS *lib, CR_GEOS_Slice a, double distance,
CR_GEOS_String *interpolatedPoint);
CR_GEOS_Status CR_GEOS_Interpolate(CR_GEOS* lib, CR_GEOS_Slice a, double distance,
CR_GEOS_String* interpolatedPoint);
//
// Binary operators.
......
......@@ -1461,43 +1461,6 @@ Note ST_Perimeter is only valid for Polygon - use ST_Length for LineString.`,
tree.VolatilityImmutable,
),
),
"st_centroid": makeBuiltin(
defProps(),
geometryOverload1(
func(ctx *tree.EvalContext, g *tree.DGeometry) (tree.Datum, error) {
centroid, err := geomfn.Centroid(g.Geometry)
if err != nil {
return nil, err
}
return tree.NewDGeometry(centroid), err
},
types.Geometry,
infoBuilder{
info: "Returns the centroid of the given geometry.",
libraryUsage: usesGEOS,
},
tree.VolatilityImmutable,
),
stringOverload1(
func(ctx *tree.EvalContext, s string) (tree.Datum, error) {
g, err := geo.ParseGeometry(s)
if err != nil {
return nil, err
}
centroid, err := geomfn.Centroid(g)
if err != nil {
return nil, err
}
return tree.NewDGeometry(centroid), err
},
types.Geometry,
infoBuilder{
info: "Returns the centroid of the given string, which will be parsed as a geometry object.",
libraryUsage: usesGEOS,
}.String(),
tree.VolatilityImmutable,
),
),
"st_geometrytype": makeBuiltin(
defProps(),
geometryOverload1(
......@@ -1944,6 +1907,99 @@ Note If the result has zero or one points, it will be returned as a POINT. If it
},
),
// Topology operations
"st_centroid": makeBuiltin(
defProps(),
geometryOverload1(
func(ctx *tree.EvalContext, g *tree.DGeometry) (tree.Datum, error) {
centroid, err := geomfn.Centroid(g.Geometry)
if err != nil {
return nil, err
}
return tree.NewDGeometry(centroid), err
},
types.Geometry,
infoBuilder{
info: "Returns the centroid of the given geometry.",
libraryUsage: usesGEOS,
},
tree.VolatilityImmutable,
),
stringOverload1(
func(ctx *tree.EvalContext, s string) (tree.Datum, error) {
g, err := geo.ParseGeometry(s)
if err != nil {
return nil, err
}
centroid, err := geomfn.Centroid(g)
if err != nil {
return nil, err
}
return tree.NewDGeometry(centroid), err
},
types.Geometry,