diff --git a/pkg/geo/geo.go b/pkg/geo/geo.go index 75f622670578..0cd21ad027aa 100644 --- a/pkg/geo/geo.go +++ b/pkg/geo/geo.go @@ -472,7 +472,7 @@ func (g *Geography) AsS2(emptyBehavior EmptyBehavior) ([]s2.Region, error) { return S2RegionsFromGeom(geomRepr, emptyBehavior) } -// BoundingRect returns the bounding rectangle of the given Geography. +// BoundingRect returns the bounding s2.Rect of the given Geography. func (g *Geography) BoundingRect() s2.Rect { bbox := g.spatialObject.BoundingBox if bbox == nil { @@ -484,6 +484,11 @@ func (g *Geography) BoundingRect() s2.Rect { } } +// BoundingCap returns the bounding s2.Cap of the given Geography. +func (g *Geography) BoundingCap() s2.Cap { + return g.BoundingRect().CapBound() +} + // isLinearRingCCW returns whether a given linear ring is counter clock wise. // See 2.07 of http://www.faqs.org/faqs/graphics/algorithms-faq/. // "Find the lowest vertex (or, if there is more than one vertex with the same lowest coordinate, diff --git a/pkg/geo/geogfn/distance.go b/pkg/geo/geogfn/distance.go index 34cdf9923767..990559caf4b6 100644 --- a/pkg/geo/geogfn/distance.go +++ b/pkg/geo/geogfn/distance.go @@ -21,6 +21,12 @@ import ( "github.com/golang/geo/s2" ) +// SpheroidErrorFraction is an error fraction to compensate for using a sphere +// to calculate the distance for what is actually a spheroid. The distance +// calculation has an error that is bounded by (2 * spheroid.Flattening)%. +// This 5% margin is pretty safe. +const SpheroidErrorFraction = 0.05 + // Distance returns the distance between geographies a and b on a sphere or spheroid. // Returns a geo.EmptyGeometryError if any of the Geographies are EMPTY. func Distance( @@ -236,12 +242,10 @@ func newGeographyMinDistanceUpdater( ) *geographyMinDistanceUpdater { multiplier := 1.0 if useSphereOrSpheroid == UseSpheroid { - // Modify the stopAfterLE distance to be 95% of the proposed stopAfterLE, since - // we use the sphere to calculate the distance and we want to leave a buffer - // for spheroid distances being slightly off. - // Distances should differ by a maximum of (2 * spheroid.Flattening)%, - // so the 5% margin is pretty safe. - multiplier = 0.95 + // Modify the stopAfterLE distance to be less by the error fraction, since + // we use the sphere to calculate the distance and we want to leave a + // buffer for spheroid distances being slightly off. + multiplier -= SpheroidErrorFraction } stopAfterLEChordAngle := s1.ChordAngleFromAngle(s1.Angle(stopAfterLE * multiplier / spheroid.SphereRadius)) return &geographyMinDistanceUpdater{ diff --git a/pkg/geo/geogfn/dwithin.go b/pkg/geo/geogfn/dwithin.go index a2ce165f2a34..17767482261c 100644 --- a/pkg/geo/geogfn/dwithin.go +++ b/pkg/geo/geogfn/dwithin.go @@ -13,6 +13,7 @@ package geogfn import ( "github.com/cockroachdb/cockroach/pkg/geo" "github.com/cockroachdb/errors" + "github.com/golang/geo/s1" ) // DWithin returns whether a is within distance d of b, i.e. Distance(a, b) <= d. @@ -26,6 +27,18 @@ func DWithin( if distance < 0 { return false, errors.Newf("dwithin distance cannot be less than zero") } + spheroid, err := a.Spheroid() + if err != nil { + return false, err + } + + distanceToExpand := s1.Angle(distance / spheroid.SphereRadius) + if useSphereOrSpheroid == UseSpheroid { + distanceToExpand *= (1 + SpheroidErrorFraction) + } + if !a.BoundingCap().Expanded(distanceToExpand).Intersects(b.BoundingCap()) { + return false, nil + } aRegions, err := a.AsS2(geo.EmptyBehaviorError) if err != nil { @@ -41,10 +54,6 @@ func DWithin( } return false, err } - spheroid, err := a.Spheroid() - if err != nil { - return false, err - } maybeClosestDistance, err := distanceGeographyRegions(spheroid, useSphereOrSpheroid, aRegions, bRegions, distance) if err != nil { return false, err