summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/mongo/SConscript3
-rw-r--r--src/mongo/db/geo/shapes.cpp4
-rw-r--r--src/mongo/db/geo/shapes.h4
-rw-r--r--src/mongo/db/matcher.cpp69
-rw-r--r--src/mongo/db/matcher.h88
-rw-r--r--src/mongo/dbtests/matchertests.cpp41
6 files changed, 203 insertions, 6 deletions
diff --git a/src/mongo/SConscript b/src/mongo/SConscript
index 97e5d0dd71b..eaa7301adbb 100644
--- a/src/mongo/SConscript
+++ b/src/mongo/SConscript
@@ -248,7 +248,8 @@ env.StaticLibrary("coredb", [
"s/shardconnection.cpp",
],
LIBDEPS=['db/auth/serverauth',
- 'server_parameters'])
+ 'server_parameters',
+ 'geometry'])
coreServerFiles = [ "db/common.cpp",
"util/net/miniwebserver.cpp",
diff --git a/src/mongo/db/geo/shapes.cpp b/src/mongo/db/geo/shapes.cpp
index dab6ede2a9d..6ea6e7bd9be 100644
--- a/src/mongo/db/geo/shapes.cpp
+++ b/src/mongo/db/geo/shapes.cpp
@@ -141,12 +141,12 @@ namespace mongo {
onBoundary(_max.y, p.y, fudge);
}
- bool Box::inside(Point p, double fudge) {
+ bool Box::inside(Point p, double fudge) const {
bool res = inside(p.x, p.y, fudge);
return res;
}
- bool Box::inside(double x, double y, double fudge) {
+ bool Box::inside(double x, double y, double fudge) const {
return between(_min.x, _max.x , x, fudge) &&
between(_min.y, _max.y , y, fudge);
}
diff --git a/src/mongo/db/geo/shapes.h b/src/mongo/db/geo/shapes.h
index ee7451d1c05..1f33e45446e 100644
--- a/src/mongo/db/geo/shapes.h
+++ b/src/mongo/db/geo/shapes.h
@@ -62,8 +62,8 @@ namespace mongo {
void truncate(double min, double max);
void fudge(double error);
bool onBoundary(Point p, double fudge = 0);
- bool inside(Point p, double fudge = 0);
- bool inside(double x, double y, double fudge = 0);
+ bool inside(Point p, double fudge = 0) const;
+ bool inside(double x, double y, double fudge = 0) const;
bool contains(const Box& other, double fudge = 0);
// TODO(hk): This could be private and Polygon could be our friend, or we could
// have getters/setters (lots of code change).
diff --git a/src/mongo/db/matcher.cpp b/src/mongo/db/matcher.cpp
index 719dc2dfd2c..4b147ce74ad 100644
--- a/src/mongo/db/matcher.cpp
+++ b/src/mongo/db/matcher.cpp
@@ -369,8 +369,51 @@ namespace mongo {
flags = fe.valuestrsafe();
break;
}
+ case BSONObj::opWITHIN: {
+ BSONObj shapeObj = fe.embeddedObject();
+ BSONObjIterator argIt(shapeObj);
+
+ while (argIt.more()) {
+ BSONElement elt = argIt.next();
+ if (!elt.isABSONObj()) { break; }
+
+ if (!strcmp(elt.fieldName(), "$box")) {
+ BSONObjIterator coordIt(elt.Obj());
+ BSONElement minE = coordIt.next();
+ if (!minE.isABSONObj()) { break; }
+ if (!coordIt.more()) { break; }
+ BSONElement maxE = coordIt.next();
+ if (!maxE.isABSONObj()) { break; }
+ _geo.push_back(GeoMatcher::makeBox(e.fieldName(), minE.Obj(), maxE.Obj()));
+ } else if (!strcmp(elt.fieldName(), "$center")) {
+ BSONObjIterator coordIt(elt.Obj());
+ BSONElement center = coordIt.next();
+ if (!center.isABSONObj()) { break; }
+ if (!coordIt.more()) { break; }
+ BSONElement radius = coordIt.next();
+ if (!radius.isNumber()) { break; }
+ _geo.push_back(
+ GeoMatcher::makeCircle(e.fieldName(), center.Obj(), radius.number()));
+ } else if (!strcmp(elt.fieldName(), "$polygon")) {
+ BSONObjIterator coordIt(elt.Obj());
+ bool valid = true;
+ while (coordIt.more()) {
+ BSONElement coord = coordIt.next();
+ if (!coord.isABSONObj()) { valid = false; break; }
+ BSONObjIterator numIt(coord.Obj());
+ if (!numIt.more()) { valid = false; break; }
+ BSONElement x = numIt.next();
+ if (!x.isNumber()) { valid = false; break; }
+ BSONElement y = numIt.next();
+ if (!y.isNumber()) { valid = false; break; }
+ }
+ if (!valid) { break; }
+ _geo.push_back(GeoMatcher::makePolygon(e.fieldName(), elt.Obj()));
+ }
+ }
+ break;
+ }
case BSONObj::opNEAR:
- case BSONObj::opWITHIN:
case BSONObj::opINTERSECT:
case BSONObj::opMAX_DISTANCE:
break;
@@ -943,6 +986,30 @@ namespace mongo {
}
}
+ for (vector<GeoMatcher>::const_iterator it = _geo.begin(); it != _geo.end(); ++it) {
+ BSONElementSet s;
+ // XXX: when do we do this and why?
+ if (!_constrainIndexKey.isEmpty()) {
+ BSONElement e = jsobj.getFieldUsingIndexNames(it->getFieldName().c_str(), _constrainIndexKey);
+ if (Array == e.type()) {
+ BSONObjIterator i(e.Obj());
+ while (i.more()) { s.insert(i.next()); }
+ } else if (!e.eoo()) {
+ s.insert(e);
+ }
+ } else {
+ jsobj.getFieldsDotted(it->getFieldName().c_str(), s, false);
+ }
+ int matches = 0;
+ for (BSONElementSet::const_iterator i = s.begin(); i != s.end(); ++i) {
+ if (!i->isABSONObj()) { continue; }
+ Point p;
+ if (!GeoMatcher::pointFrom(i->Obj(), &p)) { continue; }
+ if (it->containsPoint(p)) { ++matches; }
+ }
+ if (0 == matches) { return false; }
+ }
+
for (vector<RegexMatcher>::const_iterator it = _regexs.begin();
it != _regexs.end();
++it) {
diff --git a/src/mongo/db/matcher.h b/src/mongo/db/matcher.h
index 2cf43d52578..aec98ffb60d 100644
--- a/src/mongo/db/matcher.h
+++ b/src/mongo/db/matcher.h
@@ -22,6 +22,7 @@
#include "jsobj.h"
#include "pcrecpp.h"
+#include "geo/shapes.h"
namespace mongo {
@@ -49,6 +50,92 @@ namespace mongo {
RegexMatcher() : _isNot() {}
};
+ class GeoMatcher {
+ private:
+ GeoMatcher(const string& field) : _isBox(false), _isCircle(false), _fieldName(field) {}
+ bool _isBox;
+ Box _box;
+
+ bool _isCircle;
+ Point _center;
+ double _radius;
+
+ bool _isPolygon;
+ Polygon _polygon;
+
+ string _fieldName;
+ public:
+ const string& getFieldName() const { return _fieldName; }
+
+ static GeoMatcher makeBox(const string& field, const BSONObj &min, const BSONObj &max) {
+ GeoMatcher m(field);
+ m._isBox = true;
+ pointFrom(min, &m._box._min);
+ pointFrom(max, &m._box._max);
+ return m;
+ }
+
+ static GeoMatcher makeCircle(const string& field, const BSONObj &center, double rad) {
+ GeoMatcher m(field);
+ m._isCircle = true;
+ pointFrom(center, &m._center);
+ m._radius = rad;
+ return m;
+ }
+
+ static GeoMatcher makePolygon(const string& field, const BSONObj &poly) {
+ GeoMatcher m(field);
+ vector<Point> points;
+
+ m._isPolygon = true;
+ BSONObjIterator coordIt(poly);
+ while (coordIt.more()) {
+ BSONElement coord = coordIt.next();
+ Point p;
+ pointFrom(coord.Obj(), &p);
+ points.push_back(p);
+ }
+ m._polygon = Polygon(points);
+ return m;
+ }
+
+ bool containsPoint(Point p) const {
+ if (_isBox) {
+ return _box.inside(p, 0);
+ } else if (_isCircle) {
+ return distance(_center, p) <= _radius;
+ } else if (_isPolygon) {
+ return _polygon.contains(p);
+ } else {
+ return false;
+ }
+ }
+
+ string toString() const {
+ stringstream ss;
+ if (_isBox) {
+ ss << "GeoMatcher Box: " << _box.toString();
+ } else if (_isCircle) {
+ ss << "GeoMatcher Circle @ " << _center.toString() << " r = " << _radius;
+ } else {
+ ss << "GeoMatcher UNKNOWN";
+ }
+ return ss.str();
+ }
+
+ static bool pointFrom(const BSONObj o, Point *p) {
+ BSONObjIterator i(o);
+ if (!i.more()) { return false; }
+ BSONElement xe = i.next();
+ if (!i.more()) { return false; }
+ BSONElement ye = i.next();
+ if (!xe.isNumber() || !ye.isNumber()) { return false; }
+ p->x = xe.number();
+ p->y = ye.number();
+ return true;
+ }
+ };
+
struct element_lt {
bool operator()(const BSONElement& l, const BSONElement& r) const {
int x = (int) l.canonicalType() - (int) r.canonicalType();
@@ -285,6 +372,7 @@ namespace mongo {
bool _atomic;
vector<RegexMatcher> _regexs;
+ vector<GeoMatcher> _geo;
// so we delete the mem when we're done:
vector< shared_ptr< BSONObjBuilder > > _builders;
diff --git a/src/mongo/dbtests/matchertests.cpp b/src/mongo/dbtests/matchertests.cpp
index 441be6bf08c..ee199d0fcd1 100644
--- a/src/mongo/dbtests/matchertests.cpp
+++ b/src/mongo/dbtests/matchertests.cpp
@@ -132,6 +132,44 @@ namespace MatcherTests {
}
};
+ class WithinBox {
+ public:
+ void run() {
+ Matcher m(fromjson("{loc:{$within:{$box:[{x: 4, y:4},[6,6]]}}}"));
+ ASSERT(!m.matches(fromjson("{loc: [3,4]}")));
+ ASSERT(m.matches(fromjson("{loc: [4,4]}")));
+ ASSERT(m.matches(fromjson("{loc: [5,5]}")));
+ ASSERT(m.matches(fromjson("{loc: [5,5.1]}")));
+ ASSERT(m.matches(fromjson("{loc: {x: 5, y:5.1}}")));
+ }
+ };
+
+ class WithinPolygon {
+ public:
+ void run() {
+ Matcher m(fromjson("{loc:{$within:{$polygon:[{x:0,y:0},[0,5],[5,5],[5,0]]}}}"));
+ ASSERT(m.matches(fromjson("{loc: [3,4]}")));
+ ASSERT(m.matches(fromjson("{loc: [4,4]}")));
+ ASSERT(m.matches(fromjson("{loc: {x:5,y:5}}")));
+ ASSERT(!m.matches(fromjson("{loc: [5,5.1]}")));
+ ASSERT(!m.matches(fromjson("{loc: {}}")));
+ }
+ };
+
+ class WithinCenter {
+ public:
+ void run() {
+ Matcher m(fromjson("{loc:{$within:{$center:[{x:30,y:30},10]}}}"));
+ ASSERT(!m.matches(fromjson("{loc: [3,4]}")));
+ ASSERT(m.matches(fromjson("{loc: {x:30,y:30}}")));
+ ASSERT(m.matches(fromjson("{loc: [20,30]}")));
+ ASSERT(m.matches(fromjson("{loc: [30,20]}")));
+ ASSERT(m.matches(fromjson("{loc: [40,30]}")));
+ ASSERT(m.matches(fromjson("{loc: [30,40]}")));
+ ASSERT(!m.matches(fromjson("{loc: [31,40]}")));
+ }
+ };
+
/** Test that MatchDetails::elemMatchKey() is set correctly after a match. */
class ElemMatchKey {
public:
@@ -371,6 +409,9 @@ namespace MatcherTests {
add<Covered::ElemMatchKeyIndexedSingleKey>();
add<AllTiming>();
add<Visit>();
+ add<WithinBox>();
+ add<WithinCenter>();
+ add<WithinPolygon>();
}
} dball;