bool GeoMatchExpression::matchesSingleElement( const BSONElement& e ) const { if ( !e.isABSONObj()) return false; GeometryContainer geometry; if ( !geometry.parseFromStorage( e ).isOK() ) return false; // Never match big polygon if (geometry.getNativeCRS() == STRICT_SPHERE) return false; // Project this geometry into the CRS of the query if (!geometry.supportsProject(_query->getGeometry().getNativeCRS())) return false; geometry.projectInto(_query->getGeometry().getNativeCRS()); if (GeoExpression::WITHIN == _query->getPred()) { return _query->getGeometry().contains(geometry); } else { verify(GeoExpression::INTERSECT == _query->getPred()); return _query->getGeometry().intersects(geometry); } }
bool GeoMatchExpression::matchesSingleElement( const BSONElement& e ) const { if ( !e.isABSONObj()) return false; GeometryContainer container; if ( !container.parseFrom( e.Obj() ) ) return false; return _query.satisfiesPredicate( container ); }
bool S2SearchUtil::getKeysForObject(const BSONObj& obj, const S2IndexingParams& params, vector<string>* out) { S2RegionCoverer coverer; params.configureCoverer(&coverer); GeometryContainer geoContainer; if (!geoContainer.parseFrom(obj)) { return false; } if (!geoContainer.hasS2Region()) { return false; } keysFromRegion(&coverer, geoContainer.getRegion(), out); return true; }
bool GeoMatchExpression::matchesSingleElement( const BSONElement& e ) const { if ( !e.isABSONObj()) return false; GeometryContainer geometry; if ( !geometry.parseFrom( e.Obj() ) ) return false; if (GeoQuery::WITHIN == _query->getPred()) { return _query->getGeometry().contains(geometry); } else { verify(GeoQuery::INTERSECT == _query->getPred()); return _query->getGeometry().intersects(geometry); } }
// This is the actual search. bool S2Cursor::advance() { if (_numToReturn <= 0) { return false; } for (; _btreeCursor->ok(); _btreeCursor->advance()) { ++_nscanned; if (_seen.end() != _seen.find(_btreeCursor->currLoc())) { continue; } _seen.insert(_btreeCursor->currLoc()); ++_matchTested; MatchDetails details; bool matched = _matcher->matchesCurrent(_btreeCursor.get(), &details); if (!matched) { continue; } const BSONObj &indexedObj = _btreeCursor->currLoc().obj(); ++_geoTested; size_t geoFieldsMatched = 0; // OK, cool, non-geo match satisfied. See if the object actually overlaps w/the geo // query fields. for (size_t i = 0; i < _fields.size(); ++i) { BSONElementSet geoFieldElements; indexedObj.getFieldsDotted(_fields[i].getField(), geoFieldElements, false); if (geoFieldElements.empty()) { continue; } bool match = false; for (BSONElementSet::iterator oi = geoFieldElements.begin(); !match && (oi != geoFieldElements.end()); ++oi) { if (!oi->isABSONObj()) { continue; } const BSONObj &geoObj = oi->Obj(); GeometryContainer geoContainer; uassert(16698, "malformed geometry: " + geoObj.toString(), geoContainer.parseFrom(geoObj)); match = _fields[i].satisfiesPredicate(geoContainer); } if (match) { ++geoFieldsMatched; } } if (geoFieldsMatched == _fields.size()) { // We have a winner! And we point at it. --_numToReturn; return true; } } return false; }
void S2IndexingParams::configureCoverer(const GeometryContainer& geoContainer, S2RegionCoverer* coverer) const { // Points indexed to the finest level was introduced in version 3 // For backwards compatibility, only do this if the version is > 2 if (indexVersion >= S2_INDEX_VERSION_3 && geoContainer.isPoint()) { coverer->set_min_level(kPointIndexedLevel); coverer->set_max_level(kPointIndexedLevel); } else { coverer->set_min_level(coarsestIndexedLevel); coverer->set_max_level(finestIndexedLevel); } // This is advisory; the two above are strict. coverer->set_max_cells(maxCellsInCovering); }
// Fill _results with all of the results in the annulus defined by _innerRadius and // _outerRadius. If no results are found, grow the annulus and repeat until success (or // until the edge of the world). void S2NearIndexCursor::fillResults() { verify(_results.empty()); if (_innerRadius >= _outerRadius) { return; } if (_innerRadius > _maxDistance) { return; } // We iterate until 1. our search radius is too big or 2. we find results. do { // Some of these arguments are opaque, look at the definitions of the involved classes. FieldRangeSet frs(_descriptor->parentNS().c_str(), makeFRSObject(), false, false); shared_ptr<FieldRangeVector> frv(new FieldRangeVector(frs, _specForFRV, 1)); scoped_ptr<BtreeCursor> cursor(BtreeCursor::make(nsdetails(_descriptor->parentNS()), _descriptor->getOnDisk(), frv, 0, 1)); // The cursor may return the same obj more than once for a given // FRS, so we make sure to only consider it once in any given annulus. // // We don't want this outside of the 'do' loop because the covering // for an annulus may return an object whose distance to the query // point is actually contained in a subsequent annulus. If we // didn't consider every object in a given annulus we might miss // the point. // // We don't use a global 'seen' because we get that by requiring // the distance from the query point to the indexed geo to be // within our 'current' annulus, and I want to dodge all yield // issues if possible. unordered_set<DiskLoc, DiskLoc::Hasher> seen; LOG(1) << "looking at annulus from " << _innerRadius << " to " << _outerRadius << endl; LOG(1) << "Total # returned: " << _stats._numReturned << endl; // Do the actual search through this annulus. for (; cursor->ok(); cursor->advance()) { // Don't bother to look at anything we've returned. if (_returned.end() != _returned.find(cursor->currLoc())) { ++_stats._returnSkip; continue; } ++_stats._nscanned; if (seen.end() != seen.find(cursor->currLoc())) { ++_stats._btreeDups; continue; } // Get distance interval from our query point to the cell. // If it doesn't overlap with our current shell, toss. BSONObj currKey(cursor->currKey()); BSONObjIterator it(currKey); BSONElement geoKey; for (int i = 0; i <= _nearFieldIndex; ++i) { geoKey = it.next(); } S2Cell keyCell = S2Cell(S2CellId::FromString(geoKey.String())); if (!_annulus.MayIntersect(keyCell)) { ++_stats._keyGeoSkip; continue; } // We have to add this document to seen *AFTER* the key intersection test. // A geometry may have several keys, one of which may be in our search shell and one // of which may be outside of it. We don't want to ignore a document just because // one of its covers isn't inside this annulus. seen.insert(cursor->currLoc()); // At this point forward, we will not examine the document again in this annulus. const BSONObj& indexedObj = cursor->currLoc().obj(); // Match against indexed geo fields. ++_stats._geoMatchTested; size_t geoFieldsMatched = 0; // See if the object actually overlaps w/the geo query fields. for (size_t i = 0; i < _indexedGeoFields.size(); ++i) { BSONElementSet geoFieldElements; indexedObj.getFieldsDotted(_indexedGeoFields[i].getField(), geoFieldElements, false); if (geoFieldElements.empty()) { continue; } bool match = false; for (BSONElementSet::iterator oi = geoFieldElements.begin(); !match && (oi != geoFieldElements.end()); ++oi) { if (!oi->isABSONObj()) { continue; } const BSONObj &geoObj = oi->Obj(); GeometryContainer geoContainer; uassert(16762, "ill-formed geometry: " + geoObj.toString(), geoContainer.parseFrom(geoObj)); match = _indexedGeoFields[i].satisfiesPredicate(geoContainer); } if (match) { ++geoFieldsMatched; } } if (geoFieldsMatched != _indexedGeoFields.size()) { continue; } // Get all the fields with that name from the document. BSONElementSet geoFieldElements; indexedObj.getFieldsDotted(_nearQuery.field, geoFieldElements, false); if (geoFieldElements.empty()) { continue; } ++_stats._inAnnulusTested; double minDistance = 1e20; // Look at each field in the document and take the min. distance. for (BSONElementSet::iterator oi = geoFieldElements.begin(); oi != geoFieldElements.end(); ++oi) { if (!oi->isABSONObj()) { continue; } double dist = distanceTo(oi->Obj()); minDistance = min(dist, minDistance); } // We could be in an annulus, yield, add new points closer to // query point than the last point we returned, then unyield. // This would return points out of order. if (minDistance < _returnedDistance) { continue; } // If the min. distance satisfies our distance criteria if (minDistance >= _innerRadius && minDistance < _outerRadius) { // The result is valid. We have to de-dup ourselves here. if (_returned.end() == _returned.find(cursor->currLoc())) { _results.push(Result(cursor->currLoc(), cursor->currKey(), minDistance)); } } } if (_results.empty()) { LOG(1) << "results empty!\n"; _radiusIncrement *= 2; nextAnnulus(); } else if (_results.size() < 300) { _radiusIncrement *= 2; } else if (_results.size() > 600) { _radiusIncrement /= 2; } } while (_results.empty() && _innerRadius < _maxDistance && _innerRadius < _outerRadius); LOG(1) << "Filled shell with " << _results.size() << " results" << endl; }
// Fill _results with the next shell of results. We may have to search several times to do // this. If _results.empty() after calling fillResults, there are no more possible results. void S2NearCursor::fillResults() { verify(_results.empty()); if (_innerRadius >= _outerRadius) { return; } if (_innerRadius > _maxDistance) { return; } if (0 == _numToReturn) { return; } // We iterate until 1. our search radius is too big or 2. we find results. do { // Some of these arguments are opaque, look at the definitions of the involved classes. FieldRangeSet frs(_details->parentNS().c_str(), makeFRSObject(), false, false); shared_ptr<FieldRangeVector> frv(new FieldRangeVector(frs, _specForFRV, 1)); scoped_ptr<BtreeCursor> cursor(BtreeCursor::make(nsdetails(_details->parentNS()), *_details, frv, 0, 1)); // The cursor may return the same obj more than once for a given // FRS, so we make sure to only consider it once in any given annulus. // // We don't want this outside of the 'do' loop because the covering // for an annulus may return an object whose distance to the query // point is actually contained in a subsequent annulus. If we // didn't consider every object in a given annulus we might miss // the point. // // We don't use a global 'seen' because we get that by requiring // the distance from the query point to the indexed geo to be // within our 'current' annulus, and I want to dodge all yield // issues if possible. set<DiskLoc> seen; LOG(1) << "looking at annulus from " << _innerRadius << " to " << _outerRadius << endl; // Do the actual search through this annulus. for (; cursor->ok(); cursor->advance()) { ++_nscanned; if (seen.end() != seen.find(cursor->currLoc())) { continue; } seen.insert(cursor->currLoc()); // Match against non-indexed fields. ++_matchTested; MatchDetails details; bool matched = _matcher->matchesCurrent(cursor.get(), &details); if (!matched) { continue; } const BSONObj& indexedObj = cursor->currLoc().obj(); ++_geoTested; // Match against indexed geo fields. size_t geoFieldsMatched = 0; // OK, cool, non-geo match satisfied. See if the object actually overlaps w/the geo // query fields. for (size_t i = 0; i < _indexedGeoFields.size(); ++i) { BSONElementSet geoFieldElements; indexedObj.getFieldsDotted(_indexedGeoFields[i].getField(), geoFieldElements, false); if (geoFieldElements.empty()) { continue; } bool match = false; for (BSONElementSet::iterator oi = geoFieldElements.begin(); !match && (oi != geoFieldElements.end()); ++oi) { if (!oi->isABSONObj()) { continue; } const BSONObj &geoObj = oi->Obj(); GeometryContainer geoContainer; uassert(16699, "ill-formed geometry: " + geoObj.toString(), geoContainer.parseFrom(geoObj)); match = _indexedGeoFields[i].satisfiesPredicate(geoContainer); } if (match) { ++geoFieldsMatched; } } if (geoFieldsMatched != _indexedGeoFields.size()) { continue; } // Finally, see if the item is in our search annulus. size_t geoFieldsInRange = 0; double minMatchingDistance = 1e20; // Get all the fields with that name from the document. BSONElementSet geoFieldElements; indexedObj.getFieldsDotted(_nearQuery.field, geoFieldElements, false); if (geoFieldElements.empty()) { continue; } // For each field with that name in the document... for (BSONElementSet::iterator oi = geoFieldElements.begin(); oi != geoFieldElements.end(); ++oi) { if (!oi->isABSONObj()) { continue; } double dist = distanceTo(oi->Obj()); // If it satisfies our distance criteria... if (dist >= _innerRadius && dist <= _outerRadius) { // Success! For this field. ++geoFieldsInRange; minMatchingDistance = min(dist, minMatchingDistance); } } // If all the geo query fields had something in range if (geoFieldsInRange > 0) { // The result is valid. We have to de-dup ourselves here. if (_returned.end() == _returned.find(cursor->currLoc())) { _results.push(Result(cursor->currLoc(), cursor->currKey(), minMatchingDistance)); } } } if (_results.empty()) { _radiusIncrement *= 2; nextAnnulus(); } } while (_results.empty() && _innerRadius < _maxDistance && _innerRadius < _outerRadius); }