// Get the index keys for elements that are GeoJSON. void S2AccessMethod::getGeoKeys(const BSONElementSet& elements, BSONObjSet* out) const { for (BSONElementSet::iterator i = elements.begin(); i != elements.end(); ++i) { uassert(16754, "Can't parse geometry from element: " + i->toString(), i->isABSONObj()); const BSONObj &obj = i->Obj(); vector<string> cells; bool succeeded = S2SearchUtil::getKeysForObject(obj, _params, &cells); uassert(16755, "Can't extract geo keys from object, malformed geometry?:" + obj.toString(), succeeded); uassert(16756, "Unable to generate keys for (likely malformed) geometry: " + obj.toString(), cells.size() > 0); for (vector<string>::const_iterator it = cells.begin(); it != cells.end(); ++it) { BSONObjBuilder b; b.append("", *it); out->insert(b.obj()); } } if (0 == out->size()) { BSONObjBuilder b; b.appendNull(""); out->insert(b.obj()); } }
// Get the index keys for elements that are GeoJSON. void S2AccessMethod::getGeoKeys(const BSONElementSet& elements, BSONObjSet* out) const { S2RegionCoverer coverer; _params.configureCoverer(&coverer); // See here for GeoJSON format: geojson.org/geojson-spec.html for (BSONElementSet::iterator i = elements.begin(); i != elements.end(); ++i) { uassert(16754, "Can't parse geometry from element: " + i->toString(), i->isABSONObj()); const BSONObj &obj = i->Obj(); vector<string> cells; S2Polyline line; S2Cell point; // We only support GeoJSON polygons. Why?: // 1. we don't automagically do WGS84/flat -> WGS84, and // 2. the old polygon format must die. if (GeoParser::isGeoJSONPolygon(obj)) { S2Polygon polygon; GeoParser::parseGeoJSONPolygon(obj, &polygon); keysFromRegion(&coverer, polygon, &cells); } else if (GeoParser::parseLineString(obj, &line)) { keysFromRegion(&coverer, line, &cells); } else if (GeoParser::parsePoint(obj, &point)) { S2CellId parent(point.id().parent(_params.finestIndexedLevel)); cells.push_back(parent.toString()); } else { uasserted(16755, "Can't extract geo keys from object, malformed geometry?:" + obj.toString()); } uassert(16756, "Unable to generate keys for (likely malformed) geometry: " + obj.toString(), cells.size() > 0); for (vector<string>::const_iterator it = cells.begin(); it != cells.end(); ++it) { BSONObjBuilder b; b.append("", *it); out->insert(b.obj()); } } if (0 == out->size()) { BSONObjBuilder b; b.appendNull(""); out->insert(b.obj()); } }
// 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; }
// Get the index keys for elements that are GeoJSON. void getGeoKeys(const BSONElementSet &elements, BSONObjSet *out) const { S2RegionCoverer coverer; _params.configureCoverer(&coverer); // See here for GeoJSON format: geojson.org/geojson-spec.html for (BSONElementSet::iterator i = elements.begin(); i != elements.end(); ++i) { if (!i->isABSONObj()) { continue; } // error? const BSONObj &obj = i->Obj(); vector<string> cells; S2Polygon polygon; S2Polyline line; S2Cell point; if (GeoParser::parsePolygon(obj, &polygon)) { keysFromRegion(&coverer, polygon, &cells); } else if (GeoParser::parseLineString(obj, &line)) { keysFromRegion(&coverer, line, &cells); } else if (GeoParser::parsePoint(obj, &point)) { keysFromRegion(&coverer, point, &cells); } else { uasserted(16572, "Can't extract geo keys from object, malformed geometry?:" + obj.toString()); } for (vector<string>::const_iterator it = cells.begin(); it != cells.end(); ++it) { BSONObjBuilder b; b.append("", *it); out->insert(b.obj()); } } if (0 == out->size()) { BSONObjBuilder b; b.appendNull(""); out->insert(b.obj()); } }
PlanStage::StageState S2NearStage::addResultToQueue(WorkingSetID* out) { PlanStage::StageState state = _child->work(out); // All done reading from _child. if (PlanStage::IS_EOF == state) { _child.reset(); _keyGeoFilter.reset(); // Adjust the annulus size depending on how many results we got. if (_results.empty()) { _radiusIncrement *= 2; } else if (_results.size() < 300) { _radiusIncrement *= 2; } else if (_results.size() > 600) { _radiusIncrement /= 2; } // Make a new ixscan next time. return PlanStage::NEED_TIME; } // Nothing to do unless we advance. if (PlanStage::ADVANCED != state) { return state; } WorkingSetMember* member = _ws->get(*out); // Must have an object in order to get geometry out of it. verify(member->hasObj()); // The scans we use don't dedup so we must dedup them ourselves. We only put locs into here // if we know for sure whether or not we'll return them in this annulus. if (member->hasLoc()) { if (_seenInScan.end() != _seenInScan.find(member->loc)) { return PlanStage::NEED_TIME; } } // Get all the fields with that name from the document. BSONElementSet geom; member->obj.getFieldsDotted(_params.nearQuery.field, geom, false); if (geom.empty()) { return PlanStage::NEED_TIME; } // Some value that any distance we can calculate will be less than. double minDistance = numeric_limits<double>::max(); BSONObj minDistanceObj; for (BSONElementSet::iterator git = geom.begin(); git != geom.end(); ++git) { if (!git->isABSONObj()) { mongoutils::str::stream ss; ss << "s2near stage read invalid geometry element " << *git << " from child"; Status status(ErrorCodes::InternalError, ss); *out = WorkingSetCommon::allocateStatusMember( _ws, status); return PlanStage::FAILURE; } BSONObj obj = git->Obj(); double distToObj; if (S2SearchUtil::distanceBetween(_params.nearQuery.centroid.point, obj, &distToObj)) { if (distToObj < minDistance) { minDistance = distToObj; minDistanceObj = obj; } } else { warning() << "unknown geometry: " << obj.toString(); } } // If we're here we'll either include the doc in this annulus or reject it. It's safe to // ignore it if it pops up again in this annulus. if (member->hasLoc()) { _seenInScan.insert(member->loc); } // If the distance to the doc satisfies our distance criteria, add it to our buffered // results. if (minDistance >= _innerRadius && (_outerRadiusInclusive ? minDistance <= _outerRadius : minDistance < _outerRadius)) { _results.push(Result(*out, minDistance)); if (_params.addDistMeta) { // FLAT implies the output distances are in radians. Convert to meters. if (FLAT == _params.nearQuery.centroid.crs) { member->addComputed(new GeoDistanceComputedData(minDistance / kRadiusOfEarthInMeters)); } else { member->addComputed(new GeoDistanceComputedData(minDistance)); } } if (_params.addPointMeta) { member->addComputed(new GeoNearPointComputedData(minDistanceObj)); } if (member->hasLoc()) { _invalidationMap[member->loc] = *out; } } return PlanStage::NEED_TIME; }
// 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; }
PlanStage::StageState S2NearStage::addResultToQueue(WorkingSetID* out) { PlanStage::StageState state = _child->work(out); // All done reading from _child. if (PlanStage::IS_EOF == state) { _child.reset(); // Adjust the annulus size depending on how many results we got. if (_results.empty()) { _radiusIncrement *= 2; } else if (_results.size() < 300) { _radiusIncrement *= 2; } else if (_results.size() > 600) { _radiusIncrement /= 2; } // Make a new ixscan next time. return PlanStage::NEED_TIME; } // Nothing to do unless we advance. if (PlanStage::ADVANCED != state) { return state; } // TODO Speed improvements: // // 0. Modify fetch to preserve key data and test for intersection w/annulus. // // 1. keep track of what we've seen in this scan and possibly ignore it. // // 2. keep track of results we've returned before and ignore them. WorkingSetMember* member = _ws->get(*out); // Must have an object in order to get geometry out of it. verify(member->hasObj()); // Get all the fields with that name from the document. BSONElementSet geom; member->obj.getFieldsDotted(_params.nearQuery.field, geom, false); if (geom.empty()) {return PlanStage::NEED_TIME; } // Some value that any distance we can calculate will be less than. double minDistance = numeric_limits<double>::max(); BSONObj minDistanceObj; for (BSONElementSet::iterator git = geom.begin(); git != geom.end(); ++git) { if (!git->isABSONObj()) { mongoutils::str::stream ss; ss << "s2near stage read invalid geometry element " << *git << " from child"; Status status(ErrorCodes::InternalError, ss); *out = WorkingSetCommon::allocateStatusMember( _ws, status); return PlanStage::FAILURE; } BSONObj obj = git->Obj(); double distToObj; if (S2SearchUtil::distanceBetween(_params.nearQuery.centroid.point, obj, &distToObj)) { if (distToObj < minDistance) { minDistance = distToObj; minDistanceObj = obj; } } else { warning() << "unknown geometry: " << obj.toString(); } } // If the distance to the doc satisfies our distance criteria, add it to our buffered // results. if (minDistance >= _innerRadius && (_outerRadiusInclusive ? minDistance <= _outerRadius : minDistance < _outerRadius)) { _results.push(Result(*out, minDistance)); if (_params.addDistMeta) { member->addComputed(new GeoDistanceComputedData(minDistance)); } if (_params.addPointMeta) { member->addComputed(new GeoNearPointComputedData(minDistanceObj)); } if (member->hasLoc()) { _invalidationMap[member->loc] = *out; } } return PlanStage::NEED_TIME; }
// 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); }