summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSiyuan Zhou <siyuan.zhou@mongodb.com>2014-07-21 14:36:17 -0400
committerSiyuan Zhou <siyuan.zhou@mongodb.com>2014-08-12 17:04:40 -0400
commitcd5c001c43b295ad601b0004f9c31f9d454f5d04 (patch)
tree72e6279b18ebb7272eac033af74a822ba8e8dd76
parent712768556e72d1756995c6a7b020b875fb9d6ea9 (diff)
downloadmongo-cd5c001c43b295ad601b0004f9c31f9d454f5d04.tar.gz
SERVER-14510 Custom CRS for strict winding order enforcement
Add big polygon parsing and query.
-rw-r--r--jstests/core/geo_big_polygon.js95
-rw-r--r--src/mongo/db/exec/geo_near.cpp4
-rw-r--r--src/mongo/db/geo/geo_query.cpp9
-rw-r--r--src/mongo/db/geo/geometry_container.cpp102
-rw-r--r--src/mongo/db/geo/geoparser.cpp77
-rw-r--r--src/mongo/db/geo/geoparser.h1
-rw-r--r--src/mongo/db/geo/geoparser_test.cpp10
-rw-r--r--src/mongo/db/geo/shapes.cpp29
-rw-r--r--src/mongo/db/geo/shapes.h19
-rw-r--r--src/mongo/db/index/expression_keys_private.cpp5
-rw-r--r--src/mongo/db/matcher/expression_geo.cpp4
-rw-r--r--src/mongo/db/query/canonical_query.cpp6
12 files changed, 302 insertions, 59 deletions
diff --git a/jstests/core/geo_big_polygon.js b/jstests/core/geo_big_polygon.js
new file mode 100644
index 00000000000..7bd23324393
--- /dev/null
+++ b/jstests/core/geo_big_polygon.js
@@ -0,0 +1,95 @@
+//
+// Test of sample big polygon functionality
+//
+
+var coll = db.geo_big_polygon;
+coll.drop();
+
+//coll.ensureIndex({ loc : "2dsphere" });
+
+coll.getMongo().getDB("admin").runCommand({ setParameter : 1, verboseQueryLogging : true });
+
+var bigCRS = { type : "name",
+ properties : { name : "urn:mongodb:strictwindingcrs:EPSG:4326" } };
+
+var bigPoly20 = { type : "Polygon", coordinates : [[[10.0, 10.0],
+ [-10.0, 10.0],
+ [-10.0, -10.0],
+ [10.0, -10.0],
+ [10.0, 10.0]]],
+ crs : bigCRS };
+
+var bigPoly20Comp = { type : "Polygon", coordinates : [[[10.0, 10.0],
+ [10.0, -10.0],
+ [-10.0, -10.0],
+ [-10.0, 10.0],
+ [10.0, 10.0]]],
+ crs : bigCRS };
+
+var poly10 = { type : "Polygon", coordinates : [[[5.0, 5.0],
+ [5.0, -5.0],
+ [-5.0, -5.0],
+ [-5.0, 5.0],
+ [5.0, 5.0]]] };
+
+var line10 = { type : "LineString", coordinates : [[5.0, 5.0],
+ [5.0, -5.0],
+ [-5.0, -5.0],
+ [-5.0, 5.0],
+ [5.0, 5.0]] };
+
+var centerPoint = { type : "Point", coordinates : [0, 0] };
+
+var polarPoint = { type : "Point", coordinates : [85, 85] };
+
+var lineEquator = { type : "LineString", coordinates : [[-20, 0], [20, 0]] };
+
+assert.writeOK(coll.insert({ loc : poly10 }));
+assert.writeOK(coll.insert({ loc : line10 }));
+assert.writeOK(coll.insert({ loc : centerPoint }));
+assert.writeOK(coll.insert({ loc : polarPoint }));
+assert.writeOK(coll.insert({ loc : lineEquator }));
+assert.eq(coll.find({}).count(), 5);
+
+jsTest.log("Starting query...");
+
+assert.eq(coll.find({ loc : { $geoWithin : { $geometry : bigPoly20 } } }).count(), 3);
+assert.eq(coll.find({ loc : { $geoIntersects : { $geometry : bigPoly20 } } }).count(), 4);
+assert.eq(coll.find({ loc : { $geoWithin : { $geometry : bigPoly20Comp } } }).count(), 1);
+assert.eq(coll.find({ loc : { $geoIntersects : { $geometry : bigPoly20Comp } } }).count(), 2);
+
+assert.commandWorked(coll.ensureIndex({ loc : "2dsphere" }));
+
+assert.eq(coll.find({ loc : { $geoWithin : { $geometry : bigPoly20 } } }).count(), 3);
+assert.eq(coll.find({ loc : { $geoIntersects : { $geometry : bigPoly20 } } }).count(), 4);
+assert.eq(coll.find({ loc : { $geoWithin : { $geometry : bigPoly20Comp } } }).count(), 1);
+assert.eq(coll.find({ loc : { $geoIntersects : { $geometry : bigPoly20Comp } } }).count(), 2);
+
+
+// Test not indexing and querying big polygon
+assert.commandWorked(coll.dropIndexes());
+
+// 1. Without index, insert succeeds, but query ignores big polygon.
+var bigPoly10 = { type : "Polygon", coordinates : [[[5.0, 5.0],
+ [-5.0, 5.0],
+ [-5.0, -5.0],
+ [5.0, -5.0],
+ [5.0, 5.0]]],
+ crs : bigCRS };
+
+assert.writeOK(coll.insert({ _id: "bigPoly10", loc: bigPoly10}));
+
+assert.eq(coll.find({ loc : { $geoWithin : { $geometry : bigPoly20 } } }).count(), 3);
+assert.eq(coll.find({ loc : { $geoIntersects : { $geometry : bigPoly20 } } }).count(), 4);
+assert.eq(coll.find({ loc : { $geoWithin : { $geometry : bigPoly20Comp } } }).count(), 1);
+assert.eq(coll.find({ loc : { $geoIntersects : { $geometry : bigPoly20Comp } } }).count(), 2);
+
+// 2. Building index fails due to big polygon
+assert.commandFailed(coll.ensureIndex({ loc : "2dsphere" }));
+
+// 3. After removing big polygon, index builds successfully
+assert.writeOK(coll.remove({_id: "bigPoly10"}));
+assert.commandWorked(coll.ensureIndex({ loc : "2dsphere" }));
+
+// 4. With index, insert fails.
+assert.writeError(coll.insert({ _id: "bigPoly10", loc: bigPoly10}));
diff --git a/src/mongo/db/exec/geo_near.cpp b/src/mongo/db/exec/geo_near.cpp
index 04355f25f88..1c5a045a914 100644
--- a/src/mongo/db/exec/geo_near.cpp
+++ b/src/mongo/db/exec/geo_near.cpp
@@ -151,6 +151,10 @@ namespace mongo {
StoredGeometry& stored = **it;
+ // NOTE: A stored document with STRICT_SPHERE CRS is treated as a malformed document
+ // and ignored. Since GeoNear requires an index, there's no stored STRICT_SPHERE shape.
+ // So we don't check it here.
+
// NOTE: For now, we're sure that if we get this far in the query we'll have an
// appropriate index which validates the type of geometry we're pulling back here.
// TODO: It may make sense to change our semantics and, by default, only return
diff --git a/src/mongo/db/geo/geo_query.cpp b/src/mongo/db/geo/geo_query.cpp
index 05ae6fa4757..60cf9665bb7 100644
--- a/src/mongo/db/geo/geo_query.cpp
+++ b/src/mongo/db/geo/geo_query.cpp
@@ -271,6 +271,15 @@ namespace mongo {
if (!(parseLegacyQuery(obj) || parseNewQuery(obj)))
return false;
+ // Big polygon with strict winding order is represented as an S2Loop in SPHERE CRS.
+ // So converting the query to SPHERE CRS makes things easier than projecting all the data
+ // into STRICT_SPHERE CRS.
+ if (STRICT_SPHERE == geoContainer.getNativeCRS()) {
+ if (!geoContainer.supportsProject(SPHERE))
+ return false;
+ geoContainer.projectInto(SPHERE);
+ }
+
// $geoIntersect queries are hardcoded to *always* be in SPHERE CRS
// TODO: This is probably bad semantics, should not do this
if (GeoQuery::INTERSECT == predicate) {
diff --git a/src/mongo/db/geo/geometry_container.cpp b/src/mongo/db/geo/geometry_container.cpp
index 568351b75d1..c7ad0706ded 100644
--- a/src/mongo/db/geo/geometry_container.cpp
+++ b/src/mongo/db/geo/geometry_container.cpp
@@ -55,7 +55,7 @@ namespace mongo {
bool GeometryContainer::hasS2Region() const {
return (NULL != _point && _point->crs == SPHERE)
|| NULL != _line
- || (NULL != _polygon && _polygon->crs == SPHERE)
+ || (NULL != _polygon && (_polygon->crs == SPHERE || _polygon->crs == STRICT_SPHERE))
|| (NULL != _cap && _cap->crs == SPHERE)
|| NULL != _multiPoint
|| NULL != _multiLine
@@ -68,8 +68,10 @@ namespace mongo {
return _point->cell;
} else if (NULL != _line) {
return _line->line;
- } else if (NULL != _polygon && SPHERE == _polygon->crs) {
- return _polygon->polygon;
+ } else if (NULL != _polygon && NULL != _polygon->s2Polygon) {
+ return *_polygon->s2Polygon;
+ } else if (NULL != _polygon && NULL != _polygon->bigPolygon) {
+ return *_polygon->bigPolygon;
} else if (NULL != _cap && SPHERE == _cap->crs) {
return _cap->cap;
} else if (NULL != _multiPoint) {
@@ -286,7 +288,8 @@ namespace mongo {
}
if (NULL != otherContainer._polygon) {
- return contains(otherContainer._polygon->polygon);
+ invariant(NULL != otherContainer._polygon->s2Polygon);
+ return contains(*otherContainer._polygon->s2Polygon);
}
if (NULL != otherContainer._multiPoint) {
@@ -331,7 +334,7 @@ namespace mongo {
const vector<PolygonWithCRS*>& polys = c.polygons.vector();
for (size_t i = 0; i < polys.size(); ++i) {
- if (!contains(polys[i]->polygon)) { return false; }
+ if (!contains(*polys[i]->s2Polygon)) { return false; }
}
const vector<MultiPointWithCRS*>& multipoints = c.multiPoints.vector();
@@ -372,8 +375,14 @@ namespace mongo {
}
bool GeometryContainer::contains(const S2Cell& otherCell, const S2Point& otherPoint) const {
- if (NULL != _polygon && (_polygon->crs == SPHERE)) {
- return containsPoint(_polygon->polygon, otherCell, otherPoint);
+ if (NULL != _polygon && (NULL != _polygon->s2Polygon)) {
+ return containsPoint(*_polygon->s2Polygon, otherCell, otherPoint);
+ }
+
+ if (NULL != _polygon && (NULL != _polygon->bigPolygon)) {
+ if (_polygon->bigPolygon->Contains(otherPoint))
+ return true;
+ return _polygon->bigPolygon->MayIntersect(otherCell);
}
if (NULL != _cap && (_cap->crs == SPHERE)) {
@@ -390,9 +399,9 @@ namespace mongo {
if (NULL != _geometryCollection) {
const vector<PolygonWithCRS*>& polys = _geometryCollection->polygons.vector();
for (size_t i = 0; i < polys.size(); ++i) {
- if (containsPoint(polys[i]->polygon, otherCell, otherPoint)) { return true; }
+ if (containsPoint(*polys[i]->s2Polygon, otherCell, otherPoint)) { return true; }
}
-
+
const vector<MultiPolygonWithCRS*>& multipolys =_geometryCollection->multiPolygons.vector();
for (size_t i = 0; i < multipolys.size(); ++i) {
const vector<S2Polygon*>& innerpolys = multipolys[i]->polygons.vector();
@@ -424,8 +433,12 @@ namespace mongo {
}
bool GeometryContainer::contains(const S2Polyline& otherLine) const {
- if (NULL != _polygon && (_polygon->crs == SPHERE)) {
- return containsLine(_polygon->polygon, otherLine);
+ if (NULL != _polygon && NULL != _polygon->s2Polygon) {
+ return containsLine(*_polygon->s2Polygon, otherLine);
+ }
+
+ if (NULL != _polygon && NULL != _polygon->bigPolygon) {
+ return _polygon->bigPolygon->Contains(otherLine);
}
if (NULL != _multiPolygon) {
@@ -438,9 +451,9 @@ namespace mongo {
if (NULL != _geometryCollection) {
const vector<PolygonWithCRS*>& polys = _geometryCollection->polygons.vector();
for (size_t i = 0; i < polys.size(); ++i) {
- if (containsLine(polys[i]->polygon, otherLine)) { return true; }
+ if (containsLine(*polys[i]->s2Polygon, otherLine)) { return true; }
}
-
+
const vector<MultiPolygonWithCRS*>& multipolys =_geometryCollection->multiPolygons.vector();
for (size_t i = 0; i < multipolys.size(); ++i) {
const vector<S2Polygon*>& innerpolys = multipolys[i]->polygons.vector();
@@ -458,8 +471,12 @@ namespace mongo {
}
bool GeometryContainer::contains(const S2Polygon& otherPolygon) const {
- if (NULL != _polygon && (_polygon->crs == SPHERE)) {
- return containsPolygon(_polygon->polygon, otherPolygon);
+ if (NULL != _polygon && NULL != _polygon->s2Polygon) {
+ return containsPolygon(*_polygon->s2Polygon, otherPolygon);
+ }
+
+ if (NULL != _polygon && NULL != _polygon->bigPolygon) {
+ return _polygon->bigPolygon->Contains(otherPolygon);
}
if (NULL != _multiPolygon) {
@@ -472,9 +489,9 @@ namespace mongo {
if (NULL != _geometryCollection) {
const vector<PolygonWithCRS*>& polys = _geometryCollection->polygons.vector();
for (size_t i = 0; i < polys.size(); ++i) {
- if (containsPolygon(polys[i]->polygon, otherPolygon)) { return true; }
+ if (containsPolygon(*polys[i]->s2Polygon, otherPolygon)) { return true; }
}
-
+
const vector<MultiPolygonWithCRS*>& multipolys =_geometryCollection->multiPolygons.vector();
for (size_t i = 0; i < multipolys.size(); ++i) {
const vector<S2Polygon*>& innerpolys = multipolys[i]->polygons.vector();
@@ -493,8 +510,8 @@ namespace mongo {
} else if (NULL != otherContainer._line) {
return intersects(otherContainer._line->line);
} else if (NULL != otherContainer._polygon) {
- if (SPHERE != otherContainer._polygon->crs) { return false; }
- return intersects(otherContainer._polygon->polygon);
+ if (NULL == otherContainer._polygon->s2Polygon) { return false; }
+ return intersects(*otherContainer._polygon->s2Polygon);
} else if (NULL != otherContainer._multiPoint) {
return intersects(*otherContainer._multiPoint);
} else if (NULL != otherContainer._multiLine) {
@@ -509,7 +526,7 @@ namespace mongo {
}
for (size_t i = 0; i < c.polygons.vector().size(); ++i) {
- if (intersects(c.polygons.vector()[i]->polygon)) { return true; }
+ if (intersects(*c.polygons.vector()[i]->s2Polygon)) { return true; }
}
for (size_t i = 0; i < c.lines.vector().size(); ++i) {
@@ -559,8 +576,10 @@ namespace mongo {
return _point->cell.MayIntersect(otherPoint);
} else if (NULL != _line) {
return _line->line.MayIntersect(otherPoint);
- } else if (NULL != _polygon) {
- return _polygon->polygon.MayIntersect(otherPoint);
+ } else if (NULL != _polygon && NULL != _polygon->s2Polygon) {
+ return _polygon->s2Polygon->MayIntersect(otherPoint);
+ } else if (NULL != _polygon && NULL != _polygon->bigPolygon) {
+ return _polygon->bigPolygon->MayIntersect(otherPoint);
} else if (NULL != _multiPoint) {
const vector<S2Cell>& cells = _multiPoint->cells;
for (size_t i = 0; i < cells.size(); ++i) {
@@ -584,7 +603,7 @@ namespace mongo {
}
for (size_t i = 0; i < c.polygons.vector().size(); ++i) {
- if (c.polygons.vector()[i]->polygon.MayIntersect(otherPoint)) { return true; }
+ if (c.polygons.vector()[i]->s2Polygon->MayIntersect(otherPoint)) { return true; }
}
for (size_t i = 0; i < c.lines.vector().size(); ++i) {
@@ -633,8 +652,10 @@ namespace mongo {
return otherLine.MayIntersect(_point->cell);
} else if (NULL != _line) {
return otherLine.Intersects(&_line->line);
- } else if (NULL != _polygon && (_polygon->crs == SPHERE)) {
- return polygonLineIntersection(otherLine, _polygon->polygon);
+ } else if (NULL != _polygon && NULL != _polygon->s2Polygon) {
+ return polygonLineIntersection(otherLine, *_polygon->s2Polygon);
+ } else if (NULL != _polygon && NULL != _polygon->bigPolygon) {
+ return _polygon->bigPolygon->Intersects(otherLine);
} else if (NULL != _multiPoint) {
for (size_t i = 0; i < _multiPoint->cells.size(); ++i) {
if (otherLine.MayIntersect(_multiPoint->cells[i])) { return true; }
@@ -659,7 +680,7 @@ namespace mongo {
}
for (size_t i = 0; i < c.polygons.vector().size(); ++i) {
- if (polygonLineIntersection(otherLine, c.polygons.vector()[i]->polygon)) {
+ if (polygonLineIntersection(otherLine, *c.polygons.vector()[i]->s2Polygon)) {
return true;
}
}
@@ -703,8 +724,10 @@ namespace mongo {
return otherPolygon.MayIntersect(_point->cell);
} else if (NULL != _line) {
return polygonLineIntersection(_line->line, otherPolygon);
- } else if (NULL != _polygon) {
- return otherPolygon.Intersects(&_polygon->polygon);
+ } else if (NULL != _polygon && NULL != _polygon->s2Polygon) {
+ return otherPolygon.Intersects(_polygon->s2Polygon.get());
+ } else if (NULL != _polygon && NULL != _polygon->bigPolygon) {
+ return _polygon->bigPolygon->Intersects(otherPolygon);
} else if (NULL != _multiPoint) {
for (size_t i = 0; i < _multiPoint->cells.size(); ++i) {
if (otherPolygon.MayIntersect(_multiPoint->cells[i])) { return true; }
@@ -729,7 +752,7 @@ namespace mongo {
}
for (size_t i = 0; i < c.polygons.vector().size(); ++i) {
- if (otherPolygon.Intersects(&c.polygons.vector()[i]->polygon)) {
+ if (otherPolygon.Intersects(c.polygons.vector()[i]->s2Polygon.get())) {
return true;
}
}
@@ -825,7 +848,7 @@ namespace mongo {
_s2Region->Add(&_geometryCollection->lines.vector()[i]->line);
}
for (size_t i = 0; i < _geometryCollection->polygons.vector().size(); ++i) {
- _s2Region->Add(&_geometryCollection->polygons.vector()[i]->polygon);
+ _s2Region->Add(_geometryCollection->polygons.vector()[i]->s2Polygon.get());
}
for (size_t i = 0; i < _geometryCollection->multiPoints.vector().size(); ++i) {
MultiPointWithCRS* multiPoint = _geometryCollection->multiPoints.vector()[i];
@@ -901,7 +924,9 @@ namespace mongo {
}
else if (NULL != _line) { return _line->crs == otherCRS; }
else if (NULL != _box) { return _box->crs == otherCRS; }
- else if (NULL != _polygon) { return _polygon->crs == otherCRS; }
+ else if (NULL != _polygon) {
+ return ShapeProjection::supportsProject(*_polygon, otherCRS);
+ }
else if (NULL != _cap ) { return _cap->crs == otherCRS; }
else if (NULL != _multiPoint) { return _multiPoint->crs == otherCRS; }
else if (NULL != _multiLine) { return _multiLine->crs == otherCRS; }
@@ -914,11 +939,14 @@ namespace mongo {
void GeometryContainer::projectInto(CRS otherCRS) {
- if (otherCRS == getNativeCRS())
+ if (getNativeCRS() == otherCRS) return;
+
+ if (NULL != _polygon) {
+ ShapeProjection::projectInto(_polygon.get(), otherCRS);
return;
+ }
invariant(NULL != _point);
-
ShapeProjection::projectInto(_point.get(), otherCRS);
}
@@ -995,7 +1023,9 @@ namespace mongo {
it != geometryCollection.polygons.vector().end(); ++it) {
invariant(SPHERE == (*it)->crs);
- double nextDistance = S2Distance::minDistanceRad(s2Point, (*it)->polygon);
+ // We don't support distances for big polygons yet.
+ invariant(NULL != (*it)->s2Polygon);
+ double nextDistance = S2Distance::minDistanceRad(s2Point, *((*it)->s2Polygon));
if (minDistance < 0 || nextDistance < minDistance) {
minDistance = nextDistance;
}
@@ -1060,7 +1090,9 @@ namespace mongo {
minDistance = S2Distance::minDistanceRad(otherPoint.point, _line->line);
}
else if (NULL != _polygon) {
- minDistance = S2Distance::minDistanceRad(otherPoint.point, _polygon->polygon);
+ // We don't support distances for big polygons yet.
+ invariant(NULL != _polygon->s2Polygon);
+ minDistance = S2Distance::minDistanceRad(otherPoint.point, *_polygon->s2Polygon);
}
else if (NULL != _cap) {
minDistance = S2Distance::minDistanceRad(otherPoint.point, _cap->cap);
diff --git a/src/mongo/db/geo/geoparser.cpp b/src/mongo/db/geo/geoparser.cpp
index 6efecc3ec58..658e145efb1 100644
--- a/src/mongo/db/geo/geoparser.cpp
+++ b/src/mongo/db/geo/geoparser.cpp
@@ -153,7 +153,9 @@ namespace mongo {
}
static bool parseGeoJSONPolygonCoordinates(const vector<BSONElement>& coordinates,
- const BSONObj &sourceObject, S2Polygon *out) {
+ const BSONObj &sourceObject,
+ S2Polygon *out) {
+
const vector<BSONElement>& exteriorRing = coordinates[0].Array();
vector<S2Point> exteriorVertices;
if (!parsePoints(exteriorRing, &exteriorVertices)) { return false; }
@@ -206,6 +208,38 @@ namespace mongo {
return polyBuilder.AssemblePolygon(out, NULL);
}
+ static bool parseBigSimplePolygonCoordinates(const vector<BSONElement>& coordinates,
+ const BSONObj &sourceObject,
+ BigSimplePolygon *out) {
+
+ // Only one loop is allowed in a BigSimplePolygon
+ if (coordinates.size() != 1)
+ return false;
+
+ const vector<BSONElement>& exteriorRing = coordinates[0].Array();
+
+ vector<S2Point> exteriorVertices;
+ if (!parsePoints(exteriorRing, &exteriorVertices))
+ return false;
+
+ eraseDuplicatePoints(&exteriorVertices);
+
+ // The last point is duplicated. We drop it, since S2Loop expects no
+ // duplicate points
+ exteriorVertices.resize(exteriorVertices.size() - 1);
+
+ // S2 Polygon loops must have 3 vertices
+ if (exteriorVertices.size() < 3)
+ return false;
+
+ auto_ptr<S2Loop> loop(new S2Loop(exteriorVertices));
+ if (!loop->IsValid())
+ return false;
+
+ out->Init(loop.release());
+ return true;
+ }
+
static bool parseLegacyPoint(const BSONObj &obj, Point *out) {
BSONObjIterator it(obj);
BSONElement x = it.next();
@@ -326,7 +360,7 @@ namespace mongo {
BSONElement x = it.next();
BSONElement y = it.next();
out->oldPoint.x = x.Number();
- out->oldPoint.y = y.Number();
+ out->oldPoint.y = y.Number();
out->crs = FLAT;
} else if (isGeoJSONPoint(obj)) {
const vector<BSONElement>& coords = obj.getFieldDotted(GEOJSON_COORDINATES).Array();
@@ -401,8 +435,22 @@ namespace mongo {
bool GeoParser::parsePolygon(const BSONObj &obj, PolygonWithCRS *out) {
if (isGeoJSONPolygon(obj)) {
const vector<BSONElement>& coordinates = obj.getFieldDotted(GEOJSON_COORDINATES).Array();
- if (!parseGeoJSONPolygonCoordinates(coordinates, obj, &out->polygon)) { return false; }
- out->crs = SPHERE;
+
+ if (!parseGeoJSONCRS(obj, &out->crs))
+ return false;
+
+ if (out->crs == SPHERE) {
+ out->s2Polygon.reset(new S2Polygon());
+ if (!parseGeoJSONPolygonCoordinates(coordinates, obj, out->s2Polygon.get())) {
+ return false;
+ }
+ }
+ else if (out->crs == STRICT_SPHERE) {
+ out->bigPolygon.reset(new BigSimplePolygon());
+ if (!parseBigSimplePolygonCoordinates(coordinates, obj, out->bigPolygon.get())) {
+ return false;
+ }
+ }
} else {
BSONObjIterator typeIt(obj);
BSONElement type = typeIt.next();
@@ -580,7 +628,26 @@ namespace mongo {
// see http://portal.opengeospatial.org/files/?artifact_id=24045
// and http://spatialreference.org/ref/epsg/4326/
// and http://www.geojson.org/geojson-spec.html#named-crs
- return ("urn:ogc:def:crs:OGC:1.3:CRS84" == name) || ("EPSG:4326" == name);
+ return ("urn:ogc:def:crs:OGC:1.3:CRS84" == name) || ("EPSG:4326" == name) ||
+ ("urn:mongodb:strictwindingcrs:EPSG:4326" == name);
+ }
+
+ bool GeoParser::parseGeoJSONCRS(const BSONObj& obj, CRS* crs) {
+
+ dassert(crsIsOK(obj));
+
+ *crs = SPHERE;
+
+ if (!obj["crs"].eoo()) {
+ const string name = obj["crs"].Obj()["properties"].Obj()["name"].String();
+
+ if (name == "urn:mongodb:strictwindingcrs:EPSG:4326")
+ *crs = STRICT_SPHERE;
+ else
+ *crs = SPHERE;
+ }
+
+ return true;
}
bool GeoParser::isCap(const BSONObj &obj) {
diff --git a/src/mongo/db/geo/geoparser.h b/src/mongo/db/geo/geoparser.h
index e8222bcce74..d996de84664 100644
--- a/src/mongo/db/geo/geoparser.h
+++ b/src/mongo/db/geo/geoparser.h
@@ -82,6 +82,7 @@ namespace mongo {
// returning states: missing, invalid, unknown, ok, etc. -- whatever
// needed.
static bool crsIsOK(const BSONObj& obj);
+ static bool parseGeoJSONCRS(const BSONObj& obj, CRS* crs);
};
} // namespace mongo
diff --git a/src/mongo/db/geo/geoparser_test.cpp b/src/mongo/db/geo/geoparser_test.cpp
index eeac3725336..e3f124d24c9 100644
--- a/src/mongo/db/geo/geoparser_test.cpp
+++ b/src/mongo/db/geo/geoparser_test.cpp
@@ -143,7 +143,7 @@ namespace {
GeoParser::parsePolygon(
fromjson("{'type':'Polygon', 'coordinates':[ [[0,0],[5,0],[5,5],[0,5],[0,0]] ]}"),
&polygonA);
- ASSERT_TRUE(polygonA.polygon.Contains(point.point));
+ ASSERT_TRUE(polygonA.s2Polygon->Contains(point.point));
PolygonWithCRS polygonB;
GeoParser::parsePolygon(
@@ -151,7 +151,7 @@ namespace {
" [[1,1],[1,4],[4,4],[4,1],[1,1]] ]}"),
&polygonB);
// We removed this in the hole.
- ASSERT_FALSE(polygonB.polygon.Contains(point.point));
+ ASSERT_FALSE(polygonB.s2Polygon->Contains(point.point));
// Now we reverse the orientations and verify that the code fixes it up
// (outer loop must be CCW, inner CW).
@@ -159,7 +159,7 @@ namespace {
GeoParser::parsePolygon(
fromjson("{'type':'Polygon', 'coordinates':[ [[0,0],[0,5],[5,5],[5,0],[0,0]] ]}"),
&polygonC);
- ASSERT_TRUE(polygonC.polygon.Contains(point.point));
+ ASSERT_TRUE(polygonC.s2Polygon->Contains(point.point));
PolygonWithCRS polygonD;
GeoParser::parsePolygon(
@@ -167,7 +167,7 @@ namespace {
" [[1,1],[1,4],[4,4],[4,1],[1,1]] ]}"),
&polygonD);
// Also removed in the loop.
- ASSERT_FALSE(polygonD.polygon.Contains(point.point));
+ ASSERT_FALSE(polygonD.s2Polygon->Contains(point.point));
//
// Bad polygon examples
@@ -300,7 +300,7 @@ namespace {
fromjson("{'type':'MultiLine','coordinates':[ [[1,1],[2,2],[3,3]],"
"[[4,5],[6,7]]]}"),
&mls);
-
+
GeoParser::parseMultiLine(
fromjson("{'type':'MultiLine','coordinates':[ [[1,1],[2,2]],"
"[[4,5],[6,7]]]}"),
diff --git a/src/mongo/db/geo/shapes.cpp b/src/mongo/db/geo/shapes.cpp
index 14a4fa3de04..562e35a46fc 100644
--- a/src/mongo/db/geo/shapes.cpp
+++ b/src/mongo/db/geo/shapes.cpp
@@ -759,6 +759,11 @@ namespace mongo {
return isValidLngLat(point.oldPoint.x, point.oldPoint.y);
}
+ bool ShapeProjection::supportsProject(const PolygonWithCRS& polygon, const CRS crs) {
+ return polygon.crs == crs
+ || (polygon.crs == STRICT_SPHERE && crs == SPHERE);
+ }
+
void ShapeProjection::projectInto(PointWithCRS* point, CRS crs) {
dassert(supportsProject(*point, crs));
@@ -766,6 +771,7 @@ namespace mongo {
return;
if (FLAT == point->crs) {
+ // Prohibit projection to STRICT_SPHERE CRS
invariant(SPHERE == crs);
// Note that it's (lat, lng) for S2 but (lng, lat) for MongoDB.
@@ -775,16 +781,23 @@ namespace mongo {
point->point = latLng.ToPoint();
point->cell = S2Cell(point->point);
point->crs = SPHERE;
+ return;
}
- else {
- invariant(SPHERE == point->crs);
- invariant(FLAT == crs);
- // Just remove the additional spherical information
- point->point = S2Point();
- point->cell = S2Cell();
- point->crs = FLAT;
- }
+ // Prohibit projection to STRICT_SPHERE CRS
+ invariant(SPHERE == point->crs && FLAT == crs);
+ // Just remove the additional spherical information
+ point->point = S2Point();
+ point->cell = S2Cell();
+ point->crs = FLAT;
+ }
+
+ void ShapeProjection::projectInto(PolygonWithCRS* polygon, CRS crs) {
+ if (polygon->crs == crs) return;
+
+ // Only project from STRICT_SPHERE to SPHERE
+ invariant(STRICT_SPHERE == polygon->crs && SPHERE == crs);
+ polygon->crs = SPHERE;
}
} // namespace mongo
diff --git a/src/mongo/db/geo/shapes.h b/src/mongo/db/geo/shapes.h
index 4091ce9357c..074c71d2e4e 100644
--- a/src/mongo/db/geo/shapes.h
+++ b/src/mongo/db/geo/shapes.h
@@ -34,6 +34,7 @@
#include "mongo/base/owned_pointer_vector.h"
#include "mongo/db/jsobj.h"
+#include "mongo/db/geo/big_polygon.h"
#include "mongo/db/geo/s2.h"
#include "third_party/s2/s2cap.h"
#include "third_party/s2/s2cell.h"
@@ -250,8 +251,9 @@ namespace mongo {
// Clearly this isn't right but currently it's sufficient.
enum CRS {
UNSET,
- FLAT,
- SPHERE
+ FLAT, // Equirectangular flat projection (i.e. trivial long/lat projection to flat map)
+ SPHERE, // WGS84
+ STRICT_SPHERE // WGS84 with strict winding order
};
// TODO: Make S2 less integral to these types - additional S2 shapes should be an optimization
@@ -298,7 +300,11 @@ namespace mongo {
PolygonWithCRS() : crs(UNSET) {}
- S2Polygon polygon;
+ scoped_ptr<S2Polygon> s2Polygon;
+ // Simple polygons with strict winding order may be bigger or smaller than a hemisphere.
+ // Only used for query. We don't support storing/indexing big polygons.
+ scoped_ptr<BigSimplePolygon> bigPolygon;
+
Polygon oldPolygon;
CRS crs;
};
@@ -347,12 +353,15 @@ namespace mongo {
};
//
- // Projection functions - we don't project types other than points for now
+ // Projection functions - we only project following types for now
+ // - Point
+ // - Polygon (from STRICT_SPHERE TO SPHERE)
//
-
struct ShapeProjection {
static bool supportsProject(const PointWithCRS& point, const CRS crs);
+ static bool supportsProject(const PolygonWithCRS& polygon, const CRS crs);
static void projectInto(PointWithCRS* point, CRS crs);
+ static void projectInto(PolygonWithCRS* point, CRS crs);
};
} // namespace mongo
diff --git a/src/mongo/db/index/expression_keys_private.cpp b/src/mongo/db/index/expression_keys_private.cpp
index 9ca7eade530..44be3b10aa8 100644
--- a/src/mongo/db/index/expression_keys_private.cpp
+++ b/src/mongo/db/index/expression_keys_private.cpp
@@ -91,6 +91,11 @@ namespace {
GeometryContainer geoContainer;
if (!geoContainer.parseFrom(obj)) { return false; }
+ // Don't index big polygon
+ if (geoContainer.getNativeCRS() == STRICT_SPHERE) {
+ return false;
+ }
+
// Only certain geometries can be indexed in the old index format S2_INDEX_VERSION_1. See
// definition of S2IndexVersion for details.
if (params.indexVersion == S2_INDEX_VERSION_1 && !geoContainer.isSimpleContainer()) {
diff --git a/src/mongo/db/matcher/expression_geo.cpp b/src/mongo/db/matcher/expression_geo.cpp
index 3c55f8a9133..ee3142d1120 100644
--- a/src/mongo/db/matcher/expression_geo.cpp
+++ b/src/mongo/db/matcher/expression_geo.cpp
@@ -52,6 +52,10 @@ namespace mongo {
if ( !geometry.parseFrom( e.Obj() ) )
return false;
+ // Never match big polygon
+ if (geometry.getNativeCRS() == STRICT_SPHERE)
+ return false;
+
// Project this geometry into the CRS of the query
if (!geometry.supportsProject(_query->getGeometry().getNativeCRS()))
return false;
diff --git a/src/mongo/db/query/canonical_query.cpp b/src/mongo/db/query/canonical_query.cpp
index b143f59e150..603c636a403 100644
--- a/src/mongo/db/query/canonical_query.cpp
+++ b/src/mongo/db/query/canonical_query.cpp
@@ -172,6 +172,9 @@ namespace {
else if (SPHERE == geoQuery.getGeometry().getNativeCRS()) {
*os << "sp";
}
+ else if (STRICT_SPHERE == geoQuery.getGeometry().getNativeCRS()) {
+ *os << "ss";
+ }
else {
error() << "unknown CRS type " << (int)geoQuery.getGeometry().getNativeCRS()
<< " in geometry of type " << geoQuery.getGeometry().getDebugType();
@@ -192,10 +195,11 @@ namespace {
// isNearSphere
*os << (nearQuery.isNearSphere ? "ns" : "nr");
- // CRS (flat or spherical)
+ // CRS (flat or spherical or strict-winding spherical)
switch (nearQuery.centroid.crs) {
case FLAT: *os << "fl"; break;
case SPHERE: *os << "sp"; break;
+ case STRICT_SPHERE: *os << "ss"; break;
case UNSET:
error() << "unknown CRS type " << (int)nearQuery.centroid.crs
<< " in point geometry for near query";