Skip to content

Commit

Permalink
SERVER-14192 Add covering for annulus
Browse files Browse the repository at this point in the history
  • Loading branch information
visualzhou committed Jun 13, 2014
1 parent 8a47cfe commit 83f2b11
Show file tree
Hide file tree
Showing 4 changed files with 174 additions and 28 deletions.
8 changes: 2 additions & 6 deletions src/mongo/db/geo/geoquery.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -349,12 +349,8 @@ namespace mongo {

if ( _geometry->_cap && FLAT == _geometry->_cap->crs ) {
const Circle& circle = _geometry->_cap->circle;
const Point& a = other._min;
const Point& b = other._max;
return distanceWithin( circle.center, a, circle.radius )
&& distanceWithin( circle.center, b, circle.radius )
&& distanceWithin( circle.center, Point( a.x, b.y ), circle.radius )
&& distanceWithin( circle.center, Point( b.x, a.y ), circle.radius );
// Exact test
return circleContainsBox(circle, other);
}

if (_geometry->_polygon && FLAT == _geometry->_polygon->crs) {
Expand Down
72 changes: 72 additions & 0 deletions src/mongo/db/geo/r2_region_coverer_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -543,4 +543,76 @@ namespace {
ASSERT_FALSE(polygonContainsBox(concave, box));
}

TEST(ShapeIntersection, Annulus) {
R2Annulus annulus(Point(0.0, 0.0), 1, 5);
Box box;

// Disjoint, out of outer circle
box = Box(4, 4, 1);
ASSERT_TRUE(annulus.fastDisjoint(box));
ASSERT_FALSE(annulus.fastContains(box));

// Box contains outer circle
box = Box(-6, -5.5, 12);
ASSERT_FALSE(annulus.fastDisjoint(box));
ASSERT_FALSE(annulus.fastContains(box));

// Box intersects with the outer circle, but not the inner circle
box = Box(3, 3, 4);
ASSERT_FALSE(annulus.fastDisjoint(box));
ASSERT_FALSE(annulus.fastContains(box));

// Box is contained by the annulus
box = Box(2, 2, 1);
ASSERT_FALSE(annulus.fastDisjoint(box));
ASSERT_TRUE(annulus.fastContains(box));

// Box is contained by the outer circle and intersects with the inner circle
box = Box(0.4, 0.5, 3);
ASSERT_FALSE(annulus.fastDisjoint(box));
ASSERT_FALSE(annulus.fastContains(box));

// Box intersects with both outer and inner circle
box = Box(-4, -4, 4.5);
ASSERT_FALSE(annulus.fastDisjoint(box));
ASSERT_FALSE(annulus.fastContains(box));

// Box is inside the inner circle
box = Box(-0.1, -0.2, 0.5);
ASSERT_TRUE(annulus.fastDisjoint(box));
ASSERT_FALSE(annulus.fastContains(box));

// Box contains the inner circle, but intersects with the outer circle
box = Box(-2, -2, 7);
ASSERT_FALSE(annulus.fastDisjoint(box));
ASSERT_FALSE(annulus.fastContains(box));

//
// Annulus contains both inner and outer circles as boundaries.
//

// Box only touches the outer boundary
box = Box(3, 4, 1); // Lower left touches boundary
ASSERT_FALSE(annulus.fastDisjoint(box));
ASSERT_FALSE(annulus.fastContains(box));
box = Box(-4, -5, 1); // Upper right touches boundary
ASSERT_FALSE(annulus.fastDisjoint(box));
ASSERT_FALSE(annulus.fastContains(box));

// Box is contained by the annulus touching the outer boundary
box = Box(-4, -3, 0.1);
ASSERT_FALSE(annulus.fastDisjoint(box));
ASSERT_TRUE(annulus.fastContains(box));

// Box is contained by the annulus touching the inner boundary
box = Box(0, 1, 1);
ASSERT_FALSE(annulus.fastDisjoint(box));
ASSERT_TRUE(annulus.fastContains(box));

// Box only touches the inner boundary at (-0.6, 0.8)
box = Box(-0.6, 0.3, 0.5);
ASSERT_FALSE(annulus.fastDisjoint(box));
ASSERT_FALSE(annulus.fastContains(box));
}

} // namespace
91 changes: 79 additions & 12 deletions src/mongo/db/geo/shapes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ namespace mongo {

bool Polygon::contains(const Point& p) const { return contains(p, 0) > 0; }

/*
/*
* Return values:
* -1 if no intersection
* 0 if maybe an intersection (using fudge)
Expand Down Expand Up @@ -418,21 +418,36 @@ namespace mongo {
return _outer;
}

bool R2Annulus::contains(const Point& point, double maxError) const {
bool R2Annulus::contains(const Point& point) const {

// See if we're inside the inner radius
if (distanceWithin(point, _center, getInner() - maxError)) {
if (distanceCompare(point, _center, _inner) < 0) {
return false;
}

// See if we're outside the outer radius
if (!distanceWithin(point, _center, getOuter() + maxError)) {
if (distanceCompare(point, _center, _outer) > 0) {
return false;
}

return true;
}

Box R2Annulus::getR2Bounds() const {
return Box(_center.x - _outer, _center.y - _outer, 2 * _outer); // Box(_min.x, _min.y, edgeLength)
}

bool R2Annulus::fastContains(const Box& other) const {
return circleContainsBox(Circle(_outer, _center), other)
&& !circleInteriorIntersectsWithBox(Circle(_inner, _center), other);
}

bool R2Annulus::fastDisjoint(const Box& other) const {
return !circleIntersectsWithBox(Circle(_outer, _center), other)
|| circleInteriorContainsBox(Circle(_inner, _center), other);
}


/////// Other methods

/**
Expand All @@ -448,6 +463,16 @@ namespace mongo {
* (radius + center.x, center.y) or vice-versa.
*/
bool distanceWithin(const Point &p1, const Point &p2, double radius) {
return distanceCompare(p1, p2, radius) <= 0.0;
}

// Compare the distance between p1 and p2 with the radius.
// Float-number comparison might be inaccurate.
//
// > 0: distance is greater than radius
// = 0: distance equals radius
// < 0: distance is less than radius
double distanceCompare(const Point &p1, const Point &p2, double radius) {
double a = p2.x - p1.x;
double b = p2.y - p1.y;

Expand All @@ -462,23 +487,23 @@ namespace mongo {
// for all 32-bit systems, not just affected systems.
if (sizeof(void*) <= 4){
volatile double sum = p2.y > p1.y ? p1.y + radius : p2.y + radius;
return p2.y > p1.y ? sum >= p2.y : sum >= p1.y;
return p2.y > p1.y ? p2.y - sum : p1.y - sum;
} else {
// Original math, correct for most systems
return p2.y > p1.y ? p1.y + radius >= p2.y : p2.y + radius >= p1.y;
return p2.y > p1.y ? p2.y - (p1.y + radius) : p1.y - (p2.y + radius);
}
}

if (b == 0) {
if (sizeof(void*) <= 4){
volatile double sum = p2.x > p1.x ? p1.x + radius : p2.x + radius;
return p2.x > p1.x ? sum >= p2.x : sum >= p1.x;
return p2.x > p1.x ? p2.x - sum : p1.x - sum;
} else {
return p2.x > p1.x ? p1.x + radius >= p2.x : p2.x + radius >= p1.x;
return p2.x > p1.x ? p2.x - (p1.x + radius) : p1.x - (p2.x + radius);
}
}

return sqrt((a * a) + (b * b)) <= radius;
return sqrt((a * a) + (b * b)) - radius;
}

// Technically lat/long bounds, not really tied to earth radius.
Expand Down Expand Up @@ -572,8 +597,37 @@ namespace mongo {
return dotProdNormalCD_CA * dotProdNormalCD_CB <= 0; // Perhaps A or B is on line CD
}

static bool circleContainsBoxInternal(const Circle& circle,
const Box& box,
bool includeCircleBoundary) {
const Point& a = box._min;
const Point& b = box._max;
double compareLL = distanceCompare( circle.center, a, circle.radius ); // Lower left
double compareUR = distanceCompare( circle.center, b, circle.radius ); // Upper right
// Upper Left
double compareUL = distanceCompare( circle.center, Point( a.x, b.y ), circle.radius );
// Lower right
double compareLR = distanceCompare( circle.center, Point( b.x, a.y ), circle.radius );
if ( includeCircleBoundary ) {
return compareLL <= 0 && compareUR <= 0 && compareUL <= 0 && compareLR <= 0;
}
else {
return compareLL < 0 && compareUR < 0 && compareUL < 0 && compareLR < 0;
}
}

bool circleContainsBox(const Circle& circle, const Box& box) {
return circleContainsBoxInternal(circle, box, true);
}

bool circleInteriorContainsBox(const Circle& circle, const Box& box) {
return circleContainsBoxInternal(circle, box, false);
}

// Check the intersection by measuring the distance between circle center and box center.
bool circleIntersectsWithBox(const Circle& circle, const Box& box) {
static bool circleIntersectsWithBoxInternal(const Circle& circle,
const Box& box,
bool includeCircleBoundary) {
/* Collapses the four quadrants down into one.
* ________
* r|___B___ \ <- a quarter round corner here. Let's name it "D".
Expand All @@ -593,10 +647,23 @@ namespace mongo {

// Check if circle.center is in A, B or C.
// The circle center could be above the box (B) or right to the box (C), but close enough.
if ((dx <= w + r && dy <= h) || (dx <= w && dy <= h + r)) return true;
if (includeCircleBoundary) {
if ((dx <= w + r && dy <= h) || (dx <= w && dy <= h + r)) return true;
} else {
if ((dx < w + r && dy < h) || (dx < w && dy < h + r)) return true;
}

// Now check if circle.center is in the round corner "D".
return distanceWithin(Point(dx, dy), Point(w, h), r);
double compareResult = distanceCompare(Point(dx, dy), Point(w, h), r);
return compareResult < 0 || (compareResult == 0 && includeCircleBoundary);
}

bool circleIntersectsWithBox(const Circle& circle, const Box& box) {
return circleIntersectsWithBoxInternal(circle, box, true);
}

bool circleInteriorIntersectsWithBox(const Circle& circle, const Box& box) {
return circleIntersectsWithBoxInternal(circle, box, false);
}

bool lineIntersectsWithBox(const Point& a, const Point& b, const Box& box) {
Expand Down
31 changes: 21 additions & 10 deletions src/mongo/db/geo/shapes.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,16 @@ namespace mongo {
class Polygon;

double distance(const Point& p1, const Point &p2);
double distanceCompare(const Point &p1, const Point &p2, double radius);
bool distanceWithin(const Point &p1, const Point &p2, double radius);
void checkEarthBounds(const Point &p);
double spheredist_rad(const Point& p1, const Point& p2);
double spheredist_deg(const Point& p1, const Point& p2);
bool linesIntersect(const Point& pA, const Point& pB, const Point& pC, const Point& pD);
bool circleContainsBox(const Circle& circle, const Box& box);
bool circleInteriorContainsBox(const Circle& circle, const Box& box);
bool circleIntersectsWithBox(const Circle& circle, const Box& box);
bool circleInteriorIntersectsWithBox(const Circle& circle, const Box& box);
bool edgesIntersectsWithBox(const vector<Point>& vertices, const Box& box);
bool polygonContainsBox(const Polygon& polygon, const Box& box);
bool polygonIntersectsWithBox(const Polygon& polygon, const Box& box);
Expand Down Expand Up @@ -130,7 +134,7 @@ namespace mongo {

bool contains(const Point& p) const;

/*
/*
* Return values:
* -1 if no intersection
* 0 if maybe an intersection (using fudge)
Expand Down Expand Up @@ -180,14 +184,8 @@ namespace mongo {
virtual bool fastDisjoint(const Box& other) const = 0;
};

// Clearly this isn't right but currently it's sufficient.
enum CRS {
UNSET,
FLAT,
SPHERE
};

class R2Annulus {
// Annulus is used by GeoNear. Both inner and outer circles are inlcuded.
class R2Annulus : public R2Region {
public:

R2Annulus();
Expand All @@ -198,7 +196,13 @@ namespace mongo {
double getInner() const;
double getOuter() const;

bool contains(const Point& point, double maxError) const;
bool contains(const Point& point) const;

// R2Region interface
Box getR2Bounds() const;
bool fastContains(const Box& other) const;
bool fastDisjoint(const Box& other) const;


private:

Expand All @@ -207,6 +211,13 @@ namespace mongo {
double _outer;
};

// Clearly this isn't right but currently it's sufficient.
enum CRS {
UNSET,
FLAT,
SPHERE
};

struct PointWithCRS {

PointWithCRS() : crs(UNSET), flatUpgradedToSphere(false) {}
Expand Down

0 comments on commit 83f2b11

Please sign in to comment.