bool S2Cursor::ok() { if (NULL == _btreeCursor.get()) { // FieldRangeVector needs an IndexSpec so we make it one. BSONObjBuilder specBuilder; BSONObjIterator i(_keyPattern); while (i.more()) { BSONElement e = i.next(); specBuilder.append(e.fieldName(), 1); } BSONObj spec = specBuilder.obj(); IndexSpec specForFRV(spec); // All the magic is in makeUnifiedFRS. See below. // A lot of these arguments are opaque. BSONObj frsObj; if (!makeFRSObject(&frsObj)) { return false; } FieldRangeSet frs(_details->parentNS().c_str(), frsObj, false, false); shared_ptr<FieldRangeVector> frv(new FieldRangeVector(frs, specForFRV, 1)); _btreeCursor.reset(BtreeCursor::make(nsdetails(_details->parentNS()), *_details, frv, 0, 1)); return advance(); } return _btreeCursor->ok(); }
// 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; } // 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().c_str()), *_details, frv, 0, 1)); // Do the actual search through this annulus. size_t considered = 0; for (; cursor->ok(); cursor->advance()) { ++_nscanned; ++considered; MatchDetails details; bool matched = _matcher->matchesCurrent(cursor.get(), &details); if (!matched) { continue; } const BSONObj& indexedObj = cursor->currLoc().obj(); size_t geoFieldsInRange = 0; double minMatchingDistance = 1e20; // Calculate the distance from our query point(s) to the geo field(s). for (size_t i = 0; i < _fields.size(); ++i) { const GeoQueryField& field = _fields[i]; BSONElementSet geoFieldElements; indexedObj.getFieldsDotted(field.field, geoFieldElements); if (geoFieldElements.empty()) { continue; } S2Point us = field.getCentroid(); for (BSONElementSet::iterator oi = geoFieldElements.begin(); oi != geoFieldElements.end(); ++oi) { const BSONObj &geoObj = oi->Obj(); double dist = -1; S2Point them; if (GeoJSONParser::isPolygon(geoObj)) { S2Polygon shape; GeoJSONParser::parsePolygon(geoObj, &shape); them = shape.Project(us); } else if (GeoJSONParser::isLineString(geoObj)) { S2Polyline shape; GeoJSONParser::parseLineString(geoObj, &shape); int tmp; them = shape.Project(us, &tmp); } else if (GeoJSONParser::isPoint(geoObj)) { S2Cell point; GeoJSONParser::parsePoint(geoObj, &point); them = point.GetCenter(); } S1Angle angle(us, them); dist = angle.radians() * _params.radius; if (dist >= _innerRadius && dist <= _outerRadius) { ++geoFieldsInRange; minMatchingDistance = min(dist, minMatchingDistance); } } } if (_fields.size() == geoFieldsInRange) { 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 < M_PI * _params.radius); // TODO: consider shrinking _radiusIncrement if _results.size() meets some criteria. }
// 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; } // 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().c_str()), *_details, frv, 0, 1)); // Do the actual search through this annulus. size_t considered = 0; for (; cursor->ok(); cursor->advance()) { ++considered; MatchDetails details; bool matched = _matcher->matchesCurrent(cursor.get(), &details); if (!matched) { continue; } const BSONObj& indexedObj = cursor->currLoc().obj(); size_t geoFieldsInRange = 0; double minMatchingDistance = 1e20; // Calculate the distance from our query point(s) to the geo field(s). // For each geo field in the query... for (size_t i = 0; i < _fields.size(); ++i) { const QueryGeometry& field = _fields[i]; // Get all the fields with that name from the document. BSONElementSet geoFieldElements; indexedObj.getFieldsDotted(field.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 = distanceBetween(field, 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 (_fields.size() == geoFieldsInRange) { // 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 && _innerRadius < M_PI * _params.radius); // TODO: consider shrinking _radiusIncrement if _results.size() meets some criteria. }
// 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); }