summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorA. Jesse Jiryu Davis <jesse@10gen.com>2013-07-01 16:51:30 -0400
committerMatt Kangas <matt.kangas@10gen.com>2013-07-10 14:37:48 -0500
commit53dab689a519b4daf6a5c4772123b74616c0283b (patch)
tree46c1d0cf2aa8d2db26d0ae1b707c024644dec64d
parent1556be30b24b7a00038182e233a52e6c20385c26 (diff)
downloadmongo-53dab689a519b4daf6a5c4772123b74616c0283b.tar.gz
SERVER-9395 Implement $minDistance for geoNear command
Squashed: - Spelling in comment. - $minDistance option for $near queries with 2dsphere index. SERVER-9395 - Implement $minDistance for geoNear command. SERVER-9395 - uassert that geoNear's min/maxDistance param is non-negative - More $nearSphere tests. SERVER-9395 - uassert that minDistance isn't used with 2d index. 2dsphere index is required. - More informative error if assert.throws is misused. Signed-off-by: Matt Kangas <matt.kangas@10gen.com>
-rw-r--r--jstests/geo_mindistance.js338
-rw-r--r--jstests/geo_mindistance_boundaries.js119
-rw-r--r--src/mongo/db/geo/geoquery.cpp45
-rw-r--r--src/mongo/db/geo/geoquery.h9
-rw-r--r--src/mongo/db/index/2d_index_cursor.cpp9
-rw-r--r--src/mongo/db/index/s2_near_cursor.cpp5
-rw-r--r--src/mongo/db/index/s2_near_cursor.h2
-rw-r--r--src/mongo/shell/assert.js2
8 files changed, 514 insertions, 15 deletions
diff --git a/jstests/geo_mindistance.js b/jstests/geo_mindistance.js
new file mode 100644
index 00000000000..d9909a5a4e9
--- /dev/null
+++ b/jstests/geo_mindistance.js
@@ -0,0 +1,338 @@
+/* Test $minDistance option for $near and $nearSphere queries, and geoNear
+ * command. SERVER-9395.
+*/
+var t = db.geo_mindistance;
+t.drop();
+
+//
+// Useful constants and functions.
+//
+
+var km = 1000,
+ earthRadiusMeters = 6378.1 * km;
+
+function metersToRadians(m) { return m / earthRadiusMeters; }
+
+/* Count documents within some radius of (0, 0), in kilometers.
+ * With this function we can use the existing $maxDistance option to test
+ * the newer $minDistance option's behavior.
+ */
+function n_docs_within(radius_km) {
+ // geoNear's distances are in meters for geoJSON points.
+ var cmdResult = db.runCommand({
+ geoNear: t.getName(),
+ near: {type: 'Point', coordinates: [0, 0]},
+ spherical: true,
+ maxDistance: radius_km * km,
+ num: 1000
+ });
+
+ return cmdResult.results.length;
+}
+
+//
+// Setup.
+//
+
+/* Make 121 points from long, lat = (0, 0) (in Gulf of Guinea) to (10, 10)
+ * (inland Nigeria).
+ */
+for (var x = 0; x <= 10; x += 1) {
+ for (var y = 0; y <= 10; y += 1) {
+ t.insert({loc: [x, y]});
+ }
+}
+
+/* $minDistance is supported for 2dsphere index only, not 2d or geoHaystack. */
+t.ensureIndex({loc: "2dsphere"});
+
+var n_docs = t.count(),
+ geoJSONPoint = {$geometry: {type: 'Point', coordinates: [0, 0]}},
+ legacyPoint = [0, 0];
+
+//
+// Test $near with GeoJSON point (required for $near with 2dsphere index).
+// min/maxDistance are in meters.
+//
+
+var n_min1400_count = t.find({loc: {
+ $near: geoJSONPoint, $minDistance: 1400 * km
+}}).count();
+
+assert.eq(
+ n_docs - n_docs_within(1400),
+ n_min1400_count,
+ "Expected " + (n_docs - n_docs_within(1400))
+ + " points $near (0, 0) with $minDistance 1400 km, got "
+ + n_min1400_count
+);
+
+var n_bw500_and_1000_count = t.find({loc: {
+ $near: geoJSONPoint,
+ $minDistance: 500 * km,
+ $maxDistance: 1000 * km
+}}).count();
+
+assert.eq(
+ n_docs_within(1000) - n_docs_within(500),
+ n_bw500_and_1000_count,
+ "Expected " + (n_docs_within(1000) - n_docs_within(500))
+ + " points $near (0, 0) with $minDistance 500 km and $maxDistance 1000 km, got "
+ + n_bw500_and_1000_count
+);
+
+//
+// $nearSphere with 2dsphere index can take a legacy or GeoJSON point.
+// First test $nearSphere with legacy point.
+// min/maxDistance are in radians.
+//
+
+n_min1400_count = t.find({loc: {
+ $nearSphere: legacyPoint, $minDistance: metersToRadians(1400 * km)
+}}).count();
+
+assert.eq(
+ n_docs - n_docs_within(1400),
+ n_min1400_count,
+ "Expected " + (n_docs - n_docs_within(1400))
+ + " points $nearSphere (0, 0) with $minDistance 1400 km, got "
+ + n_min1400_count
+);
+
+n_bw500_and_1000_count = t.find({loc: {
+ $nearSphere: legacyPoint,
+ $minDistance: metersToRadians(500 * km),
+ $maxDistance: metersToRadians(1000 * km)
+}}).count();
+
+assert.eq(
+ n_docs_within(1000) - n_docs_within(500),
+ n_bw500_and_1000_count,
+ "Expected " + (n_docs_within(1000) - n_docs_within(500))
+ + " points $nearSphere (0, 0) with $minDistance 500 km and $maxDistance 1000 km, got "
+ + n_bw500_and_1000_count
+);
+
+//
+// Test $nearSphere with GeoJSON point.
+// min/maxDistance are in meters.
+//
+
+n_min1400_count = t.find({loc: {
+ $nearSphere: geoJSONPoint, $minDistance: 1400 * km
+}}).count();
+
+assert.eq(
+ n_docs - n_docs_within(1400),
+ n_min1400_count,
+ "Expected " + (n_docs - n_docs_within(1400))
+ + " points $nearSphere (0, 0) with $minDistance 1400 km, got "
+ + n_min1400_count
+);
+
+n_bw500_and_1000_count = t.find({loc: {
+ $nearSphere: geoJSONPoint,
+ $minDistance: 500 * km,
+ $maxDistance: 1000 * km
+}}).count();
+
+assert.eq(
+ n_docs_within(1000) - n_docs_within(500),
+ n_bw500_and_1000_count,
+ "Expected " + (n_docs_within(1000) - n_docs_within(500))
+ + " points $nearSphere (0, 0) with $minDistance 500 km and $maxDistance 1000 km, got "
+ + n_bw500_and_1000_count
+);
+
+
+//
+// Test geoNear command with GeoJSON point.
+// Distances are in meters.
+//
+
+var cmdResult = db.runCommand({
+ geoNear: t.getName(),
+ near: {type: 'Point', coordinates: [0, 0]},
+ minDistance: 1400 * km,
+ spherical: true // spherical required for 2dsphere index
+});
+assert.eq(
+ n_docs - n_docs_within(1400),
+ cmdResult.results.length,
+ "Expected " + (n_docs - n_docs_within(1400))
+ + " points geoNear (0, 0) with $minDistance 1400 km, got "
+ + cmdResult.results.length
+);
+
+cmdResult = db.runCommand({
+ geoNear: t.getName(),
+ near: {type: 'Point', coordinates: [0, 0]},
+ minDistance: 500 * km,
+ maxDistance: 1000 * km,
+ spherical: true
+});
+assert.eq(
+ n_docs_within(1000) - n_docs_within(500),
+ cmdResult.results.length,
+ "Expected " + (n_docs_within(1000) - n_docs_within(500))
+ + " points geoNear (0, 0) with $minDistance 500 km and $maxDistance 1000 km, got "
+ + cmdResult.results.length
+);
+
+//
+// Test geoNear command with legacy point.
+// Distances are in radians.
+//
+
+cmdResult = db.runCommand({
+ geoNear: t.getName(),
+ near: legacyPoint,
+ minDistance: metersToRadians(1400 * km),
+ spherical: true // spherical required for 2dsphere index
+});
+assert.eq(
+ n_docs - n_docs_within(1400),
+ cmdResult.results.length,
+ "Expected " + (n_docs - n_docs_within(1400))
+ + " points geoNear (0, 0) with $minDistance 1400 km, got "
+ + cmdResult.results.length
+);
+
+cmdResult = db.runCommand({
+ geoNear: t.getName(),
+ near: legacyPoint,
+ minDistance: metersToRadians(500 * km),
+ maxDistance: metersToRadians(1000 * km),
+ spherical: true
+});
+assert.eq(
+ n_docs_within(1000) - n_docs_within(500),
+ cmdResult.results.length,
+ "Expected " + (n_docs_within(1000) - n_docs_within(500))
+ + " points geoNear (0, 0) with $minDistance 500 km and $maxDistance 1000 km, got "
+ + cmdResult.results.length
+);
+
+//
+// Test $minDistance input validation for $near and $nearSphere queries,
+// and for geoNear command.
+//
+
+/** Some bad inputs for $near and $nearSphere. */
+var badMinDistance, badMinDistances = [-1, undefined, 'foo'];
+for (var i = 0; i < badMinDistances.length; i++) {
+ badMinDistance = badMinDistances[i];
+
+ assert.throws(
+ function(minDistance) {
+ t.find({loc: {$nearSphere: geoJSONPoint, $minDistance: minDistance}}).next();
+ },
+ [badMinDistance],
+ "$nearSphere with GeoJSON point should've failed with $minDistance = " + badMinDistance);
+
+ assert.throws(
+ function(minDistance) {
+ t.find({loc: {$nearSphere: legacyPoint, $minDistance: minDistance}}).next();
+ },
+ [badMinDistance],
+ "$nearSphere with legacy coordinates should've failed with $minDistance = " + badMinDistance);
+
+ assert.throws(
+ function(minDistance) {
+ t.find({loc: {$near: geoJSONPoint, $minDistance: minDistance}}).next();
+ },
+ [badMinDistance],
+ "$near with GeoJSON point should've failed with $minDistance = " + badMinDistance);
+
+ assert.commandFailed(
+ db.runCommand({
+ geoNear: t.getName(),
+ near: legacyPoint,
+ minDistance: badMinDistance,
+ spherical: true
+ }),
+ "geoNear with legacy coordinates should've failed with $minDistance = " + badMinDistance);
+
+ assert.commandFailed(
+ db.runCommand({
+ geoNear: t.getName(),
+ near: {type: 'Point', coordinates: [0, 0]},
+ minDistance: badMinDistance,
+ spherical: true
+ }),
+ "geoNear with GeoJSON point should've failed with $minDistance = " + badMinDistance);
+}
+
+/* Can't be more than half earth radius in meters. */
+badMinDistance = Math.PI * earthRadiusMeters + 10;
+assert.throws(
+ function(minDistance) {
+ t.find({loc: {$near: geoJSONPoint, $minDistance: minDistance}}).next();
+ },
+ [badMinDistance],
+ "$near should've failed with $minDistance = " + badMinDistance);
+
+assert.throws(
+ function(minDistance) {
+ t.find({loc: {$nearSphere: geoJSONPoint, $minDistance: minDistance}}).next();
+ },
+ [badMinDistance],
+ "$nearSphere should've failed with $minDistance = " + badMinDistance);
+
+/* Can't be more than pi. */
+badMinDistance = Math.PI + 0.1;
+assert.throws(
+ function(minDistance) {
+ t.find({loc: {$nearSphere: legacyPoint, $minDistance: minDistance}}).next();
+ },
+ [badMinDistance],
+ "$near should've failed with $minDistance = " + badMinDistance);
+
+//
+// Verify that we throw errors using 2d index with $minDistance.
+// ($minDistance requires a 2dsphere index, not supported with 2d.)
+//
+
+t.dropIndexes();
+t.ensureIndex({loc: "2d"});
+
+assert.throws(
+ function() {
+ t.find({loc: {$near: legacyPoint, $minDistance: 1}}).next();
+ }, [],
+ "$near with legacy coordinates and $minDistance should've failed with 2d index"
+);
+
+assert.throws(
+ function() {
+ t.find({loc: {$near: geoJSONPoint, $minDistance: 1}}).next();
+ }, [],
+ "$near with GeoJSON point and $minDistance should've failed with 2d index"
+);
+
+assert.throws(
+ function() {
+ t.find({loc: {$nearSphere: legacyPoint, $minDistance: 1}}).next();
+ }, [],
+ "$nearSphere with legacy coordinates and $minDistance should've failed with 2d index"
+);
+
+assert.throws(
+ function() {
+ t.find({loc: {$nearSphere: geoJSONPoint, $minDistance: 1}}).next();
+ }, [],
+ "$nearSphere with GeoJSON point and $minDistance should've failed with 2d index"
+);
+
+/* geoNear command with 2d index requires legacy point, not GeoJSON.
+ * It fails here, even with a legacy point, because of minDistance.
+*/
+assert.commandFailed(
+ db.runCommand({
+ geoNear: t.getName(),
+ near: legacyPoint,
+ minDistance: 1,
+ spherical: true
+ }),
+ "geoNear with legacy coordinates and $minDistance should've failed with 2d index"
+);
diff --git a/jstests/geo_mindistance_boundaries.js b/jstests/geo_mindistance_boundaries.js
new file mode 100644
index 00000000000..2b6c26afb3f
--- /dev/null
+++ b/jstests/geo_mindistance_boundaries.js
@@ -0,0 +1,119 @@
+/* Test boundary conditions for $minDistance option for $near and $nearSphere
+ * queries. SERVER-9395.
+*/
+var t = db.geo_mindistance_boundaries;
+t.drop();
+t.insert({loc: [1, 0]}); // 1 degree of longitude from origin.
+
+/* $minDistance is supported for 2dsphere index only, not 2d or geoHaystack. */
+t.ensureIndex({loc: "2dsphere"});
+
+//
+// Useful constants.
+//
+
+var km = 1000,
+ earthRadiusMeters = 6378.1 * km,
+ geoJSONPoint = {$geometry: {type: 'Point', coordinates: [0, 0]}},
+ // One degree of longitude at the equator, about 111 km.
+ degreeInMeters = 2 * Math.PI * earthRadiusMeters / 360,
+ metersEpsilon = Number.MIN_VALUE;
+
+/* Grow epsilon's exponent until epsilon exceeds the margin of error for the
+ * representation of degreeInMeters. The server uses 64-bit math, too, so we'll
+ * find the smallest epsilon the server can detect.
+*/
+while (degreeInMeters + metersEpsilon == degreeInMeters) { metersEpsilon *= 2; }
+
+//
+// Test boundary conditions for $near and GeoJSON, in meters.
+//
+
+assert.eq(
+ 1, t.find({loc: {
+ $near: geoJSONPoint,
+ $minDistance: degreeInMeters
+ }}).count(),
+ "Expected to find (0, 1) within $minDistance 1 degree from origin"
+);
+
+assert.eq(
+ 1, t.find({loc: {
+ $near: geoJSONPoint,
+ $minDistance: degreeInMeters - metersEpsilon
+ }}).count(),
+ "Expected to find (0, 1) within $minDistance (1 degree - epsilon) from origin"
+);
+
+assert.eq(
+ 0, t.find({loc: {
+ $near: geoJSONPoint,
+ $minDistance: degreeInMeters + metersEpsilon
+ }}).count(),
+ "Expected *not* to find (0, 1) within $minDistance (1 degree + epsilon) from origin"
+);
+
+//
+// Test boundary conditions for $nearSphere and GeoJSON, in meters.
+//
+
+assert.eq(
+ 1, t.find({loc: {
+ $nearSphere: geoJSONPoint,
+ $minDistance: degreeInMeters
+ }}).count(),
+ "Expected to find (0, 1) within $minDistance 1 degree from origin"
+);
+
+assert.eq(
+ 1, t.find({loc: {
+ $nearSphere: geoJSONPoint,
+ $minDistance: degreeInMeters - metersEpsilon
+ }}).count(),
+ "Expected to find (0, 1) within $minDistance (1 degree - epsilon) from origin"
+);
+
+assert.eq(
+ 0, t.find({loc: {
+ $nearSphere: geoJSONPoint,
+ $minDistance: degreeInMeters + metersEpsilon
+ }}).count(),
+ "Expected *not* to find (0, 1) within $minDistance (1 degree + epsilon) from origin"
+);
+
+//
+// Test boundary conditions for $nearSphere and a legacy point, in radians.
+//
+// $minDistance with legacy point requires $nearSphere; $near not
+// supported.
+//
+
+var legacyPoint = [0, 0],
+ degreeInRadians = 2 * Math.PI / 360,
+ radiansEpsilon = Number.MIN_VALUE;
+
+while (1 + radiansEpsilon == 1) { radiansEpsilon *= 2; }
+
+assert.eq(
+ 1, t.find({loc: {
+ $nearSphere: legacyPoint,
+ $minDistance: degreeInRadians
+ }}).count(),
+ "Expected to find (0, 1) within $minDistance 1 degree from origin"
+);
+
+assert.eq(
+ 1, t.find({loc: {
+ $nearSphere: legacyPoint,
+ $minDistance: degreeInRadians - radiansEpsilon
+ }}).count(),
+ "Expected to find (0, 1) within $minDistance (1 degree - epsilon) from origin"
+);
+
+assert.eq(
+ 0, t.find({loc: {
+ $nearSphere: legacyPoint,
+ $minDistance: degreeInRadians + radiansEpsilon
+ }}).count(),
+ "Expected *not* to find (0, 1) within $minDistance (1 degree + epsilon) from origin"
+);
diff --git a/src/mongo/db/geo/geoquery.cpp b/src/mongo/db/geo/geoquery.cpp
index 46ef5da8642..6e561ddd287 100644
--- a/src/mongo/db/geo/geoquery.cpp
+++ b/src/mongo/db/geo/geoquery.cpp
@@ -28,9 +28,24 @@ namespace mongo {
// The CRS for the legacy points dictates that distances are in radians.
fromRadians = (FLAT == centroid.crs);
+ if (!obj["minDistance"].eoo()) {
+ if (obj["minDistance"].isNumber()) {
+ double distArg = obj["minDistance"].number();
+ uassert(16901, "minDistance must be non-negative", distArg >= 0.0);
+ if (fromRadians) {
+ minDistance = distArg * radius;
+ } else {
+ minDistance = distArg;
+ }
+ } else {
+ return false;
+ }
+ }
+
if (!obj["maxDistance"].eoo()) {
if (obj["maxDistance"].isNumber()) {
double distArg = obj["maxDistance"].number();
+ uassert(16902, "maxDistance must be non-negative", distArg >= 0.0);
if (fromRadians) {
maxDistance = distArg * radius;
} else {
@@ -46,14 +61,15 @@ namespace mongo {
bool NearQuery::parseFrom(const BSONObj &obj, double radius) {
bool hasGeometry = false;
- // First, try legacy near.
- // Legacy near parsing: t.find({ loc : { $nearSphere: [0,0], $maxDistance: 3 }})
- // Legacy near parsing: t.find({ loc : { $nearSphere: [0,0] }})
- // Legacy near parsing: t.find({ loc : { $near: { someGeoJSONPoint}})
+ // First, try legacy near, e.g.:
+ // t.find({ loc : { $nearSphere: [0,0], $minDistance: 1, $maxDistance: 3 }})
+ // t.find({ loc : { $nearSphere: [0,0] }})
+ // t.find({ loc : { $near: { someGeoJSONPoint}})
BSONObjIterator it(obj);
while (it.more()) {
BSONElement e = it.next();
bool isNearSphere = mongoutils::str::equals(e.fieldName(), "$nearSphere");
+ bool isMinDistance = mongoutils::str::equals(e.fieldName(), "$minDistance");
bool isMaxDistance = mongoutils::str::equals(e.fieldName(), "$maxDistance");
bool isNear = mongoutils::str::equals(e.fieldName(), "$near")
|| mongoutils::str::equals(e.fieldName(), "$geoNear");
@@ -71,19 +87,26 @@ namespace mongo {
// We don't accept $near : [oldstylepoint].
hasGeometry = true;
}
+ } else if (isMinDistance) {
+ uassert(16893, "$minDistance must be a number", e.isNumber());
+ minDistance = e.Number();
+ uassert(16894, "$minDistance must be non-negative", minDistance >= 0.0);
} else if (isMaxDistance) {
+ uassert(16895, "$maxDistance must be a number", e.isNumber());
maxDistance = e.Number();
+ uassert(16896, "$maxDistance must be non-negative", maxDistance >= 0.0);
}
}
if (fromRadians) {
+ minDistance *= radius;
maxDistance *= radius;
}
if (hasGeometry) { return true; }
- // Next, try "new" near
- // New near: t.find({ "geo" : { "$near" : { "$geometry" : pointA, $maxDistance : 20 }}})
+ // Next, try "new" near:
+ // t.find({"geo" : {"$near" : {"$geometry": pointA, $minDistance: 1, $maxDistance: 3}}})
BSONElement e = obj.firstElement();
if (!e.isABSONObj()) { return false; }
BSONObj::MatchType matchType = static_cast<BSONObj::MatchType>(e.getGtLtOp());
@@ -103,10 +126,14 @@ namespace mongo {
(SPHERE == centroid.crs));
hasGeometry = true;
}
+ } else if (mongoutils::str::equals(e.fieldName(), "$minDistance")) {
+ uassert(16897, "$minDistance must be a number", e.isNumber());
+ minDistance = e.Number();
+ uassert(16898, "$minDistance must be non-negative", minDistance >= 0.0);
} else if (mongoutils::str::equals(e.fieldName(), "$maxDistance")) {
- if (e.isNumber()) {
- maxDistance = e.Number();
- }
+ uassert(16899, "$maxDistance must be a number", e.isNumber());
+ maxDistance = e.Number();
+ uassert(16900, "$maxDistance must be non-negative", maxDistance >= 0.0);
}
}
return hasGeometry;
diff --git a/src/mongo/db/geo/geoquery.h b/src/mongo/db/geo/geoquery.h
index 31ecb5df311..7a18007b905 100644
--- a/src/mongo/db/geo/geoquery.h
+++ b/src/mongo/db/geo/geoquery.h
@@ -86,14 +86,17 @@ namespace mongo {
class NearQuery {
public:
- NearQuery() : maxDistance(std::numeric_limits<double>::max()), fromRadians(false) {}
- NearQuery(const string& f) : field(f), maxDistance(std::numeric_limits<double>::max()),
+ NearQuery() : minDistance(0), maxDistance(std::numeric_limits<double>::max()),
+ fromRadians(false) {}
+ NearQuery(const string& f) : field(f), minDistance(0),
+ maxDistance(std::numeric_limits<double>::max()),
fromRadians(false) {}
bool parseFrom(const BSONObj &obj, double radius);
bool parseFromGeoNear(const BSONObj &obj, double radius);
string field;
PointWithCRS centroid;
- // Distance IN METERS that we're willing to search.
+ // Min and max distance IN METERS from centroid that we're willing to search.
+ double minDistance;
double maxDistance;
// Did we convert to this distance from radians? (If so, we output distances in radians.)
bool fromRadians;
diff --git a/src/mongo/db/index/2d_index_cursor.cpp b/src/mongo/db/index/2d_index_cursor.cpp
index ccad127e17a..9cd88fbc333 100644
--- a/src/mongo/db/index/2d_index_cursor.cpp
+++ b/src/mongo/db/index/2d_index_cursor.cpp
@@ -52,7 +52,7 @@ namespace mongo {
};
inline double computeXScanDistance(double y, double maxDistDegrees) {
- // TODO: this overestimates for large madDistDegrees far from the equator
+ // TODO: this overestimates for large maxDistDegrees far from the equator
return maxDistDegrees / min(cos(deg2rad(min(+89.0, y + maxDistDegrees))),
cos(deg2rad(max(-89.0, y - maxDistDegrees))));
}
@@ -1778,6 +1778,9 @@ namespace mongo {
const Point n(cmdObj["near"]);
result.append("near", params.geoHashConverter->hash(cmdObj["near"]).toString());
+ uassert(16903, "'minDistance' param not supported for 2d index, requires 2dsphere index",
+ cmdObj["minDistance"].eoo());
+
double maxDistance = numeric_limits<double>::max();
if (cmdObj["maxDistance"].isNumber())
maxDistance = cmdObj["maxDistance"].number();
@@ -1883,6 +1886,10 @@ namespace mongo {
type = twod_internal::GEO_PLANE; // prevents uninitialized warning
}
+ uassert(16904,
+ "'$minDistance' param not supported for 2d index, requires 2dsphere index",
+ n["$minDistance"].eoo());
+
double maxDistance = numeric_limits<double>::max();
if (e.isABSONObj() && e.embeddedObject().nFields() > 2) {
BSONObjIterator i(e.embeddedObject());
diff --git a/src/mongo/db/index/s2_near_cursor.cpp b/src/mongo/db/index/s2_near_cursor.cpp
index 2a5cd197b16..242b5c50227 100644
--- a/src/mongo/db/index/s2_near_cursor.cpp
+++ b/src/mongo/db/index/s2_near_cursor.cpp
@@ -67,13 +67,16 @@ namespace mongo {
++_nearFieldIndex;
}
+ _minDistance = max(0.0, _nearQuery.minDistance);
+
// _outerRadius can't be greater than (pi * r) or we wrap around the opposite
// side of the world.
_maxDistance = min(M_PI * _params.radius, _nearQuery.maxDistance);
+ uassert(16892, "$minDistance too large", _minDistance < _maxDistance);
// Start with a conservative _radiusIncrement.
_radiusIncrement = 5 * S2::kAvgEdge.GetValue(_params.finestIndexedLevel) * _params.radius;
- _innerRadius = _outerRadius = 0;
+ _innerRadius = _outerRadius = _minDistance;
// We might want to adjust the sizes of our coverings if our search
// isn't local to the start point.
// Set up _outerRadius with proper checks (maybe maxDistance is really small?)
diff --git a/src/mongo/db/index/s2_near_cursor.h b/src/mongo/db/index/s2_near_cursor.h
index 480db1210ac..070d67f07dd 100644
--- a/src/mongo/db/index/s2_near_cursor.h
+++ b/src/mongo/db/index/s2_near_cursor.h
@@ -116,6 +116,8 @@ namespace mongo {
BSONObj _specForFRV;
// Geo-related variables.
+ // At what min distance (arc length) do we start looking for results?
+ double _minDistance;
// What's the max distance (arc length) we're willing to look for results?
double _maxDistance;
diff --git a/src/mongo/shell/assert.js b/src/mongo/shell/assert.js
index dea85b762b1..5cd70582d8d 100644
--- a/src/mongo/shell/assert.js
+++ b/src/mongo/shell/assert.js
@@ -197,7 +197,7 @@ assert.time = function(f, msg, timeout /*ms*/) {
assert.throws = function(func, params, msg){
if (assert._debug && msg) print("in assert for: " + msg);
if (params && typeof(params) == "string")
- throw "2nd argument to assert.throws has to be an array"
+ throw ("2nd argument to assert.throws has to be an array, not " + params);
try {
func.apply(null, params);
}