From 89dc4336dfcfe812a5a309e30489db5081b2569d Mon Sep 17 00:00:00 2001
From: Kumar Akshay
st_orderingequals(geometry_a: geometry, geometry_b: geometry) → bool
Returns true if geometry_a is exactly equal to geometry_b, having all coordinates in the same order, as well as the same type, SRID, bounding box, and so on.
st_orientedenvelope(geometry: geometry) → geometry
Returns a minimum rotated rectangle enclosing a geometry. +Note that more than one minimum rotated rectangle may exist. +May return a Point or LineString in the case of degenerate inputs.
+st_overlaps(geometry_a: geometry, geometry_b: geometry) → bool
Returns true if geometry_a intersects but does not completely contain geometry_b, or vice versa. “Does not completely” implies ST_Within(geometry_a, geometry_b) = ST_Within(geometry_b, geometry_a) = false.
This function utilizes the GEOS module.
This function variant will attempt to utilize any available spatial index.
diff --git a/pkg/geo/geomfn/topology_operations.go b/pkg/geo/geomfn/topology_operations.go index 329ca6606f13..d16e873bec22 100644 --- a/pkg/geo/geomfn/topology_operations.go +++ b/pkg/geo/geomfn/topology_operations.go @@ -188,3 +188,16 @@ func SharedPaths(a geo.Geometry, b geo.Geometry) (geo.Geometry, error) { } return gm, nil } + +// MinimumRotatedRectangle Returns a minimum rotated rectangle enclosing a geometry +func MinimumRotatedRectangle(g geo.Geometry) (geo.Geometry, error) { + paths, err := geos.MinimumRotatedRectangle(g.EWKB()) + if err != nil { + return geo.Geometry{}, err + } + gm, err := geo.ParseGeometryFromEWKB(paths) + if err != nil { + return geo.Geometry{}, err + } + return gm, nil +} diff --git a/pkg/geo/geomfn/topology_operations_test.go b/pkg/geo/geomfn/topology_operations_test.go index 0a8f22d80674..ea22fb23fbb4 100644 --- a/pkg/geo/geomfn/topology_operations_test.go +++ b/pkg/geo/geomfn/topology_operations_test.go @@ -490,3 +490,69 @@ func TestUnaryUnion(t *testing.T) { }) } } + +func TestMinimumRotatedRectangle(t *testing.T) { + tests := []struct { + name string + arg geo.Geometry + want geo.Geometry + }{ + { + "empty multipoint", + geo.MustParseGeometry("MULTIPOINT EMPTY"), + geo.MustParseGeometry("POLYGON EMPTY"), + }, + { + "multipoint, must return the valid polygon", + geo.MustParseGeometry("MULTIPOINT ((0 0), (-1 -1), (3 2))"), + geo.MustParseGeometry("POLYGON((3 2,2.88 2.16,-1.12 -0.84,-1 -1,3 2))"), + }, + { + "multipoint, must give linestring in case of degenerate input", + geo.MustParseGeometry("MULTIPOINT ((0 0), (-2 0), (1 0))"), + geo.MustParseGeometry("LINESTRING (-2 0, 1 0)"), + }, + { + "multipoint, must give point in case of degenerate input", + geo.MustParseGeometry("MULTIPOINT ((0 0), (0 0), (0 0))"), + geo.MustParseGeometry("POINT (0 0)"), + }, + { + "point, must return the valid point", + geo.MustParseGeometry("POINT (1 1)"), + geo.MustParseGeometry("POINT (1 1)"), + }, + { + "linestring, must return the valid polygon", + geo.MustParseGeometry("LINESTRING (0 0, 50 200, 100 0)"), + geo.MustParseGeometry("POLYGON ((0 0,94.1176470588235 -23.5294117647059,144.117647058824 176.470588235294,50 200,0 0))"), + }, + { + "polygon, must return the valid polygon", + geo.MustParseGeometry("POLYGON ((0 0,1 -2,1 1,5 2,0 0))"), + geo.MustParseGeometry("POLYGON ((-0.5 -0.5,1 -2,5 2,3.5 3.5,-0.5 -0.5))"), + }, + { + "multilinestring, must give linestring in case of degenerate input", + geo.MustParseGeometry("MULTILINESTRING ((1 1, 2 2))"), + geo.MustParseGeometry("LINESTRING (1 1,2 2)"), + }, + { + "multipolygon, must give linestring in case of degenerate input", + geo.MustParseGeometry("MULTIPOLYGON (((1 2, 3 4, 5 6, 1 2)))"), + geo.MustParseGeometry("LINESTRING (1 2,5 6)"), + }, + { + "multilinestring, must give linestring in case of degenerate input", + geo.MustParseGeometry("GEOMETRYCOLLECTION (MULTIPOINT (1 1, 2 2))"), + geo.MustParseGeometry("LINESTRING (1 1,2 2)"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := MinimumRotatedRectangle(tt.arg) + require.NoError(t, err) + require.Equal(t, true, EqualsExact(got, tt.want, 1e-6)) + }) + } +} diff --git a/pkg/geo/geomfn/voronoi.go b/pkg/geo/geomfn/voronoi.go index 319565610469..bdb705cd06ae 100644 --- a/pkg/geo/geomfn/voronoi.go +++ b/pkg/geo/geomfn/voronoi.go @@ -31,9 +31,9 @@ func VoronoiDiagram( if err != nil { return geo.Geometry{}, err } - gm, gmErr := geo.ParseGeometryFromEWKB(paths) - if gmErr != nil { - return geo.Geometry{}, gmErr + gm, err := geo.ParseGeometryFromEWKB(paths) + if err != nil { + return geo.Geometry{}, err } return gm, nil } diff --git a/pkg/geo/geos/geos.cc b/pkg/geo/geos/geos.cc index 3148940395df..d60083f7c1b9 100644 --- a/pkg/geo/geos/geos.cc +++ b/pkg/geo/geos/geos.cc @@ -167,6 +167,8 @@ typedef CR_GEOS_Geometry (*CR_GEOS_VoronoiDiagram_r)(CR_GEOS_Handle, CR_GEOS_Geo typedef char (*CR_GEOS_EqualsExact_r)(CR_GEOS_Handle, CR_GEOS_Geometry, CR_GEOS_Geometry, double); +typedef CR_GEOS_Geometry (*CR_GEOS_MinimumRotatedRectangle_r)(CR_GEOS_Handle, CR_GEOS_Geometry); + std::string ToString(CR_GEOS_Slice slice) { return std::string(slice.data, slice.len); } } // namespace @@ -267,6 +269,7 @@ struct CR_GEOS { CR_GEOS_SharedPaths_r GEOSSharedPaths_r; CR_GEOS_VoronoiDiagram_r GEOSVoronoiDiagram_r; CR_GEOS_EqualsExact_r GEOSEqualsExact_r; + CR_GEOS_MinimumRotatedRectangle_r GEOSMinimumRotatedRectangle_r; CR_GEOS_Node_r GEOSNode_r; @@ -351,6 +354,7 @@ struct CR_GEOS { INIT(GEOSRelate_r); INIT(GEOSVoronoiDiagram_r); INIT(GEOSEqualsExact_r); + INIT(GEOSMinimumRotatedRectangle_r); INIT(GEOSRelateBoundaryNodeRule_r); INIT(GEOSRelatePattern_r); INIT(GEOSSharedPaths_r); @@ -1419,3 +1423,22 @@ CR_GEOS_Status CR_GEOS_EqualsExact(CR_GEOS* lib, CR_GEOS_Slice lhs, CR_GEOS_Slic lib->GEOS_finish_r(handle); return toGEOSString(error.data(), error.length()); } + +CR_GEOS_Status CR_GEOS_MinimumRotatedRectangle(CR_GEOS* lib, CR_GEOS_Slice g, CR_GEOS_String* ret) { + std::string error; + auto handle = initHandleWithErrorBuffer(lib, &error); + auto gGeom = CR_GEOS_GeometryFromSlice(lib, handle, g); + *ret = {.data = NULL, .len = 0}; + if (gGeom != nullptr) { + auto r = lib->GEOSMinimumRotatedRectangle_r(handle, gGeom); + if (r != NULL) { + auto srid = lib->GEOSGetSRID_r(handle, r); + CR_GEOS_writeGeomToEWKB(lib, handle, r, ret, srid); + lib->GEOSGeom_destroy_r(handle, r); + } + lib->GEOSGeom_destroy_r(handle, gGeom); + } + + lib->GEOS_finish_r(handle); + return toGEOSString(error.data(), error.length()); +} diff --git a/pkg/geo/geos/geos.go b/pkg/geo/geos/geos.go index d32087d6cc15..47d5d63f2a70 100644 --- a/pkg/geo/geos/geos.go +++ b/pkg/geo/geos/geos.go @@ -1017,3 +1017,18 @@ func VoronoiDiagram(a, env geopb.EWKB, tolerance float64, onlyEdges bool) (geopb } return cStringToSafeGoBytes(cEWKB), nil } + +// MinimumRotatedRectangle Returns a minimum rotated rectangle enclosing a geometry +func MinimumRotatedRectangle(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_MinimumRotatedRectangle(g, goToCSlice(ewkb), &cEWKB), + ); err != nil { + return nil, err + } + return cStringToSafeGoBytes(cEWKB), nil +} diff --git a/pkg/geo/geos/geos.h b/pkg/geo/geos/geos.h index 4f0660b13f7a..5a6eef639821 100644 --- a/pkg/geo/geos/geos.h +++ b/pkg/geo/geos/geos.h @@ -124,6 +124,8 @@ CR_GEOS_Status CR_GEOS_Node(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_String* ret); CR_GEOS_Status CR_GEOS_MinimumBoundingCircle(CR_GEOS* lib, CR_GEOS_Slice a, double* radius, CR_GEOS_String* centerEWKB, CR_GEOS_String* polygonEWKB); + +CR_GEOS_Status CR_GEOS_MinimumRotatedRectangle(CR_GEOS* lib, CR_GEOS_Slice g, CR_GEOS_String* ret); // // Linear reference. // diff --git a/pkg/sql/logictest/testdata/logic_test/geospatial b/pkg/sql/logictest/testdata/logic_test/geospatial index 7df5aa9a0906..a4f1378dee87 100644 --- a/pkg/sql/logictest/testdata/logic_test/geospatial +++ b/pkg/sql/logictest/testdata/logic_test/geospatial @@ -5671,3 +5671,10 @@ query T SELECT ST_AsText(ST_VoronoiLines(ST_GeomFromText('MULTIPOINT(50 30, 60 30, 100 100,10 150, 110 120)'))); ---- MULTILINESTRING ((100.5 270, 59.347826086956523 132.826086956521749), (59.347826086956523 132.826086956521749, 36.81818181818182 92.272727272727266), (36.81818181818182 92.272727272727266, -110 43.333333333333321), (36.81818181818182 92.272727272727266, 55 79.285714285714278), (55 79.285714285714278, 55 -90), (59.347826086956523 132.826086956521749, 230 47.5), (230 -20.714285714285733, 55 79.285714285714278)) + +subtest st_orientedenvelope + +query T +SELECT ST_AsText(ST_OrientedEnvelope(ST_GeomFromText('MULTIPOINT ((0 0), (-1 -1), (3 2))'))); +---- +POLYGON ((3 2, 2.88 2.16, -1.12 -0.84, -1 -1, 3 2)) diff --git a/pkg/sql/sem/builtins/geo_builtins.go b/pkg/sql/sem/builtins/geo_builtins.go index 542a0b2567f5..6bdbcff4ea0b 100644 --- a/pkg/sql/sem/builtins/geo_builtins.go +++ b/pkg/sql/sem/builtins/geo_builtins.go @@ -5830,6 +5830,29 @@ See http://developers.google.com/maps/documentation/utilities/polylinealgorithm` Volatility: tree.VolatilityImmutable, }, ), + "st_orientedenvelope": makeBuiltin( + defProps(), + tree.Overload{ + Types: tree.ArgTypes{ + {"geometry", types.Geometry}, + }, + ReturnType: tree.FixedReturnType(types.Geometry), + Fn: func(ctx *tree.EvalContext, args tree.Datums) (tree.Datum, error) { + g := tree.MustBeDGeometry(args[0]) + ret, err := geomfn.MinimumRotatedRectangle(g.Geometry) + if err != nil { + return nil, err + } + return tree.NewDGeometry(ret), nil + }, + Info: infoBuilder{ + info: `Returns a minimum rotated rectangle enclosing a geometry. +Note that more than one minimum rotated rectangle may exist. +May return a Point or LineString in the case of degenerate inputs.`, + }.String(), + Volatility: tree.VolatilityImmutable, + }, + ), // // Unimplemented. @@ -5860,7 +5883,6 @@ See http://developers.google.com/maps/documentation/utilities/polylinealgorithm` "st_lengthspheroid": makeBuiltin(tree.FunctionProperties{UnsupportedWithIssue: 48968}), "st_linecrossingdirection": makeBuiltin(tree.FunctionProperties{UnsupportedWithIssue: 48969}), "st_linesubstring": makeBuiltin(tree.FunctionProperties{UnsupportedWithIssue: 48975}), - "st_orientedenvelope": makeBuiltin(tree.FunctionProperties{UnsupportedWithIssue: 49003}), "st_polygonize": makeBuiltin(tree.FunctionProperties{UnsupportedWithIssue: 49011}), "st_quantizecoordinates": makeBuiltin(tree.FunctionProperties{UnsupportedWithIssue: 49012}), "st_seteffectivearea": makeBuiltin(tree.FunctionProperties{UnsupportedWithIssue: 49030}),