summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGreg Studer <greg@10gen.com>2014-07-18 15:28:37 -0400
committerGreg Studer <greg@10gen.com>2014-07-23 16:52:36 -0400
commit2cbd9f1d77ce6b25e9957d3121fccb521caf848f (patch)
tree98376940b7d7de3f23877f72cbe2e9008a64b8a7
parent7174d92cc1ad3dd24430769b72c05b1ee6fbcd74 (diff)
downloadmongo-2cbd9f1d77ce6b25e9957d3121fccb521caf848f.tar.gz
SERVER-5800 changes for geo expression index performance, avoid preprojecting
- additional covering parameters - only project points when required
-rw-r--r--jstests/core/geo_max.js2
-rw-r--r--src/mongo/SConscript31
-rw-r--r--src/mongo/db/commands/geo_near_cmd.cpp2
-rw-r--r--src/mongo/db/exec/geo_near.cpp66
-rw-r--r--src/mongo/db/exec/geo_near.h2
-rw-r--r--src/mongo/db/geo/SConscript31
-rw-r--r--src/mongo/db/geo/geo_query.cpp285
-rw-r--r--src/mongo/db/geo/geo_query.h118
-rw-r--r--src/mongo/db/geo/geometry_container.cpp (renamed from src/mongo/db/geo/geoquery.cpp)286
-rw-r--r--src/mongo/db/geo/geometry_container.h (renamed from src/mongo/db/geo/geoquery.h)142
-rw-r--r--src/mongo/db/geo/geoparser.cpp39
-rw-r--r--src/mongo/db/geo/r2_region_coverer_test.cpp2
-rw-r--r--src/mongo/db/geo/s2common.cpp1
-rw-r--r--src/mongo/db/geo/shapes.cpp47
-rw-r--r--src/mongo/db/geo/shapes.h21
-rw-r--r--src/mongo/db/index/SConscript5
-rw-r--r--src/mongo/db/index/expression_index.h217
-rw-r--r--src/mongo/db/index/expression_keys_private.cpp10
-rw-r--r--src/mongo/db/matcher/expression_geo.cpp6
-rw-r--r--src/mongo/db/matcher/expression_geo.h2
-rw-r--r--src/mongo/db/query/SConscript3
-rw-r--r--src/mongo/db/query/canonical_query_test.cpp2
-rw-r--r--src/mongo/db/query/expression_index.cpp209
-rw-r--r--src/mongo/db/query/expression_index.h60
-rw-r--r--src/mongo/db/query/expression_index_knobs.cpp39
-rw-r--r--src/mongo/db/query/expression_index_knobs.h47
-rw-r--r--src/mongo/db/query/index_bounds_builder.cpp11
-rw-r--r--src/mongo/db/query/planner_ixselect.cpp7
-rw-r--r--src/mongo/db/query/query_solution.h2
29 files changed, 987 insertions, 708 deletions
diff --git a/jstests/core/geo_max.js b/jstests/core/geo_max.js
index 03932004b75..70fde0c35ec 100644
--- a/jstests/core/geo_max.js
+++ b/jstests/core/geo_max.js
@@ -44,6 +44,8 @@ assert.eq(test.t.find({loc:{$near:[ 180, 0]}}, {_id:0}).limit(2).toArray(), [{lo
assert.eq(test.t.find({loc:{$near:[-180, 0]}}, {_id:0}).limit(2).toArray(), [{loc: [-180, 0]}, {loc: [-179.999, 0]}])
// These will need to change when SERVER-1760 is fixed
+printjson(test.t.find({loc:{$nearSphere:[ 180, 0]}}, {_id:0}).limit(2).explain());
assert.eq(test.t.find({loc:{$nearSphere:[ 180, 0]}}, {_id:0}).limit(2).toArray(), [{loc: [ 180, 0]}, {loc: [ 179.999, 0]}])
+printjson(test.t.find({loc:{$nearSphere:[-180, 0]}}, {_id:0}).limit(2).explain());
assert.eq(test.t.find({loc:{$nearSphere:[-180, 0]}}, {_id:0}).limit(2).toArray(), [{loc: [-180, 0]}, {loc: [-179.999, 0]}])
diff --git a/src/mongo/SConscript b/src/mongo/SConscript
index bdda44ee17c..2c289b82f10 100644
--- a/src/mongo/SConscript
+++ b/src/mongo/SConscript
@@ -24,6 +24,7 @@ env.SConscript(['base/SConscript',
'db/catalog/SConscript',
'db/commands/SConscript',
'db/concurrency/SConscript',
+ 'db/geo/SConscript',
'db/exec/SConscript',
'db/fts/SConscript',
'db/index/SConscript',
@@ -179,8 +180,9 @@ env.Library('expressions',
env.Library('expressions_geo',
['db/matcher/expression_geo.cpp',
- 'db/matcher/expression_parser_geo.cpp'],
- LIBDEPS=['expressions','geoquery','geoparser'] )
+ 'db/matcher/expression_parser_geo.cpp',
+ 'db/geo/geo_query.cpp'],
+ LIBDEPS=['expressions','db/geo/geometry','db/geo/geoparser'] )
env.Library('expressions_text',
['db/matcher/expression_text.cpp',
@@ -488,8 +490,6 @@ coredbEnv.Library("coredb", [
'db/commands/server_status_core',
'db/common',
'server_parameters',
- 'geoparser',
- 'geoquery',
'expressions',
'expressions_geo',
'expressions_text',
@@ -835,27 +835,6 @@ if has_option( 'use-cpu-profiler' ):
env.Library("defaultversion", "s/default_version.cpp")
-# Geo
-env.Library("geometry", [ "db/geo/hash.cpp", "db/geo/shapes.cpp", ],
- LIBDEPS = [ "bson",
- "$BUILD_DIR/third_party/s2/s2" ])
-env.Library("geoparser", [ "db/geo/geoparser.cpp", ],
- LIBDEPS = [ "bson",
- "geometry",
- '$BUILD_DIR/third_party/s2/s2' ])
-env.Library("geoquery", [ "db/geo/geoquery.cpp", "db/geo/r2_region_coverer.cpp", ],
- LIBDEPS = [ "bson",
- "geometry",
- "geoparser",
- '$BUILD_DIR/third_party/s2/s2' ])
-
-env.CppUnitTest("hash_test", [ "db/geo/hash_test.cpp" ],
- LIBDEPS = ["geometry", "db/common" ]) # db/common needed for field parsing
-env.CppUnitTest("geoparser_test", [ "db/geo/geoparser_test.cpp" ],
- LIBDEPS = ["geoparser", "db/common" ]) # db/common needed for field parsing
-env.CppUnitTest("r2_region_coverer_test", [ "db/geo/r2_region_coverer_test.cpp" ],
- LIBDEPS = [ "geoquery", "db/common" ]) # db/common needed for field parsing
-
env.CppUnitTest(
target="index_filter_commands_test",
source=[
@@ -925,8 +904,6 @@ serveronlyLibdeps = ["coreshard",
"db/concurrency/lock_mgr",
"db/ops/update_driver",
"defaultversion",
- "geoparser",
- "geoquery",
"global_optime",
"index_key_validate",
"index_set",
diff --git a/src/mongo/db/commands/geo_near_cmd.cpp b/src/mongo/db/commands/geo_near_cmd.cpp
index 28e9e802e23..7b85946e9cf 100644
--- a/src/mongo/db/commands/geo_near_cmd.cpp
+++ b/src/mongo/db/commands/geo_near_cmd.cpp
@@ -35,7 +35,7 @@
#include "mongo/db/commands.h"
#include "mongo/db/curop.h"
#include "mongo/db/geo/geoconstants.h"
-#include "mongo/db/geo/geoquery.h"
+#include "mongo/db/geo/geo_query.h"
#include "mongo/db/geo/s2common.h"
#include "mongo/db/index_names.h"
#include "mongo/db/index/index_descriptor.h"
diff --git a/src/mongo/db/exec/geo_near.cpp b/src/mongo/db/exec/geo_near.cpp
index ed570ed0e4f..dc62c17236f 100644
--- a/src/mongo/db/exec/geo_near.cpp
+++ b/src/mongo/db/exec/geo_near.cpp
@@ -36,8 +36,10 @@
#include "mongo/db/exec/fetch.h"
#include "mongo/db/exec/working_set_computed_data.h"
#include "mongo/db/geo/geoconstants.h"
-#include "mongo/db/index/expression_index.h"
+#include "mongo/db/geo/geoparser.h"
#include "mongo/db/matcher/expression.h"
+#include "mongo/db/query/expression_index.h"
+#include "mongo/db/query/expression_index_knobs.h"
namespace mongo {
@@ -134,7 +136,7 @@ namespace mongo {
// Must have an object in order to get geometry out of it.
invariant(member->hasObj());
- CRS queryCRS = nearParams.nearQuery.getQueryCRS();
+ CRS queryCRS = nearParams.nearQuery.centroid.crs;
// Extract all the geometries out of this document for the near query
OwnedPointerVector<StoredGeometry> geometriesOwned;
@@ -171,7 +173,7 @@ namespace mongo {
}
if (nearParams.addDistMeta) {
- if (nearParams.nearQuery.unitsAreRadians()) {
+ if (nearParams.nearQuery.unitsAreRadians) {
// Hack for nearSphere
// TODO: Remove nearSphere?
invariant(SPHERE == queryCRS);
@@ -190,37 +192,34 @@ namespace mongo {
return StatusWith<double>(minDistance);
}
- static Point toPoint(const PointWithCRS& ptCRS) {
- if (ptCRS.crs == FLAT) {
- return ptCRS.oldPoint;
- }
- else {
- S2LatLng latLng(ptCRS.point);
- return Point(latLng.lng().degrees(), latLng.lat().degrees());
- }
- }
-
static R2Annulus geoNearDistanceBounds(const NearQuery& query) {
- if (FLAT == query.getQueryCRS()) {
- return R2Annulus(toPoint(query.centroid), query.minDistance, query.maxDistance);
+ const CRS queryCRS = query.centroid.crs;
+
+ if (FLAT == queryCRS) {
+ return R2Annulus(query.centroid.oldPoint, query.minDistance, query.maxDistance);
}
- invariant(SPHERE == query.getQueryCRS());
+ invariant(SPHERE == queryCRS);
// TODO: Tighten this up a bit by making a CRS for "sphere with radians"
double minDistance = query.minDistance;
double maxDistance = query.maxDistance;
- if (query.unitsAreRadians()) {
+ if (query.unitsAreRadians) {
// Our input bounds are in radians, convert to meters since the query CRS is actually
// SPHERE. We'll convert back to radians on outputting distances.
minDistance *= kRadiusOfEarthInMeters;
maxDistance *= kRadiusOfEarthInMeters;
}
- invariant(SPHERE == query.getQueryCRS());
- return R2Annulus(toPoint(query.centroid),
+ // GOTCHA: oldPoint is a misnomer - it is the original point data and is in the correct
+ // CRS. We must not try to derive the original point from the spherical S2Point generated
+ // as an optimization - the mapping is not 1->1 - [-180, 0] and [180, 0] map to the same
+ // place.
+ // TODO: Wrapping behavior should not depend on the index, which would make $near code
+ // insensitive to which direction we explore the index in.
+ return R2Annulus(query.centroid.oldPoint,
min(minDistance, kMaxEarthDistanceInMeters),
min(maxDistance, kMaxEarthDistanceInMeters));
}
@@ -233,8 +232,9 @@ namespace mongo {
const IndexDescriptor* twoDIndex) {
R2Annulus fullBounds = geoNearDistanceBounds(nearParams.nearQuery);
+ const CRS queryCRS = nearParams.nearQuery.centroid.crs;
- if (FLAT == nearParams.nearQuery.getQueryCRS()) {
+ if (FLAT == queryCRS) {
// Reset the full bounds based on our index bounds
GeoHashConverter::Parameters hashParams;
@@ -253,8 +253,8 @@ namespace mongo {
else {
// Spherical queries have upper bounds set by the earth - no-op
// TODO: Wrapping errors would creep in here if nearSphere wasn't defined to not wrap
- invariant(SPHERE == nearParams.nearQuery.getQueryCRS());
- invariant(!nearParams.nearQuery.isWrappingQuery());
+ invariant(SPHERE == queryCRS);
+ invariant(!nearParams.nearQuery.isWrappingQuery);
}
return fullBounds;
@@ -262,7 +262,7 @@ namespace mongo {
static double twoDBoundsIncrement(const GeoNearParams& nearParams) {
// TODO: Revisit and tune these
- if (FLAT == nearParams.nearQuery.getQueryCRS()) {
+ if (FLAT == nearParams.nearQuery.centroid.crs) {
return 10;
}
else {
@@ -460,10 +460,11 @@ namespace mongo {
// max radius. This is slightly conservative for now (box diagonal vs circle radius).
double minBoundsIncrement = hasher.getError() / 2;
- if (FLAT == query.getQueryCRS())
+ const CRS queryCRS = query.centroid.crs;
+ if (FLAT == queryCRS)
return minBoundsIncrement;
- invariant(SPHERE == query.getQueryCRS());
+ invariant(SPHERE == queryCRS);
// If this is a spherical query, units are in meters - this is just a heuristic
return minBoundsIncrement * kMetersPerDegreeAtEquator;
@@ -522,8 +523,11 @@ namespace mongo {
// Get a covering region for this interval
//
+ const CRS queryCRS = _nearParams.nearQuery.centroid.crs;
+
auto_ptr<R2Region> coverRegion;
- if (_nearParams.nearQuery.getQueryCRS() == FLAT) {
+
+ if (FLAT == queryCRS) {
// NOTE: Due to floating point math issues, FLAT searches of a 2D index need to treat
// containment and distance separately.
@@ -567,7 +571,7 @@ namespace mongo {
nextBounds.getOuter() + epsilon));
}
else {
- invariant(SPHERE == _nearParams.nearQuery.getQueryCRS());
+ invariant(SPHERE == queryCRS);
// TODO: As above, make this consistent with $within : $centerSphere
// Our intervals aren't in the same CRS as our index, so we need to adjust them
@@ -598,7 +602,11 @@ namespace mongo {
OrderedIntervalList coveredIntervals;
coveredIntervals.name = scanParams.bounds.fields[twoDFieldPosition].name;
- ExpressionMapping::cover2d(*coverRegion, _twoDIndex->infoObj(), &coveredIntervals);
+
+ ExpressionMapping::cover2d(*coverRegion,
+ _twoDIndex->infoObj(),
+ internalGeoNearQuery2DMaxCoveringCells,
+ &coveredIntervals);
// Intersect the $near bounds we just generated into the bounds we have for anything else
// in the scan (i.e. $within)
@@ -630,7 +638,7 @@ namespace mongo {
_nearParams.fullFilter ? _nearParams.fullFilter->shallowClone() : NULL;
// FLAT searches need to add an additional annulus $within matcher, see above
- if (FLAT == _nearParams.nearQuery.getQueryCRS()) {
+ if (FLAT == queryCRS) {
MatchExpression* withinMatcher =
new TwoDPtInAnnulusExpression(_fullBounds, twoDFieldName);
diff --git a/src/mongo/db/exec/geo_near.h b/src/mongo/db/exec/geo_near.h
index bc3eccf6f00..8f576bc82ed 100644
--- a/src/mongo/db/exec/geo_near.h
+++ b/src/mongo/db/exec/geo_near.h
@@ -32,7 +32,7 @@
#include "mongo/db/exec/working_set.h"
#include "mongo/db/exec/plan_stats.h"
#include "mongo/db/index/index_descriptor.h"
-#include "mongo/db/geo/geoquery.h"
+#include "mongo/db/geo/geo_query.h"
#include "mongo/db/matcher/expression.h"
#include "mongo/db/query/index_bounds.h"
diff --git a/src/mongo/db/geo/SConscript b/src/mongo/db/geo/SConscript
new file mode 100644
index 00000000000..888dd3b02f0
--- /dev/null
+++ b/src/mongo/db/geo/SConscript
@@ -0,0 +1,31 @@
+# -*- mode: python -*-
+
+Import("env")
+
+# Core geometry shape libraries
+env.Library("geometry", [ "hash.cpp",
+ "shapes.cpp",
+ "r2_region_coverer.cpp" ],
+ LIBDEPS = [ "$BUILD_DIR/mongo/bson",
+ "$BUILD_DIR/third_party/s2/s2" ])
+
+# Geometry / BSON parsing and wrapping
+env.Library("geoparser", [ "geoparser.cpp",
+ "geometry_container.cpp" ],
+ LIBDEPS = [ "geometry",
+ "$BUILD_DIR/mongo/bson",
+ "$BUILD_DIR/third_party/s2/s2" ])
+
+env.CppUnitTest("hash_test", [ "hash_test.cpp" ],
+ LIBDEPS = ["geometry",
+ "$BUILD_DIR/mongo/db/common" ]) # db/common needed for field parsing
+
+env.CppUnitTest("geoparser_test", [ "geoparser_test.cpp" ],
+ LIBDEPS = [ "geoparser",
+ "$BUILD_DIR/mongo/db/common" ]) # db/common needed for field parsing
+
+env.CppUnitTest("r2_region_coverer_test", [ "r2_region_coverer_test.cpp" ],
+ LIBDEPS = [ "geometry",
+ "geoparser",
+ "$BUILD_DIR/mongo/db/common" ]) # db/common needed for field parsing
+
diff --git a/src/mongo/db/geo/geo_query.cpp b/src/mongo/db/geo/geo_query.cpp
new file mode 100644
index 00000000000..05ae6fa4757
--- /dev/null
+++ b/src/mongo/db/geo/geo_query.cpp
@@ -0,0 +1,285 @@
+/**
+ * Copyright (C) 2013 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#include "mongo/db/geo/geo_query.h"
+
+#include "mongo/db/geo/geoparser.h"
+#include "mongo/util/mongoutils/str.h"
+
+namespace mongo {
+
+ using mongoutils::str::equals;
+
+ bool NearQuery::parseLegacyQuery(const BSONObj &obj) {
+
+ bool hasGeometry = false;
+
+ // 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 : [0, 0, 1] } });
+ // t.find({ loc : { $near: { someGeoJSONPoint}})
+ // t.find({ loc : { $geoNear: { someGeoJSONPoint}})
+ BSONObjIterator it(obj);
+ while (it.more()) {
+ BSONElement e = it.next();
+ if (equals(e.fieldName(), "$near") || equals(e.fieldName(), "$geoNear")
+ || equals(e.fieldName(), "$nearSphere")) {
+ if (!e.isABSONObj()) { return false; }
+ BSONObj embeddedObj = e.embeddedObject();
+
+ if ((GeoParser::isPoint(embeddedObj) && GeoParser::parsePoint(embeddedObj, &centroid))
+ || GeoParser::parsePointWithMaxDistance(embeddedObj, &centroid, &maxDistance)) {
+ uassert(18522, "max distance must be non-negative", maxDistance >= 0.0);
+ hasGeometry = true;
+ isNearSphere = equals(e.fieldName(), "$nearSphere");
+ }
+ } else if (equals(e.fieldName(), "$minDistance")) {
+ uassert(16893, "$minDistance must be a number", e.isNumber());
+ minDistance = e.Number();
+ uassert(16894, "$minDistance must be non-negative", minDistance >= 0.0);
+ } else if (equals(e.fieldName(), "$maxDistance")) {
+ uassert(16895, "$maxDistance must be a number", e.isNumber());
+ maxDistance = e.Number();
+ uassert(16896, "$maxDistance must be non-negative", maxDistance >= 0.0);
+ } else if (equals(e.fieldName(), "$uniqueDocs")) {
+ warning() << "ignoring deprecated option $uniqueDocs";
+ }
+ }
+
+ return hasGeometry;
+ }
+
+ Status NearQuery::parseNewQuery(const BSONObj &obj) {
+ bool hasGeometry = false;
+
+ BSONObjIterator objIt(obj);
+ if (!objIt.more()) {
+ return Status(ErrorCodes::BadValue, "empty geo near query object");
+ }
+ BSONElement e = objIt.next();
+ // Just one arg. to $geoNear.
+ if (objIt.more()) {
+ return Status(ErrorCodes::BadValue, mongoutils::str::stream() <<
+ "geo near accepts just one argument when querying for a GeoJSON " <<
+ "point. Extra field found: " << objIt.next());
+ }
+
+ // Parse "new" near:
+ // t.find({"geo" : {"$near" : {"$geometry": pointA, $minDistance: 1, $maxDistance: 3}}})
+ // t.find({"geo" : {"$geoNear" : {"$geometry": pointA, $minDistance: 1, $maxDistance: 3}}})
+ if (!e.isABSONObj()) {
+ return Status(ErrorCodes::BadValue, "geo near query argument is not an object");
+ }
+ BSONObj::MatchType matchType = static_cast<BSONObj::MatchType>(e.getGtLtOp());
+ if (BSONObj::opNEAR != matchType) {
+ return Status(ErrorCodes::BadValue, mongoutils::str::stream() <<
+ "invalid geo near query operator: " << e.fieldName());
+ }
+
+ // Iterate over the argument.
+ BSONObjIterator it(e.embeddedObject());
+ while (it.more()) {
+ BSONElement e = it.next();
+ if (equals(e.fieldName(), "$geometry")) {
+ if (e.isABSONObj()) {
+ BSONObj embeddedObj = e.embeddedObject();
+ uassert(16885, "$near requires a point, given " + embeddedObj.toString(),
+ GeoParser::isPoint(embeddedObj));
+ if (!GeoParser::parsePoint(embeddedObj, &centroid)) {
+ return Status(ErrorCodes::BadValue, mongoutils::str::stream() <<
+ "invalid point in geo near query $geometry argument: " <<
+ embeddedObj);
+ }
+ uassert(16681, "$near requires geojson point, given " + embeddedObj.toString(),
+ (SPHERE == centroid.crs));
+ hasGeometry = true;
+ }
+ } else if (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 (equals(e.fieldName(), "$maxDistance")) {
+ uassert(16899, "$maxDistance must be a number", e.isNumber());
+ maxDistance = e.Number();
+ uassert(16900, "$maxDistance must be non-negative", maxDistance >= 0.0);
+ }
+ }
+
+ if (!hasGeometry) {
+ return Status(ErrorCodes::BadValue, "$geometry is required for geo near query");
+ }
+
+ return Status::OK();
+ }
+
+
+ Status NearQuery::parseFrom(const BSONObj &obj) {
+
+ Status status = Status::OK();
+
+ if (!parseLegacyQuery(obj)) {
+ // Clear out any half-baked data.
+ minDistance = 0;
+ isNearSphere = false;
+ maxDistance = std::numeric_limits<double>::max();
+ centroid = PointWithCRS();
+ // ...and try parsing new format.
+ status = parseNewQuery(obj);
+ }
+
+ if (!status.isOK())
+ return status;
+
+ // Fixup the near query for anonoyances caused by $nearSphere
+ if (isNearSphere) {
+
+ // The user-provided point can be flat for a spherical query - needs to be projectable
+ uassert(17444,
+ "Legacy point is out of bounds for spherical query",
+ ShapeProjection::supportsProject(centroid, SPHERE));
+
+ unitsAreRadians = SPHERE != centroid.crs;
+ // GeoJSON points imply wrapping queries
+ isWrappingQuery = SPHERE == centroid.crs;
+
+ // Project the point to a spherical CRS now that we've got the settings we need
+ // We need to manually project here since we aren't using GeometryContainer
+ ShapeProjection::projectInto(&centroid, SPHERE);
+ }
+ else {
+ unitsAreRadians = false;
+ isWrappingQuery = SPHERE == centroid.crs;
+ }
+
+ return status;
+ }
+
+ bool GeoQuery::parseLegacyQuery(const BSONObj &obj) {
+ // The only legacy syntax is {$within: {.....}}
+ BSONObjIterator outerIt(obj);
+ if (!outerIt.more()) { return false; }
+ BSONElement withinElt = outerIt.next();
+ if (outerIt.more()) { return false; }
+ if (!withinElt.isABSONObj()) { return false; }
+ if (!equals(withinElt.fieldName(), "$within") && !equals(withinElt.fieldName(), "$geoWithin")) {
+ return false;
+ }
+ BSONObj withinObj = withinElt.embeddedObject();
+
+ bool hasGeometry = false;
+
+ BSONObjIterator withinIt(withinObj);
+ while (withinIt.more()) {
+ BSONElement elt = withinIt.next();
+ if (equals(elt.fieldName(), "$uniqueDocs")) {
+ warning() << "deprecated $uniqueDocs option: " << obj.toString() << endl;
+ // return false;
+ }
+ else if (elt.isABSONObj()) {
+ hasGeometry = geoContainer.parseFrom(elt.wrap());
+ }
+ else {
+ warning() << "bad geo query: " << obj.toString() << endl;
+ return false;
+ }
+ }
+
+ predicate = GeoQuery::WITHIN;
+
+ return hasGeometry;
+ }
+
+ bool GeoQuery::parseNewQuery(const BSONObj &obj) {
+ // pointA = { "type" : "Point", "coordinates": [ 40, 5 ] }
+ // t.find({ "geo" : { "$intersect" : { "$geometry" : pointA} } })
+ // t.find({ "geo" : { "$within" : { "$geometry" : polygon } } })
+ // where field.name is "geo"
+ BSONElement e = obj.firstElement();
+ if (!e.isABSONObj()) { return false; }
+
+ BSONObj::MatchType matchType = static_cast<BSONObj::MatchType>(e.getGtLtOp());
+ if (BSONObj::opGEO_INTERSECTS == matchType) {
+ predicate = GeoQuery::INTERSECT;
+ } else if (BSONObj::opWITHIN == matchType) {
+ predicate = GeoQuery::WITHIN;
+ } else {
+ return false;
+ }
+
+ bool hasGeometry = false;
+ BSONObjIterator argIt(e.embeddedObject());
+ while (argIt.more()) {
+ BSONElement e = argIt.next();
+ if (mongoutils::str::equals(e.fieldName(), "$geometry")) {
+ if (e.isABSONObj()) {
+ BSONObj embeddedObj = e.embeddedObject();
+ if (geoContainer.parseFrom(embeddedObj)) {
+ hasGeometry = true;
+ }
+ }
+ }
+ }
+
+ // Don't want to give the error below if we could not pull any geometry out.
+ if (!hasGeometry) { return false; }
+
+ if (GeoQuery::WITHIN == predicate) {
+ // Why do we only deal with $within {polygon}?
+ // 1. Finding things within a point is silly and only valid
+ // for points and degenerate lines/polys.
+ //
+ // 2. Finding points within a line is easy but that's called intersect.
+ // Finding lines within a line is kind of tricky given what S2 gives us.
+ // Doing line-within-line is a valid yet unsupported feature,
+ // though I wonder if we want to preserve orientation for lines or
+ // allow (a,b),(c,d) to be within (c,d),(a,b). Anyway, punt on
+ // this for now.
+ uassert(16672, "$within not supported with provided geometry: " + obj.toString(),
+ geoContainer.supportsContains());
+ }
+
+ return hasGeometry;
+ }
+
+ bool GeoQuery::parseFrom(const BSONObj &obj) {
+ if (!(parseLegacyQuery(obj) || parseNewQuery(obj)))
+ return false;
+
+ // $geoIntersect queries are hardcoded to *always* be in SPHERE CRS
+ // TODO: This is probably bad semantics, should not do this
+ if (GeoQuery::INTERSECT == predicate) {
+ if (!geoContainer.supportsProject(SPHERE))
+ return false;
+ geoContainer.projectInto(SPHERE);
+ }
+
+ return true;
+ }
+
+} // namespace mongo
diff --git a/src/mongo/db/geo/geo_query.h b/src/mongo/db/geo/geo_query.h
new file mode 100644
index 00000000000..5390d7c5a23
--- /dev/null
+++ b/src/mongo/db/geo/geo_query.h
@@ -0,0 +1,118 @@
+/**
+* Copyright (C) 2013 10gen Inc.
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License, version 3,
+* as published by the Free Software Foundation.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see <http://www.gnu.org/licenses/>.
+*
+* As a special exception, the copyright holders give permission to link the
+* code of portions of this program with the OpenSSL library under certain
+* conditions as described in each individual source file and distribute
+* linked combinations including the program with the OpenSSL library. You
+* must comply with the GNU Affero General Public License in all respects for
+* all of the code used other than as permitted herein. If you modify file(s)
+* with this exception, you may extend this exception to your version of the
+* file(s), but you are not obligated to do so. If you do not wish to do so,
+* delete this exception statement from your version. If you delete this
+* exception statement from all source files in the program, then also delete
+* it in the license file.
+*/
+
+#pragma once
+
+#include <string>
+
+#include "mongo/db/geo/shapes.h"
+#include "mongo/db/geo/geometry_container.h"
+
+namespace mongo {
+
+ // TODO: Make a struct, turn parse stuff into something like
+ // static Status parseNearQuery(const BSONObj& obj, NearQuery** out);
+ class NearQuery {
+ public:
+ NearQuery()
+ : minDistance(0),
+ maxDistance(std::numeric_limits<double>::max()),
+ isNearSphere(false),
+ unitsAreRadians(false),
+ isWrappingQuery(false) { }
+
+ NearQuery(const std::string& f)
+ : field(f),
+ minDistance(0),
+ maxDistance(std::numeric_limits<double>::max()),
+ isNearSphere(false),
+ unitsAreRadians(false),
+ isWrappingQuery(false) { }
+
+ Status parseFrom(const BSONObj &obj);
+
+ // The name of the field that contains the geometry.
+ std::string field;
+
+ // The starting point of the near search.
+ PointWithCRS centroid;
+
+ // Min and max distance from centroid that we're willing to search.
+ // Distance is in units of the geometry's CRS, except SPHERE and isNearSphere => radians
+ double minDistance;
+ double maxDistance;
+
+ // Is this a $nearSphere query
+ bool isNearSphere;
+ // $nearSphere with a legacy point implies units are radians
+ bool unitsAreRadians;
+ // $near with a non-legacy point implies a wrapping query, otherwise the query doesn't wrap
+ bool isWrappingQuery;
+
+ std::string toString() const {
+ std::stringstream ss;
+ ss << " field=" << field;
+ ss << " maxdist=" << maxDistance;
+ ss << " isNearSphere=" << isNearSphere;
+ return ss.str();
+ }
+
+ private:
+ bool parseLegacyQuery(const BSONObj &obj);
+ Status parseNewQuery(const BSONObj &obj);
+ };
+
+ // This represents either a $within or a $geoIntersects.
+ class GeoQuery {
+ public:
+ GeoQuery() : field(""), predicate(INVALID) {}
+ GeoQuery(const std::string& f) : field(f), predicate(INVALID) {}
+
+ enum Predicate {
+ WITHIN,
+ INTERSECT,
+ INVALID
+ };
+
+ bool parseFrom(const BSONObj &obj);
+
+ std::string getField() const { return field; }
+ Predicate getPred() const { return predicate; }
+ const GeometryContainer& getGeometry() const { return geoContainer; }
+
+ private:
+ // Try to parse the provided object into the right place.
+ bool parseLegacyQuery(const BSONObj &obj);
+ bool parseNewQuery(const BSONObj &obj);
+
+ // Name of the field in the query.
+ std::string field;
+ GeometryContainer geoContainer;
+ Predicate predicate;
+ };
+} // namespace mongo
diff --git a/src/mongo/db/geo/geoquery.cpp b/src/mongo/db/geo/geometry_container.cpp
index c393951964b..568351b75d1 100644
--- a/src/mongo/db/geo/geoquery.cpp
+++ b/src/mongo/db/geo/geometry_container.cpp
@@ -26,229 +26,16 @@
* it in the license file.
*/
-#include "mongo/db/geo/geoquery.h"
+#include "mongo/db/geo/geometry_container.h"
#include "mongo/db/geo/geoconstants.h"
#include "mongo/db/geo/s2common.h"
+#include "mongo/util/mongoutils/str.h"
namespace mongo {
using mongoutils::str::equals;
- bool NearQuery::parseLegacyQuery(const BSONObj &obj) {
- bool hasGeometry = false;
-
- // 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 : [0, 0, 1] } });
- // t.find({ loc : { $near: { someGeoJSONPoint}})
- // t.find({ loc : { $geoNear: { someGeoJSONPoint}})
- BSONObjIterator it(obj);
- while (it.more()) {
- BSONElement e = it.next();
- if (equals(e.fieldName(), "$near") || equals(e.fieldName(), "$geoNear")
- || equals(e.fieldName(), "$nearSphere")) {
- if (!e.isABSONObj()) { return false; }
- BSONObj embeddedObj = e.embeddedObject();
-
- if ((GeoParser::isPoint(embeddedObj) && GeoParser::parsePoint(embeddedObj, &centroid))
- || GeoParser::parsePointWithMaxDistance(embeddedObj, &centroid, &maxDistance)) {
- uassert(18522, "max distance must be non-negative", maxDistance >= 0.0);
- hasGeometry = true;
- isNearSphere = equals(e.fieldName(), "$nearSphere");
- }
- } else if (equals(e.fieldName(), "$minDistance")) {
- uassert(16893, "$minDistance must be a number", e.isNumber());
- minDistance = e.Number();
- uassert(16894, "$minDistance must be non-negative", minDistance >= 0.0);
- } else if (equals(e.fieldName(), "$maxDistance")) {
- uassert(16895, "$maxDistance must be a number", e.isNumber());
- maxDistance = e.Number();
- uassert(16896, "$maxDistance must be non-negative", maxDistance >= 0.0);
- } else if (equals(e.fieldName(), "$uniqueDocs")) {
- warning() << "ignoring deprecated option $uniqueDocs";
- }
- }
-
- // The user-provided point can be flat. We need to make sure that it's in bounds.
- if (isNearSphere) {
- uassert(17444,
- "Legacy point is out of bounds for spherical query",
- centroid.flatUpgradedToSphere || (SPHERE == centroid.crs));
- }
-
- return hasGeometry;
- }
-
- Status NearQuery::parseNewQuery(const BSONObj &obj) {
- bool hasGeometry = false;
-
- BSONObjIterator objIt(obj);
- if (!objIt.more()) {
- return Status(ErrorCodes::BadValue, "empty geo near query object");
- }
- BSONElement e = objIt.next();
- // Just one arg. to $geoNear.
- if (objIt.more()) {
- return Status(ErrorCodes::BadValue, mongoutils::str::stream() <<
- "geo near accepts just one argument when querying for a GeoJSON " <<
- "point. Extra field found: " << objIt.next());
- }
-
- // Parse "new" near:
- // t.find({"geo" : {"$near" : {"$geometry": pointA, $minDistance: 1, $maxDistance: 3}}})
- // t.find({"geo" : {"$geoNear" : {"$geometry": pointA, $minDistance: 1, $maxDistance: 3}}})
- if (!e.isABSONObj()) {
- return Status(ErrorCodes::BadValue, "geo near query argument is not an object");
- }
- BSONObj::MatchType matchType = static_cast<BSONObj::MatchType>(e.getGtLtOp());
- if (BSONObj::opNEAR != matchType) {
- return Status(ErrorCodes::BadValue, mongoutils::str::stream() <<
- "invalid geo near query operator: " << e.fieldName());
- }
-
- // Iterate over the argument.
- BSONObjIterator it(e.embeddedObject());
- while (it.more()) {
- BSONElement e = it.next();
- if (equals(e.fieldName(), "$geometry")) {
- if (e.isABSONObj()) {
- BSONObj embeddedObj = e.embeddedObject();
- uassert(16885, "$near requires a point, given " + embeddedObj.toString(),
- GeoParser::isPoint(embeddedObj));
- if (!GeoParser::parsePoint(embeddedObj, &centroid)) {
- return Status(ErrorCodes::BadValue, mongoutils::str::stream() <<
- "invalid point in geo near query $geometry argument: " <<
- embeddedObj);
- }
- uassert(16681, "$near requires geojson point, given " + embeddedObj.toString(),
- (SPHERE == centroid.crs));
- hasGeometry = true;
- }
- } else if (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 (equals(e.fieldName(), "$maxDistance")) {
- uassert(16899, "$maxDistance must be a number", e.isNumber());
- maxDistance = e.Number();
- uassert(16900, "$maxDistance must be non-negative", maxDistance >= 0.0);
- }
- }
-
- if (!hasGeometry) {
- return Status(ErrorCodes::BadValue, "$geometry is required for geo near query");
- }
-
- return Status::OK();
- }
-
-
- Status NearQuery::parseFrom(const BSONObj &obj) {
- if (parseLegacyQuery(obj)) {
- return Status::OK();
- }
- // Clear out any half-baked data.
- minDistance = 0;
- isNearSphere = false;
- maxDistance = std::numeric_limits<double>::max();
- centroid = PointWithCRS();
- // And try parsing new format.
- return parseNewQuery(obj);
- }
-
- bool GeoQuery::parseLegacyQuery(const BSONObj &obj) {
- // The only legacy syntax is {$within: {.....}}
- BSONObjIterator outerIt(obj);
- if (!outerIt.more()) { return false; }
- BSONElement withinElt = outerIt.next();
- if (outerIt.more()) { return false; }
- if (!withinElt.isABSONObj()) { return false; }
- if (!equals(withinElt.fieldName(), "$within") && !equals(withinElt.fieldName(), "$geoWithin")) {
- return false;
- }
- BSONObj withinObj = withinElt.embeddedObject();
-
- bool hasGeometry = false;
-
- BSONObjIterator withinIt(withinObj);
- while (withinIt.more()) {
- BSONElement elt = withinIt.next();
- if (equals(elt.fieldName(), "$uniqueDocs")) {
- warning() << "deprecated $uniqueDocs option: " << obj.toString() << endl;
- // return false;
- }
- else if (elt.isABSONObj()) {
- hasGeometry = geoContainer.parseFrom(elt.wrap());
- }
- else {
- warning() << "bad geo query: " << obj.toString() << endl;
- return false;
- }
- }
-
- predicate = GeoQuery::WITHIN;
-
- return hasGeometry;
- }
-
- bool GeoQuery::parseNewQuery(const BSONObj &obj) {
- // pointA = { "type" : "Point", "coordinates": [ 40, 5 ] }
- // t.find({ "geo" : { "$intersect" : { "$geometry" : pointA} } })
- // t.find({ "geo" : { "$within" : { "$geometry" : polygon } } })
- // where field.name is "geo"
- BSONElement e = obj.firstElement();
- if (!e.isABSONObj()) { return false; }
-
- BSONObj::MatchType matchType = static_cast<BSONObj::MatchType>(e.getGtLtOp());
- if (BSONObj::opGEO_INTERSECTS == matchType) {
- predicate = GeoQuery::INTERSECT;
- } else if (BSONObj::opWITHIN == matchType) {
- predicate = GeoQuery::WITHIN;
- } else {
- return false;
- }
-
- bool hasGeometry = false;
- BSONObjIterator argIt(e.embeddedObject());
- while (argIt.more()) {
- BSONElement e = argIt.next();
- if (mongoutils::str::equals(e.fieldName(), "$geometry")) {
- if (e.isABSONObj()) {
- BSONObj embeddedObj = e.embeddedObject();
- if (geoContainer.parseFrom(embeddedObj)) {
- hasGeometry = true;
- }
- }
- }
- }
-
- // Don't want to give the error below if we could not pull any geometry out.
- if (!hasGeometry) { return false; }
-
- if (GeoQuery::WITHIN == predicate) {
- // Why do we only deal with $within {polygon}?
- // 1. Finding things within a point is silly and only valid
- // for points and degenerate lines/polys.
- //
- // 2. Finding points within a line is easy but that's called intersect.
- // Finding lines within a line is kind of tricky given what S2 gives us.
- // Doing line-within-line is a valid yet unsupported feature,
- // though I wonder if we want to preserve orientation for lines or
- // allow (a,b),(c,d) to be within (c,d),(a,b). Anyway, punt on
- // this for now.
- uassert(16672, "$within not supported with provided geometry: " + obj.toString(),
- geoContainer.supportsContains());
- }
-
- return hasGeometry;
- }
-
- bool GeoQuery::parseFrom(const BSONObj &obj) {
- return parseLegacyQuery(obj) || parseNewQuery(obj);
- }
-
GeometryContainer::GeometryContainer() {
}
@@ -266,7 +53,7 @@ namespace mongo {
}
bool GeometryContainer::hasS2Region() const {
- return (NULL != _point && (_point->crs == SPHERE || _point->flatUpgradedToSphere))
+ return (NULL != _point && _point->crs == SPHERE)
|| NULL != _line
|| (NULL != _polygon && _polygon->crs == SPHERE)
|| (NULL != _cap && _cap->crs == SPHERE)
@@ -277,14 +64,12 @@ namespace mongo {
}
const S2Region& GeometryContainer::getS2Region() const {
- if (NULL != _point) {
- // _point->crs might be FLAT but we "upgrade" it for free if it was in bounds.
- if (FLAT == _point->crs) {
- verify(_point->flatUpgradedToSphere);
- }
+ if (NULL != _point && SPHERE == _point->crs) {
return _point->cell;
} else if (NULL != _line) {
return _line->line;
+ } else if (NULL != _polygon && SPHERE == _polygon->crs) {
+ return _polygon->polygon;
} else if (NULL != _cap && SPHERE == _cap->crs) {
return _cap->cap;
} else if (NULL != _multiPoint) {
@@ -293,12 +78,9 @@ namespace mongo {
return *_s2Region;
} else if (NULL != _multiPolygon) {
return *_s2Region;
- } else if (NULL != _geometryCollection) {
- return *_s2Region;
} else {
- verify(NULL != _polygon);
- verify(SPHERE == _polygon->crs);
- return _polygon->polygon;
+ invariant(NULL != _geometryCollection);
+ return *_s2Region;
}
}
@@ -467,7 +249,13 @@ namespace mongo {
}
bool GeometryContainer::contains(const GeometryContainer& otherContainer) const {
- // First let's deal with the case where we are FLAT.
+
+ // First let's deal with the FLAT cases
+
+ if (_point && FLAT == _point->crs) {
+ return false;
+ }
+
if (NULL != _polygon && (FLAT == _polygon->crs)) {
if (NULL == otherContainer._point) { return false; }
return _polygon->oldPolygon.contains(otherContainer._point->oldPoint);
@@ -490,11 +278,6 @@ namespace mongo {
// Iterate over the other thing and see if we contain it all.
if (NULL != otherContainer._point) {
- // The point must be valid lng, lat if it was old-style.
- if (FLAT == otherContainer._point->crs
- && !otherContainer._point->flatUpgradedToSphere) {
- return false;
- }
return contains(otherContainer._point->cell, otherContainer._point->point);
}
@@ -706,11 +489,6 @@ namespace mongo {
bool GeometryContainer::intersects(const GeometryContainer& otherContainer) const {
if (NULL != otherContainer._point) {
- // The point must be valid lng, lat if it was old-style.
- if (FLAT == otherContainer._point->crs
- && !otherContainer._point->flatUpgradedToSphere) {
- return false;
- }
return intersects(otherContainer._point->cell);
} else if (NULL != otherContainer._line) {
return intersects(otherContainer._line->line);
@@ -778,10 +556,6 @@ namespace mongo {
// Does this (GeometryContainer) intersect the provided data?
bool GeometryContainer::intersects(const S2Cell &otherPoint) const {
if (NULL != _point) {
- // The point must be valid lng, lat if it was old-style.
- if (FLAT == _point->crs && !_point->flatUpgradedToSphere) {
- return false;
- }
return _point->cell.MayIntersect(otherPoint);
} else if (NULL != _line) {
return _line->line.MayIntersect(otherPoint);
@@ -856,10 +630,6 @@ namespace mongo {
bool GeometryContainer::intersects(const S2Polyline& otherLine) const {
if (NULL != _point) {
- // The point must be valid lng, lat if it was old-style.
- if (FLAT == _point->crs && !_point->flatUpgradedToSphere) {
- return false;
- }
return otherLine.MayIntersect(_point->cell);
} else if (NULL != _line) {
return otherLine.Intersects(&_line->line);
@@ -930,10 +700,6 @@ namespace mongo {
// Does 'this' intersect with the provided polygon?
bool GeometryContainer::intersects(const S2Polygon& otherPolygon) const {
if (NULL != _point) {
- // The point must be valid lng, lat if it was old-style.
- if (FLAT == _point->crs && !_point->flatUpgradedToSphere) {
- return false;
- }
return otherPolygon.MayIntersect(_point->cell);
} else if (NULL != _line) {
return polygonLineIntersection(_line->line, otherPolygon);
@@ -1131,9 +897,7 @@ namespace mongo {
// TODO: Fix geometry collection reporting when/if we support more CRSes
if (NULL != _point) {
- if (_point->crs == otherCRS) return true;
- // SPHERE can always go FLAT, but FLAT may not always go back to SPHERE
- return _point->crs == SPHERE || _point->flatUpgradedToSphere;
+ return ShapeProjection::supportsProject(*_point, otherCRS);
}
else if (NULL != _line) { return _line->crs == otherCRS; }
else if (NULL != _box) { return _box->crs == otherCRS; }
@@ -1142,10 +906,9 @@ namespace mongo {
else if (NULL != _multiPoint) { return _multiPoint->crs == otherCRS; }
else if (NULL != _multiLine) { return _multiLine->crs == otherCRS; }
else if (NULL != _multiPolygon) { return _multiPolygon->crs == otherCRS; }
- else if (NULL != _geometryCollection) { return SPHERE == otherCRS; }
else {
- invariant(false);
- return false;
+ invariant(NULL != _geometryCollection);
+ return SPHERE == otherCRS;
}
}
@@ -1156,17 +919,7 @@ namespace mongo {
invariant(NULL != _point);
- if (FLAT == _point->crs) {
- invariant(_point->flatUpgradedToSphere);
- _point->crs = SPHERE;
- }
- else {
- invariant(FLAT == otherCRS);
- S2LatLng latLng(_point->point);
- _point->oldPoint = Point(latLng.lng().degrees(), latLng.lat().degrees());
- _point->flatUpgradedToSphere = true;
- _point->crs = FLAT;
- }
+ ShapeProjection::projectInto(_point.get(), otherCRS);
}
static double s2MinDistanceRad(const S2Point& s2Point, const MultiPointWithCRS& s2MultiPoint) {
@@ -1297,7 +1050,6 @@ namespace mongo {
}
else {
invariant(SPHERE == crs);
- invariant(FLAT != otherPoint.crs || otherPoint.flatUpgradedToSphere);
double minDistance = -1;
diff --git a/src/mongo/db/geo/geoquery.h b/src/mongo/db/geo/geometry_container.h
index f8085b2db52..7bd054778ff 100644
--- a/src/mongo/db/geo/geoquery.h
+++ b/src/mongo/db/geo/geometry_container.h
@@ -1,37 +1,37 @@
/**
-* Copyright (C) 2013 10gen Inc.
-*
-* This program is free software: you can redistribute it and/or modify
-* it under the terms of the GNU Affero General Public License, version 3,
-* as published by the Free Software Foundation.
-*
-* This program is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-* GNU Affero General Public License for more details.
-*
-* You should have received a copy of the GNU Affero General Public License
-* along with this program. If not, see <http://www.gnu.org/licenses/>.
-*
-* As a special exception, the copyright holders give permission to link the
-* code of portions of this program with the OpenSSL library under certain
-* conditions as described in each individual source file and distribute
-* linked combinations including the program with the OpenSSL library. You
-* must comply with the GNU Affero General Public License in all respects for
-* all of the code used other than as permitted herein. If you modify file(s)
-* with this exception, you may extend this exception to your version of the
-* file(s), but you are not obligated to do so. If you do not wish to do so,
-* delete this exception statement from your version. If you delete this
-* exception statement from all source files in the program, then also delete
-* it in the license file.
-*/
+ * Copyright (C) 2013 10gen Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
#pragma once
+#include <string>
+
#include "mongo/base/disallow_copying.h"
-#include "mongo/db/geo/geoparser.h"
#include "mongo/db/geo/shapes.h"
-#include "mongo/util/mongoutils/str.h"
#include "third_party/s2/s2regionunion.h"
namespace mongo {
@@ -151,88 +151,4 @@ namespace mongo {
scoped_ptr<R2Region> _r2Region;
};
- // TODO: Make a struct, turn parse stuff into something like
- // static Status parseNearQuery(const BSONObj& obj, NearQuery** out);
- class NearQuery {
- public:
- NearQuery()
- : minDistance(0),
- maxDistance(std::numeric_limits<double>::max()),
- isNearSphere(false) { }
-
- NearQuery(const std::string& f)
- : field(f),
- minDistance(0),
- maxDistance(std::numeric_limits<double>::max()),
- isNearSphere(false) { }
-
- Status parseFrom(const BSONObj &obj);
-
- CRS getQueryCRS() const {
- return isNearSphere ? SPHERE : centroid.crs;
- }
-
- bool unitsAreRadians() const {
- return isNearSphere && FLAT == centroid.crs;
- }
-
- bool isWrappingQuery() const {
- return SPHERE == centroid.crs && !isNearSphere;
- }
-
- // The name of the field that contains the geometry.
- std::string field;
-
- // The starting point of the near search.
- PointWithCRS centroid;
-
- // Min and max distance from centroid that we're willing to search.
- // Distance is in units of the geometry's CRS, except SPHERE and isNearSphere => radians
- double minDistance;
- double maxDistance;
-
- // It's either $near or $nearSphere.
- bool isNearSphere;
-
- std::string toString() const {
- std::stringstream ss;
- ss << " field=" << field;
- ss << " maxdist=" << maxDistance;
- ss << " isNearSphere=" << isNearSphere;
- return ss.str();
- }
-
- private:
- bool parseLegacyQuery(const BSONObj &obj);
- Status parseNewQuery(const BSONObj &obj);
- };
-
- // This represents either a $within or a $geoIntersects.
- class GeoQuery {
- public:
- GeoQuery() : field(""), predicate(INVALID) {}
- GeoQuery(const std::string& f) : field(f), predicate(INVALID) {}
-
- enum Predicate {
- WITHIN,
- INTERSECT,
- INVALID
- };
-
- bool parseFrom(const BSONObj &obj);
-
- std::string getField() const { return field; }
- Predicate getPred() const { return predicate; }
- const GeometryContainer& getGeometry() const { return geoContainer; }
-
- private:
- // Try to parse the provided object into the right place.
- bool parseLegacyQuery(const BSONObj &obj);
- bool parseNewQuery(const BSONObj &obj);
-
- // Name of the field in the query.
- std::string field;
- GeometryContainer geoContainer;
- Predicate predicate;
- };
-} // namespace mongo
+} // namespace mongo
diff --git a/src/mongo/db/geo/geoparser.cpp b/src/mongo/db/geo/geoparser.cpp
index a0e956d5315..6efecc3ec58 100644
--- a/src/mongo/db/geo/geoparser.cpp
+++ b/src/mongo/db/geo/geoparser.cpp
@@ -52,10 +52,6 @@ namespace mongo {
static const string GEOJSON_COORDINATES = "coordinates";
static const string GEOJSON_GEOMETRIES = "geometries";
- bool isValidLngLat(double lng, double lat) {
- return lat >= -90 && lat <= 90 && lng >= -180 && lng <= 180;
- }
-
static bool isGeoJSONPoint(const BSONObj& obj) {
BSONElement type = obj.getFieldDotted(GEOJSON_TYPE);
if (type.eoo() || (String != type.type())) { return false; }
@@ -72,6 +68,7 @@ namespace mongo {
const vector<BSONElement>& coordinates = coordElt.Array();
if (coordinates.size() != 2) { return false; }
if (!coordinates[0].isNumber() || !coordinates[1].isNumber()) { return false; }
+ // For now, we assume all GeoJSON must be within WGS84 - this may change
double lat = coordinates[1].Number();
double lng = coordinates[0].Number();
return isValidLngLat(lng, lat);
@@ -316,35 +313,30 @@ namespace mongo {
/** exported **/
bool GeoParser::isPoint(const BSONObj &obj) {
- return isGeoJSONPoint(obj) || isLegacyPoint(obj, false);
+ return isLegacyPoint(obj, false) || isGeoJSONPoint(obj);
}
bool GeoParser::isIndexablePoint(const BSONObj &obj) {
- return isGeoJSONPoint(obj) || isLegacyPoint(obj, true);
+ return isLegacyPoint(obj, true) || isGeoJSONPoint(obj);
}
bool GeoParser::parsePoint(const BSONObj &obj, PointWithCRS *out) {
- if (isGeoJSONPoint(obj)) {
- const vector<BSONElement>& coords = obj.getFieldDotted(GEOJSON_COORDINATES).Array();
- out->point = coordToPoint(coords[0].Number(), coords[1].Number());
- out->cell = S2Cell(out->point);
- out->oldPoint.x = coords[0].Number();
- out->oldPoint.y = coords[1].Number();
- out->crs = SPHERE;
- } else if (isLegacyPoint(obj, true)) {
+ if (isLegacyPoint(obj, true)) {
BSONObjIterator it(obj);
BSONElement x = it.next();
BSONElement y = it.next();
- if (isValidLngLat(x.Number(), y.Number())) {
- out->flatUpgradedToSphere = true;
- out->point = coordToPoint(x.Number(), y.Number());
- out->cell = S2Cell(out->point);
- }
out->oldPoint.x = x.Number();
out->oldPoint.y = y.Number();
out->crs = FLAT;
+ } else if (isGeoJSONPoint(obj)) {
+ const vector<BSONElement>& coords = obj.getFieldDotted(GEOJSON_COORDINATES).Array();
+ out->oldPoint.x = coords[0].Number();
+ out->oldPoint.y = coords[1].Number();
+ out->crs = FLAT;
+ if (!ShapeProjection::supportsProject(*out, SPHERE))
+ return false;
+ ShapeProjection::projectInto(out, SPHERE);
}
-
return true;
}
@@ -686,15 +678,10 @@ namespace mongo {
if (!dist.isNumber()) { return false; }
if (it.more()) { return false; }
- out->crs = FLAT;
out->oldPoint.x = lng.number();
out->oldPoint.y = lat.number();
+ out->crs = FLAT;
*maxOut = dist.number();
- if (isValidLngLat(lng.Number(), lat.Number())) {
- out->flatUpgradedToSphere = true;
- out->point = coordToPoint(lng.Number(), lat.Number());
- out->cell = S2Cell(out->point);
- }
return true;
}
diff --git a/src/mongo/db/geo/r2_region_coverer_test.cpp b/src/mongo/db/geo/r2_region_coverer_test.cpp
index b58b7246df6..e3d1c980c71 100644
--- a/src/mongo/db/geo/r2_region_coverer_test.cpp
+++ b/src/mongo/db/geo/r2_region_coverer_test.cpp
@@ -31,7 +31,7 @@
#include "mongo/unittest/unittest.h"
#include "mongo/platform/random.h"
#include "mongo/bson/bsonmisc.h"
-#include "mongo/db/geo/geoquery.h" // TODO: Move GeometryContainer out of geoquery.h
+#include "mongo/db/geo/geometry_container.h"
namespace {
diff --git a/src/mongo/db/geo/s2common.cpp b/src/mongo/db/geo/s2common.cpp
index 2dd2082c96a..9be2465640a 100644
--- a/src/mongo/db/geo/s2common.cpp
+++ b/src/mongo/db/geo/s2common.cpp
@@ -30,7 +30,6 @@
#include "mongo/db/geo/geoconstants.h"
#include "mongo/db/geo/geoparser.h"
-#include "mongo/db/geo/geoquery.h"
#include "mongo/db/geo/s2.h"
#include "third_party/s2/s2cell.h"
#include "third_party/s2/s2regioncoverer.h"
diff --git a/src/mongo/db/geo/shapes.cpp b/src/mongo/db/geo/shapes.cpp
index 9c1e5b80745..14a4fa3de04 100644
--- a/src/mongo/db/geo/shapes.cpp
+++ b/src/mongo/db/geo/shapes.cpp
@@ -563,11 +563,8 @@ namespace mongo {
}
// Technically lat/long bounds, not really tied to earth radius.
- void checkEarthBounds(const Point &p) {
- uassert(14808, str::stream() << "point " << p.toString()
- << " must be in earth-like bounds of long "
- << ": [-180, 180], lat : [-90, 90] ",
- p.x >= -180 && p.x <= 180 && p.y >= -90 && p.y <= 90);
+ bool isValidLngLat(double lng, double lat) {
+ return abs(lng) <= 180 && abs(lat) <= 90;
}
double distance(const Point& p1, const Point &p2) {
@@ -750,4 +747,44 @@ namespace mongo {
return edgesIntersectsWithBox(polygon.points(), box);
}
+ bool ShapeProjection::supportsProject(const PointWithCRS& point, const CRS crs) {
+
+ // Can always trivially project or project from SPHERE->FLAT
+ if (point.crs == crs || point.crs == SPHERE)
+ return true;
+
+ invariant(point.crs == FLAT);
+ // If crs is FLAT, we might be able to upgrade the point to SPHERE if it's a valid SPHERE
+ // point (lng/lat in bounds). In this case, we can use FLAT data with SPHERE predicates.
+ return isValidLngLat(point.oldPoint.x, point.oldPoint.y);
+ }
+
+ void ShapeProjection::projectInto(PointWithCRS* point, CRS crs) {
+ dassert(supportsProject(*point, crs));
+
+ if (point->crs == crs)
+ return;
+
+ if (FLAT == point->crs) {
+ invariant(SPHERE == crs);
+
+ // Note that it's (lat, lng) for S2 but (lng, lat) for MongoDB.
+ S2LatLng latLng =
+ S2LatLng::FromDegrees(point->oldPoint.y, point->oldPoint.x).Normalized();
+ dassert(latLng.is_valid());
+ point->point = latLng.ToPoint();
+ point->cell = S2Cell(point->point);
+ point->crs = SPHERE;
+ }
+ else {
+ invariant(SPHERE == point->crs);
+ invariant(FLAT == crs);
+
+ // Just remove the additional spherical information
+ point->point = S2Point();
+ point->cell = S2Cell();
+ point->crs = FLAT;
+ }
+ }
+
} // namespace mongo
diff --git a/src/mongo/db/geo/shapes.h b/src/mongo/db/geo/shapes.h
index fd79c3680e4..4091ce9357c 100644
--- a/src/mongo/db/geo/shapes.h
+++ b/src/mongo/db/geo/shapes.h
@@ -62,7 +62,7 @@ namespace mongo {
cos(deg2rad(std::max(-89.0, y - maxDistDegrees))));
}
- void checkEarthBounds(const Point &p);
+ bool isValidLngLat(double lng, double lat);
bool linesIntersect(const Point& pA, const Point& pB, const Point& pC, const Point& pD);
bool circleContainsBox(const Circle& circle, const Box& box);
bool circleInteriorContainsBox(const Circle& circle, const Box& box);
@@ -254,17 +254,19 @@ namespace mongo {
SPHERE
};
+ // TODO: Make S2 less integral to these types - additional S2 shapes should be an optimization
+ // when our CRS is not projected, i.e. SPHERE for now.
+ // Generic shapes (Point, Line, Polygon) should hold the raw coordinate data - right now oldXXX
+ // is a misnomer - this is the *original* data and the S2 transformation just an optimization.
+
struct PointWithCRS {
- PointWithCRS() : crs(UNSET), flatUpgradedToSphere(false) {}
+ PointWithCRS() : crs(UNSET) {}
S2Point point;
S2Cell cell;
Point oldPoint;
CRS crs;
- // If crs is FLAT, we might be able to upgrade the point to SPHERE if it's a valid SPHERE
- // point (lng/lat in bounds). In this case, we can use FLAT data with SPHERE predicates.
- bool flatUpgradedToSphere;
};
struct LineWithCRS {
@@ -344,4 +346,13 @@ namespace mongo {
}
};
+ //
+ // Projection functions - we don't project types other than points for now
+ //
+
+ struct ShapeProjection {
+ static bool supportsProject(const PointWithCRS& point, const CRS crs);
+ static void projectInto(PointWithCRS* point, CRS crs);
+ };
+
} // namespace mongo
diff --git a/src/mongo/db/index/SConscript b/src/mongo/db/index/SConscript
index 6cf5084abb2..8d062d13878 100644
--- a/src/mongo/db/index/SConscript
+++ b/src/mongo/db/index/SConscript
@@ -11,9 +11,8 @@ env.Library(
LIBDEPS=[
'$BUILD_DIR/mongo/bson',
'$BUILD_DIR/mongo/db/fts/base',
- '$BUILD_DIR/mongo/geometry',
- '$BUILD_DIR/mongo/geoparser',
- '$BUILD_DIR/mongo/geoquery',
+ '$BUILD_DIR/mongo/db/geo/geometry',
+ '$BUILD_DIR/mongo/db/geo/geoparser',
'$BUILD_DIR/mongo/index_names',
'$BUILD_DIR/third_party/s2/s2',
],
diff --git a/src/mongo/db/index/expression_index.h b/src/mongo/db/index/expression_index.h
deleted file mode 100644
index f3877cb8d0e..00000000000
--- a/src/mongo/db/index/expression_index.h
+++ /dev/null
@@ -1,217 +0,0 @@
-/**
- * Copyright (C) 2013 10gen Inc.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- * As a special exception, the copyright holders give permission to link the
- * code of portions of this program with the OpenSSL library under certain
- * conditions as described in each individual source file and distribute
- * linked combinations including the program with the OpenSSL library. You
- * must comply with the GNU Affero General Public License in all respects for
- * all of the code used other than as permitted herein. If you modify file(s)
- * with this exception, you may extend this exception to your version of the
- * file(s), but you are not obligated to do so. If you do not wish to do so,
- * delete this exception statement from your version. If you delete this
- * exception statement from all source files in the program, then also delete
- * it in the license file.
- */
-
-#pragma once
-
-#include "third_party/s2/s2regioncoverer.h"
-
-#include "mongo/db/jsobj.h"
-#include "mongo/db/geo/hash.h"
-#include "mongo/db/geo/geoquery.h"
-#include "mongo/db/geo/r2_region_coverer.h"
-#include "mongo/db/hasher.h"
-#include "mongo/db/query/index_bounds_builder.h"
-
-namespace mongo {
-
- /**
- * Functions that compute expression index mappings.
- *
- * TODO: I think we could structure this more generally with respect to planning.
- */
- class ExpressionMapping {
- public:
- static BSONObj hash(const BSONElement& value) {
- BSONObjBuilder bob;
- bob.append("", BSONElementHasher::hash64(value, BSONElementHasher::DEFAULT_HASH_SEED));
- return bob.obj();
- }
-
- // For debugging only
- static std::string toCoveringString(const GeoHashConverter& hashConverter,
- const set<GeoHash>& covering) {
- string result = "[";
- for (set<GeoHash>::const_iterator it = covering.begin(); it != covering.end();
- ++it) {
-
- if (it != covering.begin()) result += ", ";
-
- const GeoHash& geoHash = *it;
-
- result += hashConverter.unhashToBox(geoHash).toString();
- result += " (" + geoHash.toStringHex1() + ")";
- }
-
- return result + "]";
- }
-
- static void cover2d(const R2Region& region,
- const BSONObj& indexInfoObj,
- OrderedIntervalList* oil) {
-
- GeoHashConverter::Parameters hashParams;
- Status paramStatus = GeoHashConverter::parseParameters(indexInfoObj, &hashParams);
- verify(paramStatus.isOK()); // We validated the parameters when creating the index
-
- GeoHashConverter hashConverter(hashParams);
- R2RegionCoverer coverer(&hashConverter);
- coverer.setMaxLevel(hashConverter.getBits());
-
- // TODO: Maybe slightly optimize by returning results in order
- vector<GeoHash> unorderedCovering;
- coverer.getCovering(region, &unorderedCovering);
- set<GeoHash> covering(unorderedCovering.begin(), unorderedCovering.end());
-
- for (set<GeoHash>::const_iterator it = covering.begin(); it != covering.end();
- ++it) {
-
- const GeoHash& geoHash = *it;
- BSONObjBuilder builder;
- geoHash.appendHashMin(&builder, "");
- geoHash.appendHashMax(&builder, "");
-
- oil->intervals.push_back(IndexBoundsBuilder::makeRangeInterval(builder.obj(),
- true,
- true));
- }
- }
-
- // TODO: what should we really pass in for indexInfoObj?
- static void cover2dsphere(const S2Region& region,
- const BSONObj& indexInfoObj,
- OrderedIntervalList* oilOut) {
-
- int coarsestIndexedLevel;
- BSONElement ce = indexInfoObj["coarsestIndexedLevel"];
- if (ce.isNumber()) {
- coarsestIndexedLevel = ce.numberInt();
- }
- else {
- coarsestIndexedLevel =
- S2::kAvgEdge.GetClosestLevel(100 * 1000.0 / kRadiusOfEarthInMeters);
- }
-
- // The min level of our covering is the level whose cells are the closest match to the
- // *area* of the region (or the max indexed level, whichever is smaller) The max level
- // is 4 sizes larger.
- double edgeLen = sqrt(region.GetRectBound().Area());
- S2RegionCoverer coverer;
- coverer.set_min_level(min(coarsestIndexedLevel,
- 2 + S2::kAvgEdge.GetClosestLevel(edgeLen)));
- coverer.set_max_level(4 + coverer.min_level());
-
- std::vector<S2CellId> cover;
- coverer.GetCovering(region, &cover);
-
- // Look at the cells we cover and all cells that are within our covering and finer.
- // Anything with our cover as a strict prefix is contained within the cover and should
- // be intersection tested.
- bool considerCoarser = false;
- std::set<std::string> intervalSet;
- for (size_t i = 0; i < cover.size(); ++i) {
- intervalSet.insert(cover[i].toString());
- // If any of our covers could be covered by something in the index, we have
- // to look at things coarser.
- if (cover[i].level() > coarsestIndexedLevel) {
- considerCoarser = true;
- }
- }
-
- std::set<std::string> exactSet;
- if (considerCoarser) {
- // Look at the cells that cover us. We want to look at every cell that contains the
- // covering we would index on if we were to insert the query geometry. We generate
- // the would-index-with-this-covering and find all the cells strictly containing the
- // cells in that set, until we hit the coarsest indexed cell. We use equality, not
- // a prefix match. Why not prefix? Because we've already looked at everything
- // finer or as fine as our initial covering.
- //
- // Say we have a fine point with cell id 212121, we go up one, get 21212, we don't
- // want to look at cells 21212[not-1] because we know they're not going to intersect
- // with 212121, but entries inserted with cell value 21212 (no trailing digits) may.
- // And we've already looked at points with the cell id 211111 from the regex search
- // created above, so we only want things where the value of the last digit is not
- // stored (and therefore could be 1).
- for (size_t i = 0; i < cover.size(); ++i) {
- for (S2CellId id = cover[i].parent(); id.level() >= coarsestIndexedLevel;
- id = id.parent()) {
- exactSet.insert(id.toString());
- }
- }
- }
-
- // We turned the cell IDs into strings which define point intervals or prefixes of
- // strings we want to look for.
- std::set<std::string>::iterator exactIt = exactSet.begin();
- std::set<std::string>::iterator intervalIt = intervalSet.begin();
- while (exactSet.end() != exactIt && intervalSet.end() != intervalIt) {
- const std::string& exact = *exactIt;
- const std::string& ival = *intervalIt;
- if (exact < ival) {
- // add exact
- oilOut->intervals.push_back(IndexBoundsBuilder::makePointInterval(exact));
- exactIt++;
- }
- else {
- std::string end = ival;
- end[end.size() - 1]++;
- oilOut->intervals.push_back(
- IndexBoundsBuilder::makeRangeInterval(ival, end, true, false));
- intervalIt++;
- }
- }
-
- if (exactSet.end() != exactIt) {
- verify(intervalSet.end() == intervalIt);
- do {
- oilOut->intervals.push_back(IndexBoundsBuilder::makePointInterval(*exactIt));
- exactIt++;
- } while (exactSet.end() != exactIt);
- }
- else if (intervalSet.end() != intervalIt) {
- verify(exactSet.end() == exactIt);
- do {
- const std::string& ival = *intervalIt;
- std::string end = ival;
- end[end.size() - 1]++;
- oilOut->intervals.push_back(
- IndexBoundsBuilder::makeRangeInterval(ival, end, true, false));
- intervalIt++;
- } while (intervalSet.end() != intervalIt);
- }
-
- // Make sure that our intervals don't overlap each other and are ordered correctly.
- // This perhaps should only be done in debug mode.
- if (!oilOut->isValidFor(1)) {
- cout << "check your assumptions! OIL = " << oilOut->toString() << std::endl;
- verify(0);
- }
- }
- };
-
-} // namespace mongo
diff --git a/src/mongo/db/index/expression_keys_private.cpp b/src/mongo/db/index/expression_keys_private.cpp
index b42d3949a34..9ca7eade530 100644
--- a/src/mongo/db/index/expression_keys_private.cpp
+++ b/src/mongo/db/index/expression_keys_private.cpp
@@ -32,8 +32,8 @@
#include "mongo/db/fts/fts_index_format.h"
#include "mongo/db/geo/geoconstants.h"
+#include "mongo/db/geo/geometry_container.h"
#include "mongo/db/geo/geoparser.h"
-#include "mongo/db/geo/geoquery.h"
#include "mongo/db/geo/s2common.h"
#include "mongo/db/geo/s2.h"
#include "mongo/db/index_names.h"
@@ -97,10 +97,14 @@ namespace {
return false;
}
- if (!geoContainer.hasS2Region()) { return false; }
+ // Project the geometry into spherical space
+ if (!geoContainer.supportsProject(SPHERE))
+ return false;
+ geoContainer.projectInto(SPHERE);
- S2KeysFromRegion(&coverer, geoContainer.getS2Region(), out);
+ invariant(geoContainer.hasS2Region());
+ S2KeysFromRegion(&coverer, geoContainer.getS2Region(), out);
return true;
}
diff --git a/src/mongo/db/matcher/expression_geo.cpp b/src/mongo/db/matcher/expression_geo.cpp
index 23d28940d01..3c55f8a9133 100644
--- a/src/mongo/db/matcher/expression_geo.cpp
+++ b/src/mongo/db/matcher/expression_geo.cpp
@@ -52,6 +52,12 @@ namespace mongo {
if ( !geometry.parseFrom( e.Obj() ) )
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 (GeoQuery::WITHIN == _query->getPred()) {
return _query->getGeometry().contains(geometry);
}
diff --git a/src/mongo/db/matcher/expression_geo.h b/src/mongo/db/matcher/expression_geo.h
index a784dbdabda..0be28de408d 100644
--- a/src/mongo/db/matcher/expression_geo.h
+++ b/src/mongo/db/matcher/expression_geo.h
@@ -31,7 +31,7 @@
#pragma once
-#include "mongo/db/geo/geoquery.h"
+#include "mongo/db/geo/geo_query.h"
#include "mongo/db/matcher/expression.h"
#include "mongo/db/matcher/expression_leaf.h"
diff --git a/src/mongo/db/query/SConscript b/src/mongo/db/query/SConscript
index ca294bc4584..769d6f59aa1 100644
--- a/src/mongo/db/query/SConscript
+++ b/src/mongo/db/query/SConscript
@@ -65,6 +65,8 @@ env.CppUnitTest(
env.Library(
target="index_bounds",
source=[
+ "expression_index.cpp",
+ "expression_index_knobs.cpp",
"index_bounds.cpp",
"index_bounds_builder.cpp",
"interval.cpp",
@@ -75,6 +77,7 @@ env.Library(
"$BUILD_DIR/mongo/expressions_geo",
"$BUILD_DIR/mongo/index_names",
"$BUILD_DIR/mongo/mongohasher",
+ "$BUILD_DIR/mongo/server_parameters",
],
)
diff --git a/src/mongo/db/query/canonical_query_test.cpp b/src/mongo/db/query/canonical_query_test.cpp
index 22fb62b3258..73127fbd34f 100644
--- a/src/mongo/db/query/canonical_query_test.cpp
+++ b/src/mongo/db/query/canonical_query_test.cpp
@@ -625,7 +625,7 @@ namespace {
testGetPlanCacheKey("{a: {$near: [0,0], $maxDistance:0.3 }}", "{}", "{}",
"gnanrfl");
testGetPlanCacheKey("{a: {$nearSphere: [0,0], $maxDistance: 0.31 }}", "{}", "{}",
- "gnansfl");
+ "gnanssp");
testGetPlanCacheKey("{a: {$geoNear: {$geometry: {type: 'Point', coordinates: [0,0]},"
"$maxDistance:100}}}", "{}", "{}",
"gnanrsp");
diff --git a/src/mongo/db/query/expression_index.cpp b/src/mongo/db/query/expression_index.cpp
new file mode 100644
index 00000000000..ee49ee574b0
--- /dev/null
+++ b/src/mongo/db/query/expression_index.cpp
@@ -0,0 +1,209 @@
+/**
+ * Copyright (C) 2013 10gen Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#include "mongo/db/query/expression_index.h"
+
+#include "third_party/s2/s2regioncoverer.h"
+
+#include "mongo/db/geo/geoconstants.h"
+#include "mongo/db/geo/hash.h"
+#include "mongo/db/geo/r2_region_coverer.h"
+#include "mongo/db/hasher.h"
+
+namespace mongo {
+
+ BSONObj ExpressionMapping::hash(const BSONElement& value) {
+ BSONObjBuilder bob;
+ bob.append("", BSONElementHasher::hash64(value, BSONElementHasher::DEFAULT_HASH_SEED));
+ return bob.obj();
+ }
+
+ // For debugging only
+ static std::string toCoveringString(const GeoHashConverter& hashConverter,
+ const set<GeoHash>& covering) {
+ string result = "[";
+ for (set<GeoHash>::const_iterator it = covering.begin(); it != covering.end();
+ ++it) {
+
+ if (it != covering.begin()) result += ", ";
+
+ const GeoHash& geoHash = *it;
+
+ result += hashConverter.unhashToBox(geoHash).toString();
+ result += " (" + geoHash.toStringHex1() + ")";
+ }
+
+ return result + "]";
+ }
+
+ void ExpressionMapping::cover2d(const R2Region& region,
+ const BSONObj& indexInfoObj,
+ int maxCoveringCells,
+ OrderedIntervalList* oil) {
+
+ GeoHashConverter::Parameters hashParams;
+ Status paramStatus = GeoHashConverter::parseParameters(indexInfoObj, &hashParams);
+ verify(paramStatus.isOK()); // We validated the parameters when creating the index
+
+ GeoHashConverter hashConverter(hashParams);
+ R2RegionCoverer coverer(&hashConverter);
+ coverer.setMaxLevel(hashConverter.getBits());
+ coverer.setMaxCells(maxCoveringCells);
+
+ // TODO: Maybe slightly optimize by returning results in order
+ vector<GeoHash> unorderedCovering;
+ coverer.getCovering(region, &unorderedCovering);
+ set<GeoHash> covering(unorderedCovering.begin(), unorderedCovering.end());
+
+ for (set<GeoHash>::const_iterator it = covering.begin(); it != covering.end();
+ ++it) {
+
+ const GeoHash& geoHash = *it;
+ BSONObjBuilder builder;
+ geoHash.appendHashMin(&builder, "");
+ geoHash.appendHashMax(&builder, "");
+
+ oil->intervals.push_back(IndexBoundsBuilder::makeRangeInterval(builder.obj(),
+ true,
+ true));
+ }
+ }
+
+ // TODO: what should we really pass in for indexInfoObj?
+ void ExpressionMapping::cover2dsphere(const S2Region& region,
+ const BSONObj& indexInfoObj,
+ OrderedIntervalList* oilOut) {
+
+ int coarsestIndexedLevel;
+ BSONElement ce = indexInfoObj["coarsestIndexedLevel"];
+ if (ce.isNumber()) {
+ coarsestIndexedLevel = ce.numberInt();
+ }
+ else {
+ coarsestIndexedLevel =
+ S2::kAvgEdge.GetClosestLevel(100 * 1000.0 / kRadiusOfEarthInMeters);
+ }
+
+ // The min level of our covering is the level whose cells are the closest match to the
+ // *area* of the region (or the max indexed level, whichever is smaller) The max level
+ // is 4 sizes larger.
+ double edgeLen = sqrt(region.GetRectBound().Area());
+ S2RegionCoverer coverer;
+ coverer.set_min_level(min(coarsestIndexedLevel,
+ 2 + S2::kAvgEdge.GetClosestLevel(edgeLen)));
+ coverer.set_max_level(4 + coverer.min_level());
+
+ std::vector<S2CellId> cover;
+ coverer.GetCovering(region, &cover);
+
+ // Look at the cells we cover and all cells that are within our covering and finer.
+ // Anything with our cover as a strict prefix is contained within the cover and should
+ // be intersection tested.
+ bool considerCoarser = false;
+ std::set<std::string> intervalSet;
+ for (size_t i = 0; i < cover.size(); ++i) {
+ intervalSet.insert(cover[i].toString());
+ // If any of our covers could be covered by something in the index, we have
+ // to look at things coarser.
+ if (cover[i].level() > coarsestIndexedLevel) {
+ considerCoarser = true;
+ }
+ }
+
+ std::set<std::string> exactSet;
+ if (considerCoarser) {
+ // Look at the cells that cover us. We want to look at every cell that contains the
+ // covering we would index on if we were to insert the query geometry. We generate
+ // the would-index-with-this-covering and find all the cells strictly containing the
+ // cells in that set, until we hit the coarsest indexed cell. We use equality, not
+ // a prefix match. Why not prefix? Because we've already looked at everything
+ // finer or as fine as our initial covering.
+ //
+ // Say we have a fine point with cell id 212121, we go up one, get 21212, we don't
+ // want to look at cells 21212[not-1] because we know they're not going to intersect
+ // with 212121, but entries inserted with cell value 21212 (no trailing digits) may.
+ // And we've already looked at points with the cell id 211111 from the regex search
+ // created above, so we only want things where the value of the last digit is not
+ // stored (and therefore could be 1).
+ for (size_t i = 0; i < cover.size(); ++i) {
+ for (S2CellId id = cover[i].parent(); id.level() >= coarsestIndexedLevel;
+ id = id.parent()) {
+ exactSet.insert(id.toString());
+ }
+ }
+ }
+
+ // We turned the cell IDs into strings which define point intervals or prefixes of
+ // strings we want to look for.
+ std::set<std::string>::iterator exactIt = exactSet.begin();
+ std::set<std::string>::iterator intervalIt = intervalSet.begin();
+ while (exactSet.end() != exactIt && intervalSet.end() != intervalIt) {
+ const std::string& exact = *exactIt;
+ const std::string& ival = *intervalIt;
+ if (exact < ival) {
+ // add exact
+ oilOut->intervals.push_back(IndexBoundsBuilder::makePointInterval(exact));
+ exactIt++;
+ }
+ else {
+ std::string end = ival;
+ end[end.size() - 1]++;
+ oilOut->intervals.push_back(
+ IndexBoundsBuilder::makeRangeInterval(ival, end, true, false));
+ intervalIt++;
+ }
+ }
+
+ if (exactSet.end() != exactIt) {
+ verify(intervalSet.end() == intervalIt);
+ do {
+ oilOut->intervals.push_back(IndexBoundsBuilder::makePointInterval(*exactIt));
+ exactIt++;
+ } while (exactSet.end() != exactIt);
+ }
+ else if (intervalSet.end() != intervalIt) {
+ verify(exactSet.end() == exactIt);
+ do {
+ const std::string& ival = *intervalIt;
+ std::string end = ival;
+ end[end.size() - 1]++;
+ oilOut->intervals.push_back(
+ IndexBoundsBuilder::makeRangeInterval(ival, end, true, false));
+ intervalIt++;
+ } while (intervalSet.end() != intervalIt);
+ }
+
+ // Make sure that our intervals don't overlap each other and are ordered correctly.
+ // This perhaps should only be done in debug mode.
+ if (!oilOut->isValidFor(1)) {
+ cout << "check your assumptions! OIL = " << oilOut->toString() << std::endl;
+ verify(0);
+ }
+ }
+
+} // namespace mongo
diff --git a/src/mongo/db/query/expression_index.h b/src/mongo/db/query/expression_index.h
new file mode 100644
index 00000000000..b50c2037e21
--- /dev/null
+++ b/src/mongo/db/query/expression_index.h
@@ -0,0 +1,60 @@
+/**
+ * Copyright (C) 2013 10gen Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#pragma once
+
+#include "third_party/s2/s2region.h"
+
+#include "mongo/db/jsobj.h"
+#include "mongo/db/geo/shapes.h"
+#include "mongo/db/query/index_bounds_builder.h" // For OrderedIntervalList
+
+namespace mongo {
+
+ /**
+ * Functions that compute expression index mappings.
+ *
+ * TODO: I think we could structure this more generally with respect to planning.
+ */
+ class ExpressionMapping {
+ public:
+
+ static BSONObj hash(const BSONElement& value);
+
+ static void cover2d(const R2Region& region,
+ const BSONObj& indexInfoObj,
+ int maxCoveringCells,
+ OrderedIntervalList* oil);
+
+ // TODO: what should we really pass in for indexInfoObj?
+ static void cover2dsphere(const S2Region& region,
+ const BSONObj& indexInfoObj,
+ OrderedIntervalList* oilOut);
+ };
+
+} // namespace mongo
diff --git a/src/mongo/db/query/expression_index_knobs.cpp b/src/mongo/db/query/expression_index_knobs.cpp
new file mode 100644
index 00000000000..4dd8dfe69c1
--- /dev/null
+++ b/src/mongo/db/query/expression_index_knobs.cpp
@@ -0,0 +1,39 @@
+/**
+ * Copyright (C) 2014 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#include "mongo/db/query/expression_index_knobs.h"
+#include "mongo/db/server_options.h"
+#include "mongo/db/server_parameters.h"
+
+namespace mongo {
+
+ MONGO_EXPORT_SERVER_PARAMETER(internalGeoPredicateQuery2DMaxCoveringCells, int, 16);
+
+ MONGO_EXPORT_SERVER_PARAMETER(internalGeoNearQuery2DMaxCoveringCells, int, 16);
+
+} // namespace mongo
diff --git a/src/mongo/db/query/expression_index_knobs.h b/src/mongo/db/query/expression_index_knobs.h
new file mode 100644
index 00000000000..6dcfbaf2592
--- /dev/null
+++ b/src/mongo/db/query/expression_index_knobs.h
@@ -0,0 +1,47 @@
+/**
+ * Copyright (C) 2014 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#pragma once
+
+namespace mongo {
+
+ //
+ // Geo Query knobs
+ //
+
+ /**
+ * The maximum number of cells to use for 2D geo query covering for predicate queries
+ */
+ extern int internalGeoPredicateQuery2DMaxCoveringCells;
+
+ /**
+ * The maximum number of cells to use for 2D geo query covering for predicate queries
+ */
+ extern int internalGeoNearQuery2DMaxCoveringCells;
+
+} // namespace mongo
diff --git a/src/mongo/db/query/index_bounds_builder.cpp b/src/mongo/db/query/index_bounds_builder.cpp
index 63e1f7571a5..d9e423f9e07 100644
--- a/src/mongo/db/query/index_bounds_builder.cpp
+++ b/src/mongo/db/query/index_bounds_builder.cpp
@@ -31,10 +31,12 @@
#include <limits>
#include "mongo/db/geo/geoconstants.h"
#include "mongo/db/geo/s2common.h"
-#include "mongo/db/index/expression_index.h"
#include "mongo/db/matcher/expression_geo.h"
+#include "mongo/db/query/expression_index.h"
+#include "mongo/db/query/expression_index_knobs.h"
#include "mongo/db/query/indexability.h"
#include "mongo/db/query/qlog.h"
+#include "mongo/db/query/query_knobs.h"
#include "mongo/util/mongoutils/str.h"
#include "mongo/db/geo/s2.h"
#include "third_party/s2/s2cell.h"
@@ -584,7 +586,12 @@ namespace mongo {
else if (mongoutils::str::equals("2d", elt.valuestrsafe())) {
verify(gme->getGeoQuery().getGeometry().hasR2Region());
const R2Region& region = gme->getGeoQuery().getGeometry().getR2Region();
- ExpressionMapping::cover2d(region, index.infoObj, oilOut);
+
+ ExpressionMapping::cover2d(region,
+ index.infoObj,
+ internalGeoPredicateQuery2DMaxCoveringCells,
+ oilOut);
+
*tightnessOut = IndexBoundsBuilder::INEXACT_FETCH;
}
else {
diff --git a/src/mongo/db/query/planner_ixselect.cpp b/src/mongo/db/query/planner_ixselect.cpp
index 8d4252edc6a..61cda0d0eea 100644
--- a/src/mongo/db/query/planner_ixselect.cpp
+++ b/src/mongo/db/query/planner_ixselect.cpp
@@ -244,16 +244,15 @@ namespace mongo {
else if (exprtype == MatchExpression::GEO_NEAR) {
GeoNearMatchExpression* gnme = static_cast<GeoNearMatchExpression*>(node);
// Make sure the near query is compatible with 2dsphere.
- if (gnme->getData().centroid.crs == SPHERE || gnme->getData().isNearSphere) {
- return true;
- }
+ return gnme->getData().centroid.crs == SPHERE;
}
return false;
}
else if (IndexNames::GEO_2D == indexedFieldType) {
if (exprtype == MatchExpression::GEO_NEAR) {
GeoNearMatchExpression* gnme = static_cast<GeoNearMatchExpression*>(node);
- return gnme->getData().centroid.crs == FLAT;
+ // Make sure the near query is compatible with 2d index
+ return gnme->getData().centroid.crs == FLAT || !gnme->getData().isWrappingQuery;
}
else if (exprtype == MatchExpression::GEO) {
// 2d only supports within.
diff --git a/src/mongo/db/query/query_solution.h b/src/mongo/db/query/query_solution.h
index 20c0c265245..0bbea738939 100644
--- a/src/mongo/db/query/query_solution.h
+++ b/src/mongo/db/query/query_solution.h
@@ -30,7 +30,7 @@
#include "mongo/db/jsobj.h"
#include "mongo/db/matcher/expression.h"
-#include "mongo/db/geo/geoquery.h"
+#include "mongo/db/geo/geo_query.h"
#include "mongo/db/fts/fts_query.h"
#include "mongo/db/query/index_bounds.h"
#include "mongo/db/query/plan_cache.h"