From 616821a6445da748e596a4423db4c57d10504582 Mon Sep 17 00:00:00 2001 From: Oliver Tan Date: Fri, 8 May 2020 21:26:53 -0700 Subject: [PATCH] geo/geomfn: implement ST_Relate and ST_ContainsProperly Added ST_ContainsProperly to the optimizer as well that calls it to use Covers. Also update the RFC to claim ST_ContainsProperly as indexed backed. Release note (sql change): Implemented the geometry based builtins `ST_Relate` and `ST_ContainsProperly`. --- docs/RFCS/20200421_geospatial.md | 7 + docs/generated/sql/functions.md | 10 ++ pkg/geo/geomfn/binary_predicates.go | 10 ++ pkg/geo/geomfn/binary_predicates_test.go | 27 +++ pkg/geo/geomfn/de9im.go | 74 ++++++++ pkg/geo/geomfn/de9im_test.go | 72 ++++++++ pkg/geo/geos/geos.cc | 40 ++++- pkg/geo/geos/geos.go | 20 +++ pkg/geo/geos/geos.h | 5 + .../logictest/testdata/logic_test/geospatial | 162 ++++++++++++------ pkg/sql/opt/norm/fold_constants.go | 1 + pkg/sql/opt/xform/custom_funcs.go | 19 +- pkg/sql/sem/builtins/geo_builtins.go | 61 +++++++ 13 files changed, 448 insertions(+), 60 deletions(-) create mode 100644 pkg/geo/geomfn/de9im.go create mode 100644 pkg/geo/geomfn/de9im_test.go diff --git a/docs/RFCS/20200421_geospatial.md b/docs/RFCS/20200421_geospatial.md index e3839fe17a39..f06a56277df2 100644 --- a/docs/RFCS/20200421_geospatial.md +++ b/docs/RFCS/20200421_geospatial.md @@ -513,6 +513,7 @@ For 2D geometry and geography, these are: * ST_Covers * ST_CoveredBy * ST_Contains (geometry only) +* ST_ContainsProperly (geometry only) * ST_Crosses (geometry only) * ST_DFullyWithin (geometry only) * ST_DWithin @@ -1039,6 +1040,8 @@ Functions map to the index functions: contains(g, x) * ST_Contains(g, x), ST_Contains(x, g): use contains(g, x) or contained-by(g, x) +* ST_ContainsProperly(g, x), ST_ContainsProperly(x, g): use contains(g, x) or + contained-by(g, x) * ST_Crosses: use intersects * ST_DFullyWithin(g, x, d), ST_DFullyWithin(x, g, d): extend g by distance d to produce a shape g’, and then use contains(g', x). The @@ -1667,3 +1670,7 @@ good stretch option as well. ## Unresolved questions None beyond what is already mentioned in earlier text. + +# Updates +* 2020-05-07: + * added ST_ContainsProperly as an indexable function. diff --git a/docs/generated/sql/functions.md b/docs/generated/sql/functions.md index af6cb2862a85..19a2aa312103 100644 --- a/docs/generated/sql/functions.md +++ b/docs/generated/sql/functions.md @@ -703,6 +703,10 @@ has no relationship with the commit order of concurrent transactions.

This function uses the GEOS module.

This function will automatically use any available index.

+st_containsproperly(geometry_a: geometry, geometry_b: geometry) → bool

Returns true if geometry_b intersects the interior of geometry_a but not the boundary or exterior of geometry_a.

+

This function uses the GEOS module.

+

This function will automatically use any available index.

+
st_coveredby(geometry_a: geometry, geometry_b: geometry) → bool

Returns true if no point in geometry_a is outside geometry_b.

This function uses the GEOS module.

This function will automatically use any available index.

@@ -890,6 +894,12 @@ has no relationship with the commit order of concurrent transactions.

st_polygonfromwkb(wkb: bytes, srid: int) → geometry

Returns the Geometry from a WKB representation with an SRID. If the shape underneath is not Polygon, NULL is returned.

+st_relate(geometry_a: geometry, geometry_b: geometry) → string

Returns the DE-9IM spatial relation between geometry_a and geometry_b.

+

This function uses the GEOS module.

+
+st_relate(geometry_a: geometry, geometry_b: geometry, pattern: string) → bool

Returns whether the DE-9IM spatial relation between geometry_a and geometry_b matches the DE-9IM pattern.

+

This function uses the GEOS module.

+
st_touches(geometry_a: geometry, geometry_b: geometry) → bool

Returns true if the only points in common between geometry_a and geometry_b are on the boundary. Note points do not touch other points.

This function uses the GEOS module.

This function will automatically use any available index.

diff --git a/pkg/geo/geomfn/binary_predicates.go b/pkg/geo/geomfn/binary_predicates.go index 17a5ef415983..3f3b7c9e4cdb 100644 --- a/pkg/geo/geomfn/binary_predicates.go +++ b/pkg/geo/geomfn/binary_predicates.go @@ -39,6 +39,16 @@ func Contains(a *geo.Geometry, b *geo.Geometry) (bool, error) { return geos.Contains(a.EWKB(), b.EWKB()) } +// ContainsProperly returns whether geometry A properly contains geometry B. +func ContainsProperly(a *geo.Geometry, b *geo.Geometry) (bool, error) { + // No GEOS CAPI to call ContainsProperly; fallback to Relate. + relate, err := Relate(a, b) + if err != nil { + return false, err + } + return MatchesDE9IM(relate, "T**FF*FF*") +} + // Crosses returns whether geometry A crosses geometry B. func Crosses(a *geo.Geometry, b *geo.Geometry) (bool, error) { if a.SRID() != b.SRID() { diff --git a/pkg/geo/geomfn/binary_predicates_test.go b/pkg/geo/geomfn/binary_predicates_test.go index a2e70e2e13fa..f355c6ebcda3 100644 --- a/pkg/geo/geomfn/binary_predicates_test.go +++ b/pkg/geo/geomfn/binary_predicates_test.go @@ -85,6 +85,8 @@ func TestContains(t *testing.T) { }{ {rightRect, rightRectPoint, true}, {rightRectPoint, rightRect, false}, + {rightRectPoint, rightRectPoint, true}, + {rightRect, rightRect, true}, {leftRect, rightRect, false}, } @@ -102,6 +104,31 @@ func TestContains(t *testing.T) { }) } +func TestContainsProperly(t *testing.T) { + testCases := []struct { + a *geo.Geometry + b *geo.Geometry + expected bool + }{ + {rightRect, rightRect, false}, + {rightRect, rightRectPoint, true}, + {rightRectPoint, rightRectPoint, true}, + } + + for i, tc := range testCases { + t.Run(fmt.Sprintf("tc:%d", i), func(t *testing.T) { + g, err := ContainsProperly(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 := ContainsProperly(mismatchingSRIDGeometryA, mismatchingSRIDGeometryB) + requireMismatchingSRIDError(t, err) + }) +} + func TestCrosses(t *testing.T) { testCases := []struct { a *geo.Geometry diff --git a/pkg/geo/geomfn/de9im.go b/pkg/geo/geomfn/de9im.go new file mode 100644 index 000000000000..7799d2b26c90 --- /dev/null +++ b/pkg/geo/geomfn/de9im.go @@ -0,0 +1,74 @@ +// 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/cockroachdb/cockroach/pkg/util" + "github.com/cockroachdb/errors" +) + +// Relate returns the DE-9IM relation between A and B. +func Relate(a *geo.Geometry, b *geo.Geometry) (string, error) { + if a.SRID() != b.SRID() { + return "", geo.NewMismatchingSRIDsError(a, b) + } + return geos.Relate(a.EWKB(), b.EWKB()) +} + +// MatchesDE9IM checks whether the given DE-9IM relation matches the DE-91M pattern. +// Assumes the relation has been computed, and such has no 'T' and '*' characters. +// See: https://en.wikipedia.org/wiki/DE-9IM. +func MatchesDE9IM(relation string, pattern string) (bool, error) { + if len(relation) != 9 { + return false, errors.Newf("relation %q should be of length 9", relation) + } + if len(pattern) != 9 { + return false, errors.Newf("pattern %q should be of length 9", pattern) + } + for i := 0; i < len(relation); i++ { + matches, err := relationByteMatchesPatternByte(relation[i], pattern[i]) + if err != nil { + return false, err + } + if !matches { + return false, nil + } + } + return true, nil +} + +// relationByteMatchesPatternByte matches a single byte of a DE-9IM relation +// against the DE-9IM pattern. +// Pattern matches are as follows: +// * '*': allow anything. +// * 't'/'T': allow only if the relation is true. This means the relation must be +// '0' (point), '1' (line) or '2' (area) - which is the dimensionality of the +// intersection. +// * 'f'/'F': allow only if relation is also false, which is of the form 'f'/'F'. +func relationByteMatchesPatternByte(r byte, p byte) (bool, error) { + switch util.ToLowerSingleByte(p) { + case '*': + return true, nil + case 't': + if r < '0' || r > '2' { + return false, nil + } + case 'f': + if util.ToLowerSingleByte(r) != 'f' { + return false, nil + } + default: + return false, errors.Newf("unrecognized pattern character: %s", string(p)) + } + return true, nil +} diff --git a/pkg/geo/geomfn/de9im_test.go b/pkg/geo/geomfn/de9im_test.go new file mode 100644 index 000000000000..66d79dd1787f --- /dev/null +++ b/pkg/geo/geomfn/de9im_test.go @@ -0,0 +1,72 @@ +// 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" +) + +func TestRelate(t *testing.T) { + testCases := []struct { + a *geo.Geometry + b *geo.Geometry + expected string + }{ + {leftRect, rightRect, "FF2F11212"}, + } + + for i, tc := range testCases { + t.Run(fmt.Sprintf("tc:%d", i), func(t *testing.T) { + ret, err := Relate(tc.a, tc.b) + require.NoError(t, err) + require.Equal(t, tc.expected, ret) + }) + } +} + +func TestMatchesDE9IM(t *testing.T) { + testCases := []struct { + str string + pattern string + expected bool + expectedError string + }{ + {"", "T**FF*FF*", false, `relation "" should be of length 9`}, + {"TTTTTTTTT", "T**FF*FF*T", false, `pattern "T**FF*FF*T" should be of length 9`}, + {"TTTTTTTTT", "T**FF*FF*T", false, `pattern "T**FF*FF*T" should be of length 9`}, + {"000FFF000", "cTTFfFTTT", false, `unrecognized pattern character: c`}, + {"120FFF021", "TTTFfFTTT", true, ""}, + {"02FFFF000", "T**FfFTTT", true, ""}, + {"020F1F010", "TTTFFFTtT", false, ""}, + {"020FFF0f0", "TTTFFFTtT", false, ""}, + } + + for _, tc := range testCases { + t.Run(fmt.Sprintf("%s has pattern %s", tc.str, tc.pattern), func(t *testing.T) { + ret, err := MatchesDE9IM(tc.str, tc.pattern) + if tc.expectedError == "" { + require.NoError(t, err) + require.Equal(t, tc.expected, ret) + } else { + require.EqualError(t, err, tc.expectedError) + } + }) + } + + t.Run("errors if SRIDs mismatch", func(t *testing.T) { + _, err := Relate(mismatchingSRIDGeometryA, mismatchingSRIDGeometryB) + requireMismatchingSRIDError(t, err) + }) +} diff --git a/pkg/geo/geos/geos.cc b/pkg/geo/geos/geos.cc index 46197aadd42e..7ef93637aff1 100644 --- a/pkg/geo/geos/geos.cc +++ b/pkg/geo/geos/geos.cc @@ -48,7 +48,7 @@ typedef void (*CR_GEOS_finish_r)(CR_GEOS_Handle); typedef CR_GEOS_MessageHandler (*CR_GEOS_Context_setErrorMessageHandler_r)(CR_GEOS_Handle, CR_GEOS_MessageHandler, void*); - +typedef void (*CR_GEOS_Free_r)(CR_GEOS_Handle, void* buffer); typedef void (*CR_GEOS_SetSRID_r)(CR_GEOS_Handle, CR_GEOS_Geometry, int); typedef int (*CR_GEOS_GetSRID_r)(CR_GEOS_Handle, CR_GEOS_Geometry); typedef void (*CR_GEOS_GeomDestroy_r)(CR_GEOS_Handle, CR_GEOS_Geometry); @@ -78,6 +78,8 @@ typedef char (*CR_GEOS_Overlaps_r)(CR_GEOS_Handle, CR_GEOS_Geometry, CR_GEOS_Geo typedef char (*CR_GEOS_Touches_r)(CR_GEOS_Handle, CR_GEOS_Geometry, CR_GEOS_Geometry); typedef char (*CR_GEOS_Within_r)(CR_GEOS_Handle, CR_GEOS_Geometry, CR_GEOS_Geometry); +typedef char* (*CR_GEOS_Relate_r)(CR_GEOS_Handle, CR_GEOS_Geometry, CR_GEOS_Geometry); + typedef CR_GEOS_WKBWriter (*CR_GEOS_WKBWriter_create_r)(CR_GEOS_Handle); typedef char* (*CR_GEOS_WKBWriter_write_r)(CR_GEOS_Handle, CR_GEOS_WKBWriter, CR_GEOS_Geometry, size_t*); @@ -99,6 +101,7 @@ struct CR_GEOS { CR_GEOS_init_r GEOS_init_r; CR_GEOS_finish_r GEOS_finish_r; CR_GEOS_Context_setErrorMessageHandler_r GEOSContext_setErrorMessageHandler_r; + CR_GEOS_Free_r GEOSFree_r; CR_GEOS_SetSRID_r GEOSSetSRID_r; CR_GEOS_GetSRID_r GEOSGetSRID_r; @@ -127,6 +130,8 @@ struct CR_GEOS { CR_GEOS_Touches_r GEOSTouches_r; CR_GEOS_Within_r GEOSWithin_r; + CR_GEOS_Relate_r GEOSRelate_r; + CR_GEOS_WKBWriter_create_r GEOSWKBWriter_create_r; CR_GEOS_WKBWriter_destroy_r GEOSWKBWriter_destroy_r; CR_GEOS_WKBWriter_setByteOrder_r GEOSWKBWriter_setByteOrder_r; @@ -154,6 +159,7 @@ struct CR_GEOS { INIT(GEOS_init_r); INIT(GEOS_finish_r); + INIT(GEOSFree_r); INIT(GEOSContext_setErrorMessageHandler_r); INIT(GEOSGeom_destroy_r); INIT(GEOSSetSRID_r); @@ -170,6 +176,7 @@ struct CR_GEOS { INIT(GEOSOverlaps_r); INIT(GEOSTouches_r); INIT(GEOSWithin_r); + INIT(GEOSRelate_r); INIT(GEOSWKTReader_create_r); INIT(GEOSWKTReader_destroy_r); INIT(GEOSWKTReader_read_r); @@ -431,3 +438,34 @@ CR_GEOS_Status CR_GEOS_Touches(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_Slice b, c 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); } + +// +// DE-9IM related +// 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) { + std::string error; + auto handle = initHandleWithErrorBuffer(lib, &error); + + auto wkbReader = lib->GEOSWKBReader_create_r(handle); + auto geomA = lib->GEOSWKBReader_read_r(handle, wkbReader, a.data, a.len); + auto geomB = lib->GEOSWKBReader_read_r(handle, wkbReader, b.data, b.len); + lib->GEOSWKBReader_destroy_r(handle, wkbReader); + + if (geomA != nullptr && geomB != nullptr) { + auto r = lib->GEOSRelate_r(handle, geomA, geomB); + if (r != NULL) { + *ret = toGEOSString(r, strlen(r)); + lib->GEOSFree_r(handle, r); + } + } + 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()); +} diff --git a/pkg/geo/geos/geos.go b/pkg/geo/geos/geos.go index 3804384b138f..932e7e880049 100644 --- a/pkg/geo/geos/geos.go +++ b/pkg/geo/geos/geos.go @@ -399,3 +399,23 @@ func Within(a geopb.EWKB, b geopb.EWKB) (bool, error) { } return ret == 1, nil } + +// +// DE-9IM related +// + +// Relate returns the DE-9IM relation between A and B. +func Relate(a geopb.EWKB, b geopb.EWKB) (string, error) { + g, err := ensureInitInternal() + if err != nil { + return "", err + } + var ret C.CR_GEOS_String + if err := statusToError(C.CR_GEOS_Relate(g, goToCSlice(a), goToCSlice(b), &ret)); err != nil { + return "", err + } + if ret.data == nil { + return "", errors.Newf("expected DE-9IM string but found nothing") + } + return string(cStringToSafeGoBytes(ret)), nil +} diff --git a/pkg/geo/geos/geos.h b/pkg/geo/geos/geos.h index 916b5e11a51a..2123726d6378 100644 --- a/pkg/geo/geos/geos.h +++ b/pkg/geo/geos/geos.h @@ -84,6 +84,11 @@ CR_GEOS_Status CR_GEOS_Overlaps(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_Slice b, 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); #ifdef __cplusplus } // extern "C" #endif diff --git a/pkg/sql/logictest/testdata/logic_test/geospatial b/pkg/sql/logictest/testdata/logic_test/geospatial index a038857f8cea..714bcfb28253 100644 --- a/pkg/sql/logictest/testdata/logic_test/geospatial +++ b/pkg/sql/logictest/testdata/logic_test/geospatial @@ -289,13 +289,14 @@ Square overlapping left and right square Square (right) Square overlapping left and right square Square overlapping left and right square 0 # Binary predicates -query TTBBBBBBBBB +query TTBBBBBBBBBB SELECT a.dsc, b.dsc, ST_Covers(a.geom, b.geom), ST_CoveredBy(a.geom, b.geom), ST_Contains(a.geom, b.geom), + ST_ContainsProperly(a.geom, b.geom), ST_Crosses(a.geom, b.geom), ST_Equals(a.geom, b.geom), ST_Intersects(a.geom, b.geom), @@ -306,55 +307,116 @@ FROM geom_operators_test a JOIN geom_operators_test b ON (1=1) ORDER BY a.dsc, b.dsc ---- -Line going through left and right square Line going through left and right square true true true false true true false false true -Line going through left and right square NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL -Line going through left and right square Point middle of Left Square true false false false false true false true false -Line going through left and right square Point middle of Right Square true false false false false true false true false -Line going through left and right square Square (left) false false false true false true false false false -Line going through left and right square Square (right) false false false true false true false false false -Line going through left and right square Square overlapping left and right square false false false true false true false false false -NULL Line going through left and right square NULL NULL NULL NULL NULL NULL NULL NULL NULL -NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL -NULL Point middle of Left Square NULL NULL NULL NULL NULL NULL NULL NULL NULL -NULL Point middle of Right Square NULL NULL NULL NULL NULL NULL NULL NULL NULL -NULL Square (left) NULL NULL NULL NULL NULL NULL NULL NULL NULL -NULL Square (right) NULL NULL NULL NULL NULL NULL NULL NULL NULL -NULL Square overlapping left and right square NULL NULL NULL NULL NULL NULL NULL NULL NULL -Point middle of Left Square Line going through left and right square false true false false false true false true false -Point middle of Left Square NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL -Point middle of Left Square Point middle of Left Square true true true false true true false false true -Point middle of Left Square Point middle of Right Square false false false false false false false false false -Point middle of Left Square Square (left) false true false false false true false false true -Point middle of Left Square Square (right) false false false false false false false false false -Point middle of Left Square Square overlapping left and right square false false false false false false false false false -Point middle of Right Square Line going through left and right square false true false false false true false true false -Point middle of Right Square NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL -Point middle of Right Square Point middle of Left Square false false false false false false false false false -Point middle of Right Square Point middle of Right Square true true true false true true false false true -Point middle of Right Square Square (left) false false false false false false false false false -Point middle of Right Square Square (right) false true false false false true false false true -Point middle of Right Square Square overlapping left and right square false true false false false true false false true -Square (left) Line going through left and right square false false false true false true false false false -Square (left) NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL -Square (left) Point middle of Left Square true false true false false true false false false -Square (left) Point middle of Right Square false false false false false false false false false -Square (left) Square (left) true true true false true true false false true -Square (left) Square (right) false false false false false true false true false -Square (left) Square overlapping left and right square false false false false false true true false false -Square (right) Line going through left and right square false false false true false true false false false -Square (right) NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL -Square (right) Point middle of Left Square false false false false false false false false false -Square (right) Point middle of Right Square true false true false false true false false false -Square (right) Square (left) false false false false false true false true false -Square (right) Square (right) true true true false true true false false true -Square (right) Square overlapping left and right square false true false false false true false false true -Square overlapping left and right square Line going through left and right square false false false true false true false false false -Square overlapping left and right square NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL -Square overlapping left and right square Point middle of Left Square false false false false false false false false false -Square overlapping left and right square Point middle of Right Square true false true false false true false false false -Square overlapping left and right square Square (left) false false false false false true true false false -Square overlapping left and right square Square (right) true false true false false true false false false -Square overlapping left and right square Square overlapping left and right square true true true false true true false false true +Line going through left and right square Line going through left and right square true true true false false true true false false true +Line going through left and right square NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL +Line going through left and right square Point middle of Left Square true false false false false false true false true false +Line going through left and right square Point middle of Right Square true false false false false false true false true false +Line going through left and right square Square (left) false false false false true false true false false false +Line going through left and right square Square (right) false false false false true false true false false false +Line going through left and right square Square overlapping left and right square false false false false true false true false false false +NULL Line going through left and right square NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL +NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL +NULL Point middle of Left Square NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL +NULL Point middle of Right Square NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL +NULL Square (left) NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL +NULL Square (right) NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL +NULL Square overlapping left and right square NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL +Point middle of Left Square Line going through left and right square false true false false false false true false true false +Point middle of Left Square NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL +Point middle of Left Square Point middle of Left Square true true true true false true true false false true +Point middle of Left Square Point middle of Right Square false false false false false false false false false false +Point middle of Left Square Square (left) false true false false false false true false false true +Point middle of Left Square Square (right) false false false false false false false false false false +Point middle of Left Square Square overlapping left and right square false false false false false false false false false false +Point middle of Right Square Line going through left and right square false true false false false false true false true false +Point middle of Right Square NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL +Point middle of Right Square Point middle of Left Square false false false false false false false false false false +Point middle of Right Square Point middle of Right Square true true true true false true true false false true +Point middle of Right Square Square (left) false false false false false false false false false false +Point middle of Right Square Square (right) false true false false false false true false false true +Point middle of Right Square Square overlapping left and right square false true false false false false true false false true +Square (left) Line going through left and right square false false false false true false true false false false +Square (left) NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL +Square (left) Point middle of Left Square true false true true false false true false false false +Square (left) Point middle of Right Square false false false false false false false false false false +Square (left) Square (left) true true true false false true true false false true +Square (left) Square (right) false false false false false false true false true false +Square (left) Square overlapping left and right square false false false false false false true true false false +Square (right) Line going through left and right square false false false false true false true false false false +Square (right) NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL +Square (right) Point middle of Left Square false false false false false false false false false false +Square (right) Point middle of Right Square true false true true false false true false false false +Square (right) Square (left) false false false false false false true false true false +Square (right) Square (right) true true true false false true true false false true +Square (right) Square overlapping left and right square false true false false false false true false false true +Square overlapping left and right square Line going through left and right square false false false false true false true false false false +Square overlapping left and right square NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL +Square overlapping left and right square Point middle of Left Square false false false false false false false false false false +Square overlapping left and right square Point middle of Right Square true false true true false false true false false false +Square overlapping left and right square Square (left) false false false false false false true true false false +Square overlapping left and right square Square (right) true false true false false false true false false false +Square overlapping left and right square Square overlapping left and right square true true true false false true true false false true + +# DE-9IM relations +query TTTB +SELECT + a.dsc, + b.dsc, + ST_Relate(a.geom, b.geom), + ST_Relate(a.geom, b.geom, 'T**FF*FF*') +FROM geom_operators_test a +JOIN geom_operators_test b ON (1=1) +ORDER BY a.dsc, b.dsc +---- +Line going through left and right square Line going through left and right square 1FFF0FFF2 false +Line going through left and right square NULL NULL NULL +Line going through left and right square Point middle of Left Square FF10F0FF2 false +Line going through left and right square Point middle of Right Square FF10F0FF2 false +Line going through left and right square Square (left) 1010F0212 false +Line going through left and right square Square (right) 1010F0212 false +Line going through left and right square Square overlapping left and right square 1010F0212 false +NULL Line going through left and right square NULL NULL +NULL NULL NULL NULL +NULL Point middle of Left Square NULL NULL +NULL Point middle of Right Square NULL NULL +NULL Square (left) NULL NULL +NULL Square (right) NULL NULL +NULL Square overlapping left and right square NULL NULL +Point middle of Left Square Line going through left and right square F0FFFF102 false +Point middle of Left Square NULL NULL NULL +Point middle of Left Square Point middle of Left Square 0FFFFFFF2 true +Point middle of Left Square Point middle of Right Square FF0FFF0F2 false +Point middle of Left Square Square (left) 0FFFFF212 false +Point middle of Left Square Square (right) FF0FFF212 false +Point middle of Left Square Square overlapping left and right square FF0FFF212 false +Point middle of Right Square Line going through left and right square F0FFFF102 false +Point middle of Right Square NULL NULL NULL +Point middle of Right Square Point middle of Left Square FF0FFF0F2 false +Point middle of Right Square Point middle of Right Square 0FFFFFFF2 true +Point middle of Right Square Square (left) FF0FFF212 false +Point middle of Right Square Square (right) 0FFFFF212 false +Point middle of Right Square Square overlapping left and right square 0FFFFF212 false +Square (left) Line going through left and right square 1020F1102 false +Square (left) NULL NULL NULL +Square (left) Point middle of Left Square 0F2FF1FF2 true +Square (left) Point middle of Right Square FF2FF10F2 false +Square (left) Square (left) 2FFF1FFF2 false +Square (left) Square (right) FF2F11212 false +Square (left) Square overlapping left and right square 212111212 false +Square (right) Line going through left and right square 1020F1102 false +Square (right) NULL NULL NULL +Square (right) Point middle of Left Square FF2FF10F2 false +Square (right) Point middle of Right Square 0F2FF1FF2 true +Square (right) Square (left) FF2F11212 false +Square (right) Square (right) 2FFF1FFF2 false +Square (right) Square overlapping left and right square 2FF11F212 false +Square overlapping left and right square Line going through left and right square 1020F1102 false +Square overlapping left and right square NULL NULL NULL +Square overlapping left and right square Point middle of Left Square FF2FF10F2 false +Square overlapping left and right square Point middle of Right Square 0F2FF1FF2 true +Square overlapping left and right square Square (left) 212111212 false +Square overlapping left and right square Square (right) 212F11FF2 false +Square overlapping left and right square Square overlapping left and right square 2FFF1FFF2 false subtest regression_48093 diff --git a/pkg/sql/opt/norm/fold_constants.go b/pkg/sql/opt/norm/fold_constants.go index b1b854676071..49b2c0fe5e77 100644 --- a/pkg/sql/opt/norm/fold_constants.go +++ b/pkg/sql/opt/norm/fold_constants.go @@ -517,6 +517,7 @@ var FoldFunctionWhitelist = map[string]struct{}{ "st_covers": {}, "st_coveredby": {}, "st_contains": {}, + "st_containsproperly": {}, "st_crosses": {}, "st_equals": {}, "st_intersects": {}, diff --git a/pkg/sql/opt/xform/custom_funcs.go b/pkg/sql/opt/xform/custom_funcs.go index ed9a7f8eeaa3..23da65275c9c 100644 --- a/pkg/sql/opt/xform/custom_funcs.go +++ b/pkg/sql/opt/xform/custom_funcs.go @@ -1637,15 +1637,16 @@ func (c *CustomFuncs) GenerateGeoLookupJoins( // TODO(rytaft): add ST_DFullyWithin (geoindex.Covers) and ST_DWithin // (geoindex.Intersects) once we add support for extending a geometry. var geoRelationshipMap = map[string]geoindex.RelationshipType{ - "st_covers": geoindex.Covers, - "st_coveredby": geoindex.CoveredBy, - "st_contains": geoindex.Covers, - "st_crosses": geoindex.Intersects, - "st_equals": geoindex.Intersects, - "st_intersects": geoindex.Intersects, - "st_overlaps": geoindex.Intersects, - "st_touches": geoindex.Intersects, - "st_within": geoindex.CoveredBy, + "st_covers": geoindex.Covers, + "st_coveredby": geoindex.CoveredBy, + "st_contains": geoindex.Covers, + "st_containsproperly": geoindex.Covers, + "st_crosses": geoindex.Intersects, + "st_equals": geoindex.Intersects, + "st_intersects": geoindex.Intersects, + "st_overlaps": geoindex.Intersects, + "st_touches": geoindex.Intersects, + "st_within": geoindex.CoveredBy, } // IsGeoIndexFunction returns true if the given function is a geospatial diff --git a/pkg/sql/sem/builtins/geo_builtins.go b/pkg/sql/sem/builtins/geo_builtins.go index 6ec91db7ee31..e8feb70705ad 100644 --- a/pkg/sql/sem/builtins/geo_builtins.go +++ b/pkg/sql/sem/builtins/geo_builtins.go @@ -740,6 +740,17 @@ Note ST_Perimeter is only valid for Polygon - use ST_Length for LineString.`, }, ), ), + "st_containsproperly": makeBuiltin( + defProps(), + geometryOverload2BinaryPredicate( + geomfn.ContainsProperly, + infoBuilder{ + info: "Returns true if geometry_b intersects the interior of geometry_a but not the boundary or exterior of geometry_a.", + usesGEOS: true, + canUseIndex: true, + }, + ), + ), "st_crosses": makeBuiltin( defProps(), geometryOverload2BinaryPredicate( @@ -809,6 +820,56 @@ Note ST_Perimeter is only valid for Polygon - use ST_Length for LineString.`, }, ), ), + + // + // DE-9IM related + // + + "st_relate": makeBuiltin( + defProps(), + geometryOverload2( + func(ctx *tree.EvalContext, a *tree.DGeometry, b *tree.DGeometry) (tree.Datum, error) { + ret, err := geomfn.Relate(a.Geometry, b.Geometry) + if err != nil { + return nil, err + } + return tree.NewDString(ret), nil + }, + types.String, + infoBuilder{ + info: `Returns the DE-9IM spatial relation between geometry_a and geometry_b.`, + usesGEOS: true, + }, + tree.VolatilityImmutable, + ), + tree.Overload{ + Types: tree.ArgTypes{ + {"geometry_a", types.Geometry}, + {"geometry_b", types.Geometry}, + {"pattern", types.String}, + }, + ReturnType: tree.FixedReturnType(types.Bool), + Fn: func(ctx *tree.EvalContext, args tree.Datums) (tree.Datum, error) { + a := args[0].(*tree.DGeometry) + b := args[1].(*tree.DGeometry) + pattern := args[2].(*tree.DString) + relation, err := geomfn.Relate(a.Geometry, b.Geometry) + if err != nil { + return nil, err + } + ret, err := geomfn.MatchesDE9IM(relation, string(*pattern)) + if err != nil { + return nil, err + } + return tree.MakeDBool(tree.DBool(ret)), nil + }, + Info: infoBuilder{ + info: `Returns whether the DE-9IM spatial relation between geometry_a and geometry_b matches the DE-9IM pattern.`, + usesGEOS: true, + }.String(), + Volatility: tree.VolatilityImmutable, + }, + ), } // geometryOverload1 hides the boilerplate for builtins operating on one geometry.