Commit da6cd48b authored by Oliver Tan's avatar Oliver Tan

geomfn: apply bounding box checks for DWithin/DFullyWithin

We are able to optimize DWithin/DFullyWithin by extended the bounding
box for geometry types by distance units in each direction and
checking if they intersect / covers.

Also implement cartesian bounding box covers and use it for other
relevant operations.

Release note: None
parent 955ccb95
......@@ -46,6 +46,21 @@ func (b *CartesianBoundingBox) AddPoint(x, y float64) {
b.HiY = math.Max(b.HiY, y)
}
// Buffer adds n units to each side of the bounding box.
func (b *CartesianBoundingBox) Buffer(n float64) *CartesianBoundingBox {
if b == nil {
return nil
}
return &CartesianBoundingBox{
BoundingBox: geopb.BoundingBox{
LoX: b.LoX - n,
HiX: b.HiX + n,
LoY: b.LoY - n,
HiY: b.HiY + n,
},
}
}
// Intersects returns whether the BoundingBoxes intersect.
// Empty bounding boxes never intersect.
func (b *CartesianBoundingBox) Intersects(o *CartesianBoundingBox) bool {
......@@ -60,6 +75,18 @@ func (b *CartesianBoundingBox) Intersects(o *CartesianBoundingBox) bool {
return true
}
// Covers returns whether the BoundingBox covers the other bounding box.
// Empty bounding boxes never cover.
func (b *CartesianBoundingBox) Covers(o *CartesianBoundingBox) bool {
if b == nil || o == nil {
return false
}
return b.LoX <= o.LoX && o.LoX <= b.HiX &&
b.LoX <= o.HiX && o.HiX <= b.HiX &&
b.LoY <= o.LoY && o.LoY <= b.HiY &&
b.LoY <= o.HiY && o.HiY <= b.HiY
}
// boundingBoxFromGeomT returns a bounding box from a given geom.T.
// Returns nil if no bounding box was found.
func boundingBoxFromGeomT(g geom.T, soType geopb.SpatialObjectType) (*geopb.BoundingBox, error) {
......
......@@ -129,6 +129,89 @@ func TestCartesianBoundingBoxIntersects(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
require.Equal(t, tc.expected, tc.a.Intersects(tc.b))
require.Equal(t, tc.expected, tc.b.Intersects(tc.a))
})
}
}
func TestCartesianBoundingBoxCovers(t *testing.T) {
testCases := []struct {
desc string
a *CartesianBoundingBox
b *CartesianBoundingBox
expected bool
}{
{
desc: "same bounding box covers",
a: &CartesianBoundingBox{BoundingBox: geopb.BoundingBox{LoX: 0, HiX: 1, LoY: 0, HiY: 1}},
b: &CartesianBoundingBox{BoundingBox: geopb.BoundingBox{LoX: 0, HiX: 1, LoY: 0, HiY: 1}},
expected: true,
},
{
desc: "nested bounding box covers",
a: &CartesianBoundingBox{BoundingBox: geopb.BoundingBox{LoX: 0, HiX: 1, LoY: 0, HiY: 1}},
b: &CartesianBoundingBox{BoundingBox: geopb.BoundingBox{LoX: 0.1, HiX: 0.9, LoY: 0.1, HiY: 0.9}},
expected: true,
},
{
desc: "side touching bounding box covers",
a: &CartesianBoundingBox{BoundingBox: geopb.BoundingBox{LoX: 0, HiX: 1, LoY: 0, HiY: 1}},
b: &CartesianBoundingBox{BoundingBox: geopb.BoundingBox{LoX: 0.1, HiX: 0.9, LoY: 0.1, HiY: 0.9}},
expected: true,
},
{
desc: "top touching bounding box covers",
a: &CartesianBoundingBox{BoundingBox: geopb.BoundingBox{LoX: 0, HiX: 1, LoY: 0, HiY: 1}},
b: &CartesianBoundingBox{BoundingBox: geopb.BoundingBox{LoX: 0.1, HiX: 0.9, LoY: 0, HiY: 1}},
expected: true,
},
{
desc: "reversed nested bounding box does not cover",
a: &CartesianBoundingBox{BoundingBox: geopb.BoundingBox{LoX: 0.1, HiX: 0.9, LoY: 0.1, HiY: 0.9}},
b: &CartesianBoundingBox{BoundingBox: geopb.BoundingBox{LoX: 0, HiX: 1, LoY: 0, HiY: 1}},
expected: false,
},
{
desc: "overlapping bounding box from the left covers",
a: &CartesianBoundingBox{BoundingBox: geopb.BoundingBox{LoX: 0, HiX: 1, LoY: 0, HiY: 1}},
b: &CartesianBoundingBox{BoundingBox: geopb.BoundingBox{LoX: 0.5, HiX: 1.5, LoY: 0.5, HiY: 1.5}},
expected: false,
},
{
desc: "overlapping bounding box from the right covers",
a: &CartesianBoundingBox{BoundingBox: geopb.BoundingBox{LoX: 0, HiX: 1, LoY: 0, HiY: 1}},
b: &CartesianBoundingBox{BoundingBox: geopb.BoundingBox{LoX: 0.5, HiX: 1.5, LoY: 0.5, HiY: 1.5}},
expected: false,
},
{
desc: "touching bounding box covers",
a: &CartesianBoundingBox{BoundingBox: geopb.BoundingBox{LoX: 0, HiX: 1, LoY: 0, HiY: 1}},
b: &CartesianBoundingBox{BoundingBox: geopb.BoundingBox{LoX: 1, HiX: 2, LoY: 1, HiY: 2}},
expected: false,
},
{
desc: "bounding box that is left does not cover",
a: &CartesianBoundingBox{BoundingBox: geopb.BoundingBox{LoX: 0, HiX: 1, LoY: 0, HiY: 1}},
b: &CartesianBoundingBox{BoundingBox: geopb.BoundingBox{LoX: 1.5, HiX: 2, LoY: 0, HiY: 1}},
expected: false,
},
{
desc: "higher bounding box does not cover",
a: &CartesianBoundingBox{BoundingBox: geopb.BoundingBox{LoX: 0, HiX: 1, LoY: 0, HiY: 1}},
b: &CartesianBoundingBox{BoundingBox: geopb.BoundingBox{LoX: 0, HiX: 1, LoY: 1.5, HiY: 2}},
expected: false,
},
{
desc: "completely disjoint bounding box does not cover",
a: &CartesianBoundingBox{BoundingBox: geopb.BoundingBox{LoX: 0, HiX: 1, LoY: 0, HiY: 1}},
b: &CartesianBoundingBox{BoundingBox: geopb.BoundingBox{LoX: -3, HiX: -2, LoY: 1.5, HiY: 2}},
expected: false,
},
}
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
require.Equal(t, tc.expected, tc.a.Covers(tc.b))
})
}
}
......@@ -20,7 +20,7 @@ func Covers(a *geo.Geometry, b *geo.Geometry) (bool, error) {
if a.SRID() != b.SRID() {
return false, geo.NewMismatchingSRIDsError(a, b)
}
if !a.CartesianBoundingBox().Intersects(b.CartesianBoundingBox()) {
if !a.CartesianBoundingBox().Covers(b.CartesianBoundingBox()) {
return false, nil
}
return geos.Covers(a.EWKB(), b.EWKB())
......@@ -31,7 +31,7 @@ func CoveredBy(a *geo.Geometry, b *geo.Geometry) (bool, error) {
if a.SRID() != b.SRID() {
return false, geo.NewMismatchingSRIDsError(a, b)
}
if !a.CartesianBoundingBox().Intersects(b.CartesianBoundingBox()) {
if !b.CartesianBoundingBox().Covers(a.CartesianBoundingBox()) {
return false, nil
}
return geos.CoveredBy(a.EWKB(), b.EWKB())
......@@ -42,7 +42,7 @@ func Contains(a *geo.Geometry, b *geo.Geometry) (bool, error) {
if a.SRID() != b.SRID() {
return false, geo.NewMismatchingSRIDsError(a, b)
}
if !a.CartesianBoundingBox().Intersects(b.CartesianBoundingBox()) {
if !a.CartesianBoundingBox().Covers(b.CartesianBoundingBox()) {
return false, nil
}
return geos.Contains(a.EWKB(), b.EWKB())
......@@ -53,7 +53,7 @@ func ContainsProperly(a *geo.Geometry, b *geo.Geometry) (bool, error) {
if a.SRID() != b.SRID() {
return false, geo.NewMismatchingSRIDsError(a, b)
}
if !a.CartesianBoundingBox().Intersects(b.CartesianBoundingBox()) {
if !a.CartesianBoundingBox().Covers(b.CartesianBoundingBox()) {
return false, nil
}
return geos.RelatePattern(a.EWKB(), b.EWKB(), "T**FF*FF*")
......@@ -81,7 +81,7 @@ func Equals(a *geo.Geometry, b *geo.Geometry) (bool, error) {
if a.Empty() && b.Empty() {
return true, nil
}
if !a.CartesianBoundingBox().Intersects(b.CartesianBoundingBox()) {
if !a.CartesianBoundingBox().Covers(b.CartesianBoundingBox()) {
return false, nil
}
return geos.Equals(a.EWKB(), b.EWKB())
......@@ -125,7 +125,7 @@ func Within(a *geo.Geometry, b *geo.Geometry) (bool, error) {
if a.SRID() != b.SRID() {
return false, geo.NewMismatchingSRIDsError(a, b)
}
if !a.CartesianBoundingBox().Intersects(b.CartesianBoundingBox()) {
if !b.CartesianBoundingBox().Covers(a.CartesianBoundingBox()) {
return false, nil
}
return geos.Within(a.EWKB(), b.EWKB())
......
......@@ -59,6 +59,9 @@ func DWithin(a *geo.Geometry, b *geo.Geometry, d float64) (bool, error) {
if d < 0 {
return false, errors.Newf("dwithin distance cannot be less than zero")
}
if !a.CartesianBoundingBox().Buffer(d).Intersects(b.CartesianBoundingBox()) {
return false, nil
}
dist, err := minDistanceInternal(a, b, d, geo.EmptyBehaviorError)
if err != nil {
// In case of any empty geometries return false.
......@@ -79,6 +82,9 @@ func DFullyWithin(a *geo.Geometry, b *geo.Geometry, d float64) (bool, error) {
if d < 0 {
return false, errors.Newf("dwithin distance cannot be less than zero")
}
if !a.CartesianBoundingBox().Buffer(d).Covers(b.CartesianBoundingBox()) {
return false, nil
}
dist, err := maxDistanceInternal(a, b, d, geo.EmptyBehaviorError)
if err != nil {
// In case of any empty geometries return false.
......
......@@ -264,6 +264,15 @@ var Projections = map[geopb.SRID]ProjInfo{
IsLatLng: false,
Spheroid: spheroid2,
},
26918: {
SRID: 26918,
AuthName: "EPSG",
AuthSRID: 26918,
SRText: `PROJCS["NAD83 / UTM zone 18N",GEOGCS["NAD83",DATUM["North_American_Datum_1983",SPHEROID["GRS 1980",6378137,298.257222101,AUTHORITY["EPSG","7019"]],TOWGS84[0,0,0,0,0,0,0],AUTHORITY["EPSG","6269"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4269"]],PROJECTION["Transverse_Mercator"],PARAMETER["latitude_of_origin",0],PARAMETER["central_meridian",-75],PARAMETER["scale_factor",0.9996],PARAMETER["false_easting",500000],PARAMETER["false_northing",0],UNIT["metre",1,AUTHORITY["EPSG","9001"]],AXIS["Easting",EAST],AXIS["Northing",NORTH],AUTHORITY["EPSG","26918"]]`,
Proj4Text: MakeProj4Text(`+proj=utm +zone=18 +datum=NAD83 +units=m +no_defs`),
IsLatLng: false,
Spheroid: spheroid3,
},
32601: {
SRID: 32601,
AuthName: "EPSG",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment