diff options
-rw-r--r-- | src/mongo/SConscript | 3 | ||||
-rw-r--r-- | src/mongo/db/geo/shapes.cpp | 4 | ||||
-rw-r--r-- | src/mongo/db/geo/shapes.h | 4 | ||||
-rw-r--r-- | src/mongo/db/matcher.cpp | 69 | ||||
-rw-r--r-- | src/mongo/db/matcher.h | 88 | ||||
-rw-r--r-- | src/mongo/dbtests/matchertests.cpp | 41 |
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 ¢er, 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; |