summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSiyuan Zhou <siyuan.zhou@mongodb.com>2014-08-27 18:31:04 -0400
committerSiyuan Zhou <siyuan.zhou@mongodb.com>2014-09-15 15:37:28 -0400
commit13617e4a80f2d1be45f2b6f237e0f13b1d1c68a0 (patch)
tree20cf5601dfc7399ddfd46dd9c8a215257563e9ba
parent8ffb9fae4fc1dbc6519bf4b04ac9316616c2efdf (diff)
downloadmongo-13617e4a80f2d1be45f2b6f237e0f13b1d1c68a0.tar.gz
SERVER-14508 Rewrite geo parsing using Status
-rw-r--r--jstests/core/geo_big_polygon.js11
-rw-r--r--src/mongo/db/commands/geo_near_cmd.cpp4
-rw-r--r--src/mongo/db/exec/geo_near.cpp8
-rw-r--r--src/mongo/db/geo/geometry_container.cpp203
-rw-r--r--src/mongo/db/geo/geometry_container.h11
-rw-r--r--src/mongo/db/geo/geoparser.cpp881
-rw-r--r--src/mongo/db/geo/geoparser.h82
-rw-r--r--src/mongo/db/geo/geoparser_test.cpp408
-rw-r--r--src/mongo/db/geo/r2_region_coverer_test.cpp4
-rw-r--r--src/mongo/db/index/expression_keys_private.cpp7
-rw-r--r--src/mongo/db/matcher/expression_geo.cpp134
-rw-r--r--src/mongo/db/matcher/expression_geo.h7
12 files changed, 864 insertions, 896 deletions
diff --git a/jstests/core/geo_big_polygon.js b/jstests/core/geo_big_polygon.js
index 7bd23324393..ac105230d74 100644
--- a/jstests/core/geo_big_polygon.js
+++ b/jstests/core/geo_big_polygon.js
@@ -93,3 +93,14 @@ assert.commandWorked(coll.ensureIndex({ loc : "2dsphere" }));
// 4. With index, insert fails.
assert.writeError(coll.insert({ _id: "bigPoly10", loc: bigPoly10}));
+
+// Query geometries that don't support big CRS should error out.
+var bigPoint = { type: "Point", coordinates: [0, 0], crs: bigCRS };
+var bigLine = { type : "LineString", coordinates : [[-20, 0], [20, 0]], crs: bigCRS };
+
+assert.throws(function() {
+ coll.find( { loc : { $geoIntersects : { $geometry : bigPoint }}}).itcount();
+});
+assert.throws(function() {
+ coll.find( { loc : { $geoIntersects : { $geometry : bigLine }}}).itcount();
+});
diff --git a/src/mongo/db/commands/geo_near_cmd.cpp b/src/mongo/db/commands/geo_near_cmd.cpp
index 7b1be4adc82..cdc9a26d034 100644
--- a/src/mongo/db/commands/geo_near_cmd.cpp
+++ b/src/mongo/db/commands/geo_near_cmd.cpp
@@ -101,9 +101,9 @@ namespace mongo {
return false;
}
+ PointWithCRS point;
uassert(17304, "'near' field must be point",
- !cmdObj["near"].eoo() && cmdObj["near"].isABSONObj()
- && GeoParser::isPoint(cmdObj["near"].Obj()));
+ GeoParser::parseQueryPoint(cmdObj["near"], &point).isOK());
bool isSpherical = cmdObj["spherical"].trueValue();
if (!using2DIndex) {
diff --git a/src/mongo/db/exec/geo_near.cpp b/src/mongo/db/exec/geo_near.cpp
index f5217d068b6..452444c3c31 100644
--- a/src/mongo/db/exec/geo_near.cpp
+++ b/src/mongo/db/exec/geo_near.cpp
@@ -68,7 +68,7 @@ namespace mongo {
return NULL;
auto_ptr<StoredGeometry> stored(new StoredGeometry);
- if (!stored->geometry.parseFrom(element.Obj()))
+ if (!stored->geometry.parseFromStorage(element).isOK())
return NULL;
stored->element = element;
return stored.release();
@@ -326,12 +326,8 @@ namespace mongo {
if (!e.isABSONObj())
return false;
- if (!GeoParser::isIndexablePoint(e.Obj()))
- return false;
-
PointWithCRS point;
- if (!GeoParser::parsePoint(e.Obj(), &point))
- return false;
+ if (!GeoParser::parseStoredPoint(e, &point).isOK()) return false;
return _annulus.contains(point.oldPoint);
}
diff --git a/src/mongo/db/geo/geometry_container.cpp b/src/mongo/db/geo/geometry_container.cpp
index e62799a917c..55afbe5c021 100644
--- a/src/mongo/db/geo/geometry_container.cpp
+++ b/src/mongo/db/geo/geometry_container.cpp
@@ -45,6 +45,7 @@ namespace mongo {
bool GeometryContainer::supportsContains() const {
return NULL != _polygon
+ || NULL != _box
|| NULL != _cap
|| NULL != _multiPolygon
|| (NULL != _geometryCollection
@@ -796,88 +797,182 @@ namespace mongo {
return false;
}
- bool GeometryContainer::parseFrom(const BSONObj& obj) {
+ Status GeometryContainer::parseFromGeoJSON(const BSONObj& obj) {
+ GeoParser::GeoJSONType type = GeoParser::parseGeoJSONType(obj);
- if (GeoParser::isPolygon(obj)) {
- // We can't really pass these things around willy-nilly except by ptr.
- _polygon.reset(new PolygonWithCRS());
- if (!GeoParser::parsePolygon(obj, _polygon.get())) { return false; }
- } else if (GeoParser::isIndexablePoint(obj)) {
+ if (GeoParser::GEOJSON_UNKNOWN == type) {
+ return Status(ErrorCodes::BadValue, str::stream() << "unknown GeoJSON type: " << obj);
+ }
+
+ Status status = Status::OK();
+ vector<S2Region*> regions;
+
+ if (GeoParser::GEOJSON_POINT == type) {
_point.reset(new PointWithCRS());
- if (!GeoParser::parsePoint(obj, _point.get())) { return false; }
- } else if (GeoParser::isLine(obj)) {
+ status = GeoParser::parseGeoJSONPoint(obj, _point.get());
+ } else if (GeoParser::GEOJSON_LINESTRING == type) {
_line.reset(new LineWithCRS());
- if (!GeoParser::parseLine(obj, _line.get())) { return false; }
- } else if (GeoParser::isBox(obj)) {
- _box.reset(new BoxWithCRS());
- if (!GeoParser::parseBox(obj, _box.get())) { return false; }
- } else if (GeoParser::isCap(obj)) {
- _cap.reset(new CapWithCRS());
- if (!GeoParser::parseCap(obj, _cap.get())) { return false; }
- } else if (GeoParser::isMultiPoint(obj)) {
+ status = GeoParser::parseGeoJSONLine(obj, _line.get());
+ } else if (GeoParser::GEOJSON_POLYGON == type) {
+ _polygon.reset(new PolygonWithCRS());
+ status = GeoParser::parseGeoJSONPolygon(obj, _polygon.get());
+ } else if (GeoParser::GEOJSON_MULTI_POINT == type) {
_multiPoint.reset(new MultiPointWithCRS());
- if (!GeoParser::parseMultiPoint(obj, _multiPoint.get())) { return false; }
- _s2Region.reset(new S2RegionUnion());
+ status = GeoParser::parseMultiPoint(obj, _multiPoint.get());
for (size_t i = 0; i < _multiPoint->cells.size(); ++i) {
- _s2Region->Add(&_multiPoint->cells[i]);
+ regions.push_back(&_multiPoint->cells[i]);
}
- } else if (GeoParser::isMultiLine(obj)) {
+ } else if (GeoParser::GEOJSON_MULTI_LINESTRING == type) {
_multiLine.reset(new MultiLineWithCRS());
- if (!GeoParser::parseMultiLine(obj, _multiLine.get())) { return false; }
- _s2Region.reset(new S2RegionUnion());
- for (size_t i = 0; i < _multiLine->lines.vector().size(); ++i) {
- _s2Region->Add(_multiLine->lines.vector()[i]);
+ status = GeoParser::parseMultiLine(obj, _multiLine.get());
+ for (size_t i = 0; i < _multiLine->lines.size(); ++i) {
+ regions.push_back(_multiLine->lines[i]);
}
- } else if (GeoParser::isMultiPolygon(obj)) {
+ } else if (GeoParser::GEOJSON_MULTI_POLYGON == type) {
_multiPolygon.reset(new MultiPolygonWithCRS());
- if (!GeoParser::parseMultiPolygon(obj, _multiPolygon.get())) { return false; }
- _s2Region.reset(new S2RegionUnion());
- for (size_t i = 0; i < _multiPolygon->polygons.vector().size(); ++i) {
- _s2Region->Add(_multiPolygon->polygons.vector()[i]);
+ status = GeoParser::parseMultiPolygon(obj, _multiPolygon.get());
+ for (size_t i = 0; i < _multiPolygon->polygons.size(); ++i) {
+ regions.push_back(_multiPolygon->polygons[i]);
}
- } else if (GeoParser::isGeometryCollection(obj)) {
+ } else if (GeoParser::GEOJSON_GEOMETRY_COLLECTION == type) {
_geometryCollection.reset(new GeometryCollection());
- if (!GeoParser::parseGeometryCollection(obj, _geometryCollection.get())) {
- return false;
- }
- _s2Region.reset(new S2RegionUnion());
+ status = GeoParser::parseGeometryCollection(obj, _geometryCollection.get());
+
+ // Add regions
for (size_t i = 0; i < _geometryCollection->points.size(); ++i) {
- _s2Region->Add(&_geometryCollection->points[i].cell);
+ regions.push_back(&_geometryCollection->points[i].cell);
}
- for (size_t i = 0; i < _geometryCollection->lines.vector().size(); ++i) {
- _s2Region->Add(&_geometryCollection->lines.vector()[i]->line);
+ for (size_t i = 0; i < _geometryCollection->lines.size(); ++i) {
+ regions.push_back(&_geometryCollection->lines[i]->line);
}
- for (size_t i = 0; i < _geometryCollection->polygons.vector().size(); ++i) {
- _s2Region->Add(_geometryCollection->polygons.vector()[i]->s2Polygon.get());
+ for (size_t i = 0; i < _geometryCollection->polygons.size(); ++i) {
+ regions.push_back(_geometryCollection->polygons[i]->s2Polygon.get());
}
- for (size_t i = 0; i < _geometryCollection->multiPoints.vector().size(); ++i) {
- MultiPointWithCRS* multiPoint = _geometryCollection->multiPoints.vector()[i];
+ for (size_t i = 0; i < _geometryCollection->multiPoints.size(); ++i) {
+ MultiPointWithCRS* multiPoint = _geometryCollection->multiPoints[i];
for (size_t j = 0; j < multiPoint->cells.size(); ++j) {
- _s2Region->Add(&multiPoint->cells[j]);
+ regions.push_back(&multiPoint->cells[j]);
}
}
- for (size_t i = 0; i < _geometryCollection->multiLines.vector().size(); ++i) {
- const MultiLineWithCRS* multiLine = _geometryCollection->multiLines.vector()[i];
- for (size_t j = 0; j < multiLine->lines.vector().size(); ++j) {
- _s2Region->Add(multiLine->lines.vector()[j]);
+ for (size_t i = 0; i < _geometryCollection->multiLines.size(); ++i) {
+ const MultiLineWithCRS* multiLine = _geometryCollection->multiLines[i];
+ for (size_t j = 0; j < multiLine->lines.size(); ++j) {
+ regions.push_back(multiLine->lines[j]);
}
}
- for (size_t i = 0; i < _geometryCollection->multiPolygons.vector().size(); ++i) {
- const MultiPolygonWithCRS* multiPolygon =
- _geometryCollection->multiPolygons.vector()[i];
- for (size_t j = 0; j < multiPolygon->polygons.vector().size(); ++j) {
- _s2Region->Add(multiPolygon->polygons.vector()[j]);
+ for (size_t i = 0; i < _geometryCollection->multiPolygons.size(); ++i) {
+ const MultiPolygonWithCRS* multiPolygon = _geometryCollection->multiPolygons[i];
+ for (size_t j = 0; j < multiPolygon->polygons.size(); ++j) {
+ regions.push_back(multiPolygon->polygons[j]);
}
}
} else {
- return false;
+ // Should not reach here.
+ invariant(false);
+ }
+
+ // Check parsing result.
+ if (!status.isOK()) return status;
+
+ if (regions.size() > 0) {
+ // S2RegionUnion doesn't take ownership of pointers.
+ _s2Region.reset(new S2RegionUnion(&regions));
+ }
+
+ return Status::OK();
+ }
+
+ // Examples:
+ // { $geoWithin : { $geometry : <GeoJSON> } }
+ // { $geoIntersects : { $geometry : <GeoJSON> } }
+ // { $geoWithin : { $box : [[x1, y1], [x2, y2]] } }
+ // { $geoWithin : { $polygon : [[x1, y1], [x1, y2], [x2, y2], [x2, y1]] } }
+ // { $geoWithin : { $center : [[x1, y1], r], } }
+ // { $geoWithin : { $centerSphere : [[x, y], radius] } }
+ // { $geoIntersects : { $geometry : [1, 2] } }
+ //
+ // "elem" is the first element of the object after $geoWithin / $geoIntersects predicates.
+ // i.e. { $box: ... }, { $geometry: ... }
+ Status GeometryContainer::parseFromQuery(const BSONElement& elem) {
+ // Check elem is an object and has geo specifier.
+ GeoParser::GeoSpecifier specifier = GeoParser::parseGeoSpecifier(elem);
+
+ if (GeoParser::UNKNOWN == specifier) {
+ // Cannot parse geo specifier.
+ return Status(ErrorCodes::BadValue, str::stream() << "unknown geo specifier: " << elem);
+ }
+
+ Status status = Status::OK();
+ BSONObj obj = elem.Obj();
+ if (GeoParser::BOX == specifier) {
+ _box.reset(new BoxWithCRS());
+ status = GeoParser::parseLegacyBox(obj, _box.get());
+ } else if (GeoParser::CENTER == specifier) {
+ _cap.reset(new CapWithCRS());
+ status = GeoParser::parseLegacyCenter(obj, _cap.get());
+ } else if (GeoParser::POLYGON == specifier) {
+ _polygon.reset(new PolygonWithCRS());
+ status = GeoParser::parseLegacyPolygon(obj, _polygon.get());
+ } else if (GeoParser::CENTER_SPHERE == specifier) {
+ _cap.reset(new CapWithCRS());
+ status = GeoParser::parseCenterSphere(obj, _cap.get());
+ } else if (GeoParser::GEOMETRY == specifier) {
+ // GeoJSON geometry or legacy point
+ if (Array == elem.type() || obj.firstElement().isNumber()) {
+ // legacy point
+ _point.reset(new PointWithCRS());
+ status = GeoParser::parseQueryPoint(elem, _point.get());
+ } else {
+ // GeoJSON geometry
+ status = parseFromGeoJSON(obj);
+ }
}
+ if (!status.isOK()) return status;
// If we support R2 regions, build the region immediately
- if (hasR2Region())
+ if (hasR2Region()) {
_r2Region.reset(new R2BoxRegion(this));
+ }
+
+ return status;
+ }
+
+ // Examples:
+ // { location: <GeoJSON> }
+ // { location: [1, 2] }
+ // { location: [1, 2, 3] }
+ // { location: {x: 1, y: 2} }
+ //
+ // "elem" is the element that contains geo data. e.g. "location": [1, 2]
+ // We need the type information to determine whether it's legacy point.
+ Status GeometryContainer::parseFromStorage(const BSONElement& elem) {
+ if (!elem.isABSONObj()) {
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "geo element must be an array or object: " << elem);
+ }
+
+ BSONObj geoObj = elem.Obj();
+ Status status = Status::OK();
+ if (Array == elem.type() || geoObj.firstElement().isNumber()) {
+ // Legacy point
+ // { location: [1, 2] }
+ // { location: [1, 2, 3] }
+ // { location: {x: 1, y: 2} }
+ // { location: {x: 1, y: 2, type: "Point" } }
+ _point.reset(new PointWithCRS());
+ // Allow more than two dimensions or extra fields, like [1, 2, 3]
+ status = GeoParser::parseLegacyPoint(elem, _point.get(), true);
+ } else {
+ // GeoJSON
+ // { location: { type: "Point", coordinates: [...] } }
+ status = parseFromGeoJSON(elem.Obj());
+ }
+ if (!status.isOK()) return status;
+
+ // If we support R2 regions, build the region immediately
+ if (hasR2Region()) _r2Region.reset(new R2BoxRegion(this));
- return true;
+ return Status::OK();
}
string GeometryContainer::getDebugType() const {
diff --git a/src/mongo/db/geo/geometry_container.h b/src/mongo/db/geo/geometry_container.h
index 7bd054778ff..501b17ce687 100644
--- a/src/mongo/db/geo/geometry_container.h
+++ b/src/mongo/db/geo/geometry_container.h
@@ -46,9 +46,14 @@ namespace mongo {
GeometryContainer();
/**
- * Loads an empty GeometryContainer from BSON
+ * Loads an empty GeometryContainer from query.
*/
- bool parseFrom(const BSONObj &obj);
+ Status parseFromQuery(const BSONElement& elem);
+
+ /**
+ * Loads an empty GeometryContainer from stored geometry.
+ */
+ Status parseFromStorage(const BSONElement& elem);
/**
* Is the geometry any of {Point, Line, Polygon}?
@@ -117,6 +122,8 @@ namespace mongo {
class R2BoxRegion;
+ Status parseFromGeoJSON(const BSONObj& obj);
+
// Does 'this' intersect with the provided type?
bool intersects(const S2Cell& otherPoint) const;
bool intersects(const S2Polyline& otherLine) const;
diff --git a/src/mongo/db/geo/geoparser.cpp b/src/mongo/db/geo/geoparser.cpp
index d38fd2adcda..0ae6029fae2 100644
--- a/src/mongo/db/geo/geoparser.cpp
+++ b/src/mongo/db/geo/geoparser.cpp
@@ -53,38 +53,33 @@ namespace mongo {
static const string GEOJSON_COORDINATES = "coordinates";
static const string GEOJSON_GEOMETRIES = "geometries";
- static bool isGeoJSONPoint(const BSONObj& obj) {
- BSONElement type = obj.getFieldDotted(GEOJSON_TYPE);
- if (type.eoo() || (String != type.type())) { return false; }
- if (GEOJSON_TYPE_POINT != type.String()) { return false; }
-
- if (!GeoParser::crsIsOK(obj)) {
- warning() << "Invalid CRS: " << obj.toString() << endl;
- return false;
- }
-
- BSONElement coordElt = obj.getFieldDotted(GEOJSON_COORDINATES);
- if (coordElt.eoo() || (Array != coordElt.type())) { return false; }
-
- 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);
- }
-
- static bool isLegacyPoint(const BSONObj &obj, bool allowAddlFields) {
- BSONObjIterator it(obj);
- if (!it.more()) { return false; }
+ // Coordinate System Reference
+ // 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
+ static const string CRS_CRS84 = "urn:ogc:def:crs:OGC:1.3:CRS84";
+ static const string CRS_EPSG_4326 = "EPSG:4326";
+ static const string CRS_STRICT_WINDING = "urn:mongodb:strictwindingcrs:EPSG:4326";
+
+ // XXX Return better errors for all bad values
+ static const Status BAD_VALUE_STATUS(ErrorCodes::BadValue, "");
+
+ static Status parseFlatPoint(const BSONElement &elem, Point *out, bool allowAddlFields = false) {
+ if (!elem.isABSONObj()) return BAD_VALUE_STATUS;
+ BSONObjIterator it(elem.Obj());
BSONElement x = it.next();
- if (!x.isNumber()) { return false; }
- if (!it.more()) { return false; }
+ if (!x.isNumber()) { return BAD_VALUE_STATUS; }
BSONElement y = it.next();
- if (!y.isNumber()) { return false; }
- if (it.more() && !allowAddlFields) { return false; }
- return true;
+ if (!y.isNumber()) { return BAD_VALUE_STATUS; }
+ if (!allowAddlFields && it.more()) { return BAD_VALUE_STATUS; }
+ out->x = x.number();
+ out->y = y.number();
+ return Status::OK();
+ }
+
+ Status GeoParser::parseLegacyPoint(const BSONElement &elem, PointWithCRS *out, bool allowAddlFields) {
+ out->crs = FLAT;
+ return parseFlatPoint(elem, &out->oldPoint, allowAddlFields);
}
static S2Point coordToPoint(double lng, double lat) {
@@ -102,6 +97,31 @@ namespace mongo {
return ll.ToPoint();
}
+ static Status parseGeoJSONCoodinate(const BSONElement& elem, S2Point* out) {
+ if (Array != elem.type()) { return BAD_VALUE_STATUS; }
+ Point p;
+ // Check the object has and only has 2 numbers.
+ Status status = parseFlatPoint(elem, &p);
+ if (!status.isOK()) return status;
+ if (!isValidLngLat(p.x, p.y)) { return BAD_VALUE_STATUS; }
+ *out = coordToPoint(p.x, p.y);
+ return Status::OK();
+ }
+
+ // "coordinates": [ [100.0, 0.0], [101.0, 1.0] ]
+ static Status parseArrayOfCoodinates(const BSONElement& elem, vector<S2Point>* out) {
+ if (Array != elem.type()) { return BAD_VALUE_STATUS; }
+ BSONObjIterator it(elem.Obj());
+ // Iterate all coordinates in array
+ while (it.more()) {
+ S2Point p;
+ Status status = parseGeoJSONCoodinate(it.next(), &p);
+ if (!status.isOK()) return status;
+ out->push_back(p);
+ }
+ return Status::OK();
+ }
+
static void eraseDuplicatePoints(vector<S2Point>* vertices) {
for (size_t i = 1; i < vertices->size(); ++i) {
if ((*vertices)[i - 1] == (*vertices)[i]) {
@@ -112,57 +132,30 @@ namespace mongo {
}
}
- static bool isArrayOfCoordinates(const vector<BSONElement>& coordinateArray) {
- for (size_t i = 0; i < coordinateArray.size(); ++i) {
- // Each coordinate should be an array
- if (Array != coordinateArray[i].type()) { return false; }
- // ...of two
- const vector<BSONElement> &thisCoord = coordinateArray[i].Array();
- if (2 != thisCoord.size()) { return false; }
- // ...numbers.
- for (size_t j = 0; j < thisCoord.size(); ++j) {
- if (!thisCoord[j].isNumber()) { return false; }
- }
- // ...where the longitude, latitude is valid
- double lat = thisCoord[1].Number();
- double lng = thisCoord[0].Number();
- if (!isValidLngLat(lng, lat)) { return false; }
- }
- return true;
+ static Status isLoopClosed(const vector<S2Point>& loop) {
+ if (loop.empty() || loop[0] != loop[loop.size() - 1]) return BAD_VALUE_STATUS;
+ return Status::OK();
}
- static bool parsePoints(const vector<BSONElement>& coordElt, vector<S2Point>* out) {
- for (size_t i = 0; i < coordElt.size(); ++i) {
- const vector<BSONElement>& pointElt = coordElt[i].Array();
- if (pointElt.empty()) { continue; }
- if (!isValidLngLat(pointElt[0].Number(), pointElt[1].Number())) {
- return false;
- }
- out->push_back(coordToPoint(pointElt[0].Number(), pointElt[1].Number()));
- }
-
- return true;
- }
-
- static bool isValidLineString(const vector<BSONElement>& coordinateArray) {
- if (coordinateArray.size() < 2) { return false; }
- if (!isArrayOfCoordinates(coordinateArray)) { return false; }
- vector<S2Point> vertices;
- if (!parsePoints(coordinateArray, &vertices)) { return false; }
- eraseDuplicatePoints(&vertices);
- return S2Polyline::IsValid(vertices);
- }
-
- static bool parseGeoJSONPolygonCoordinates(const vector<BSONElement>& coordinates,
- const BSONObj &sourceObject,
- S2Polygon *out) {
+ static Status parseGeoJSONPolygonCoordinates(const BSONElement& elem, S2Polygon *out) {
+ if (Array != elem.type()) { return BAD_VALUE_STATUS; }
OwnedPointerVector<S2Loop> loops;
- for (size_t i = 0; i < coordinates.size(); i++) {
- const vector<BSONElement>& loopCoordinates = coordinates[i].Array();
+ Status status = Status::OK();
+
+ BSONObjIterator it(elem.Obj());
+ // Iterate all loops of the polygon.
+ while (it.more()) {
+ // Parse the array of vertices of a loop.
+ BSONElement coordinateElt = it.next();
vector<S2Point> points;
+ status = parseArrayOfCoodinates(coordinateElt, &points);
+ if (!status.isOK()) return status;
+
+ // Check if the loop is closed.
+ status = isLoopClosed(points);
+ if (!status.isOK()) return status;
- if (!parsePoints(loopCoordinates, &points)) { return false; }
eraseDuplicatePoints(&points);
// Drop the duplicated last point.
points.resize(points.size() - 1);
@@ -176,7 +169,7 @@ namespace mongo {
// 3. Loops are not allowed to have any duplicate vertices.
// 4. Non-adjacent edges are not allowed to intersect.
if (!loop->IsValid()) {
- return false;
+ return BAD_VALUE_STATUS;
}
// If the loop is more than one hemisphere, invert it.
@@ -184,14 +177,14 @@ namespace mongo {
// Check the first loop must be the exterior ring and any others must be
// interior rings or holes.
- if (i > 0 && !loops[0]->Contains(loop)) return false;
+ if (loops.size() > 1 && !loops[0]->Contains(loop)) return BAD_VALUE_STATUS;
}
// Check if the given loops form a valid polygon.
// 1. If a loop contains an edge AB, then no other loop may contain AB or BA.
// 2. No loop covers more than half of the sphere.
// 3. No two loops cross.
- if (!S2Polygon::IsValid(loops.vector())) return false;
+ if (!S2Polygon::IsValid(loops.vector())) return BAD_VALUE_STATUS;
// Given all loops are valid / normalized and S2Polygon::IsValid() above returns true.
// The polygon must be valid. See S2Polygon member function IsValid().
@@ -201,39 +194,42 @@ namespace mongo {
// Check if every loop of this polygon shares at most one vertex with
// its parent loop.
- if (!out->IsNormalized()) return false;
+ if (!out->IsNormalized()) return BAD_VALUE_STATUS;
// S2Polygon contains more than one ring, which is allowed by S2, but not by GeoJSON.
//
// Loops are indexed according to a preorder traversal of the nesting hierarchy.
// GetLastDescendant() returns the index of the last loop that is contained within
// a given loop. We guarantee that the first loop is the exterior ring.
- if (out->GetLastDescendant(0) < out->num_loops() - 1) return false;
+ if (out->GetLastDescendant(0) < out->num_loops() - 1) return BAD_VALUE_STATUS;
// In GeoJSON, only one nesting is allowed.
// The depth of a loop is set by polygon according to the nesting hierarchy of polygon,
// so the exterior ring's depth is 0, a hole in it is 1, etc.
for (int i = 0; i < out->num_loops(); i++) {
if (out->loop(i)->depth() > 1) {
- return false;
+ return BAD_VALUE_STATUS;
}
}
- return true;
+ return Status::OK();
}
- static bool parseBigSimplePolygonCoordinates(const vector<BSONElement>& coordinates,
- const BSONObj &sourceObject,
- BigSimplePolygon *out) {
+ static Status parseBigSimplePolygonCoordinates(const BSONElement& elem,
+ BigSimplePolygon *out) {
+ if (Array != elem.type()) return BAD_VALUE_STATUS;
+ const vector<BSONElement>& coordinates = elem.Array();
// Only one loop is allowed in a BigSimplePolygon
if (coordinates.size() != 1)
- return false;
-
- const vector<BSONElement>& exteriorRing = coordinates[0].Array();
+ return BAD_VALUE_STATUS;
vector<S2Point> exteriorVertices;
- if (!parsePoints(exteriorRing, &exteriorVertices))
- return false;
+ Status status = Status::OK();
+ status = parseArrayOfCoodinates(coordinates.front(), &exteriorVertices);
+ if (!status.isOK()) return status;
+
+ status = isLoopClosed(exteriorVertices);
+ if (!status.isOK()) return status;
eraseDuplicatePoints(&exteriorVertices);
@@ -243,505 +239,355 @@ namespace mongo {
// S2 Polygon loops must have 3 vertices
if (exteriorVertices.size() < 3)
- return false;
+ return BAD_VALUE_STATUS;
auto_ptr<S2Loop> loop(new S2Loop(exteriorVertices));
if (!loop->IsValid())
- return false;
+ return BAD_VALUE_STATUS;
out->Init(loop.release());
- return true;
- }
-
- static bool parseLegacyPoint(const BSONObj &obj, Point *out) {
- BSONObjIterator it(obj);
- BSONElement x = it.next();
- BSONElement y = it.next();
- out->x = x.number();
- out->y = y.number();
- return true;
- }
-
- // Coordinates looks like [[0,0],[5,0],[5,5],[0,5],[0,0]]
- static bool isLoopClosed(const vector<BSONElement>& coordinates) {
- double x1, y1, x2, y2;
- x1 = coordinates[0].Array()[0].Number();
- y1 = coordinates[0].Array()[1].Number();
- x2 = coordinates[coordinates.size() - 1].Array()[0].Number();
- y2 = coordinates[coordinates.size() - 1].Array()[1].Number();
- return (fabs(x1 - x2) < 1e-6) && fabs(y1 - y2) < 1e-6;
- }
-
- static bool isGeoJSONPolygonCoordinates(const vector<BSONElement>& coordinates) {
- // Must be at least one element, the outer shell
- if (coordinates.empty()) { return false; }
- // Verify that the shell is a bunch'a coordinates.
- for (size_t i = 0; i < coordinates.size(); ++i) {
- if (Array != coordinates[i].type()) { return false; }
- const vector<BSONElement>& thisLoop = coordinates[i].Array();
- // A triangle is the simplest 2d shape, and we repeat a vertex, so, 4.
- if (thisLoop.size() < 4) { return false; }
- if (!isArrayOfCoordinates(thisLoop)) { return false; }
- if (!isLoopClosed(thisLoop)) { return false; }
- }
- return true;
+ return Status::OK();
}
- static bool isGeoJSONPolygon(const BSONObj& obj) {
- BSONElement type = obj.getFieldDotted(GEOJSON_TYPE);
- if (type.eoo() || (String != type.type())) { return false; }
- if (GEOJSON_TYPE_POLYGON != type.String()) { return false; }
+ // Parse "crs" field of BSON object.
+ // "crs": {
+ // "type": "name",
+ // "properties": {
+ // "name": "urn:ogc:def:crs:OGC:1.3:CRS84"
+ // }
+ // }
+ static Status parseGeoJSONCRS(const BSONObj &obj, CRS* crs, bool allowStrictSphere = false) {
+ *crs = SPHERE;
- if (!GeoParser::crsIsOK(obj)) {
- warning() << "Invalid CRS: " << obj.toString() << endl;
- return false;
+ BSONElement crsElt = obj["crs"];
+ // "crs" field doesn't exist, return the default SPHERE
+ if (crsElt.eoo()) {
+ return Status::OK();
}
- BSONElement coordElt = obj.getFieldDotted(GEOJSON_COORDINATES);
- if (coordElt.eoo() || (Array != coordElt.type())) { return false; }
+ if (!crsElt.isABSONObj()) return BAD_VALUE_STATUS;
+ BSONObj crsObj = crsElt.embeddedObject();
- return isGeoJSONPolygonCoordinates(coordElt.Array());
- }
+ // "type": "name"
+ if (String != crsObj["type"].type() || "name" != crsObj["type"].String())
+ return BAD_VALUE_STATUS;
- static bool isLegacyPolygon(const BSONObj &obj) {
- BSONObjIterator typeIt(obj);
- BSONElement type = typeIt.next();
- if (!type.isABSONObj()) { return false; }
- if (!mongoutils::str::equals(type.fieldName(), "$polygon")) { return false; }
- BSONObjIterator coordIt(type.embeddedObject());
- int vertices = 0;
- while (coordIt.more()) {
- BSONElement coord = coordIt.next();
- if (!coord.isABSONObj()) { return false; }
- if (!isLegacyPoint(coord.Obj(), false)) { return false; }
- ++vertices;
+ // "properties"
+ BSONElement propertiesElt = crsObj["properties"];
+ if (!propertiesElt.isABSONObj()) return BAD_VALUE_STATUS;
+ BSONObj propertiesObj = propertiesElt.embeddedObject();
+ if (String != propertiesObj["name"].type()) return BAD_VALUE_STATUS;
+ const string& name = propertiesObj["name"].String();
+ if (CRS_CRS84 == name || CRS_EPSG_4326 == name) {
+ *crs = SPHERE;
+ } else if (CRS_STRICT_WINDING == name) {
+ if (!allowStrictSphere) {
+ return Status(ErrorCodes::BadValue,
+ "strict winding order is only supported by polygon");
+ }
+ *crs = STRICT_SPHERE;
+ } else {
+ return BAD_VALUE_STATUS;
}
- if (vertices < 3) { return false; }
- return true;
+ return Status::OK();
}
- static bool isLegacyCenter(const BSONObj &obj) {
- BSONObjIterator typeIt(obj);
- BSONElement type = typeIt.next();
- if (!type.isABSONObj()) { return false; }
- bool isCenter = mongoutils::str::equals(type.fieldName(), "$center");
- if (!isCenter) { return false; }
- BSONObjIterator objIt(type.embeddedObject());
- BSONElement center = objIt.next();
- if (!center.isABSONObj()) { return false; }
- if (!isLegacyPoint(center.Obj(), false)) { return false; }
- if (!objIt.more()) { return false; }
- BSONElement radius = objIt.next();
- if (!radius.isNumber()) { return false; }
- return true;
- }
-
- static bool isLegacyCenterSphere(const BSONObj &obj) {
- BSONObjIterator typeIt(obj);
- BSONElement type = typeIt.next();
- if (!type.isABSONObj()) { return false; }
- bool isCenterSphere = mongoutils::str::equals(type.fieldName(), "$centerSphere");
- if (!isCenterSphere) { return false; }
- BSONObjIterator objIt(type.embeddedObject());
- BSONElement center = objIt.next();
- if (!center.isABSONObj()) { return false; }
- if (!isLegacyPoint(center.Obj(), false)) { return false; }
- // Check to make sure the points are valid lng/lat.
- BSONObjIterator coordIt(center.Obj());
- BSONElement lng = coordIt.next();
- BSONElement lat = coordIt.next();
- if (!isValidLngLat(lng.Number(), lat.Number())) { return false; }
- if (!objIt.more()) { return false; }
- BSONElement radius = objIt.next();
- if (!radius.isNumber()) { return false; }
- return true;
- }
-
- /** exported **/
-
- bool GeoParser::isPoint(const BSONObj &obj) {
- return isLegacyPoint(obj, false) || isGeoJSONPoint(obj);
- }
+ // Parse "coordinates" field of GeoJSON LineString
+ // e.g. "coordinates": [ [100.0, 0.0], [101.0, 1.0] ]
+ // Or a line in "coordinates" field of GeoJSON MultiLineString
+ static Status parseGeoJSONLineCoordinates(const BSONElement& elem, S2Polyline* out) {
+ vector<S2Point> vertices;
+ Status status = parseArrayOfCoodinates(elem, &vertices);
+ if (!status.isOK()) return status;
- bool GeoParser::isIndexablePoint(const BSONObj &obj) {
- return isLegacyPoint(obj, true) || isGeoJSONPoint(obj);
- }
+ eraseDuplicatePoints(&vertices);
+ if (vertices.size() < 2) return BAD_VALUE_STATUS;
- bool GeoParser::parsePoint(const BSONObj &obj, PointWithCRS *out) {
- if (isLegacyPoint(obj, true)) {
- BSONObjIterator it(obj);
- BSONElement x = it.next();
- BSONElement y = it.next();
- 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;
+ // XXX change to status
+ if (!S2Polyline::IsValid(vertices)) return BAD_VALUE_STATUS;
+ out->Init(vertices);
+ return Status::OK();
}
- bool GeoParser::isLine(const BSONObj& obj) {
- BSONElement type = obj.getFieldDotted(GEOJSON_TYPE);
- if (type.eoo() || (String != type.type())) { return false; }
- if (GEOJSON_TYPE_LINESTRING != type.String()) { return false; }
+ // Parse legacy point or GeoJSON point, used by geo near.
+ // Only stored legacy points allow additional fields.
+ Status parsePoint(const BSONElement &elem, PointWithCRS *out, bool allowAddlFields) {
+ if (!elem.isABSONObj()) return BAD_VALUE_STATUS;
- if (!crsIsOK(obj)) {
- warning() << "Invalid CRS: " << obj.toString() << endl;
- return false;
+ BSONObj obj = elem.Obj();
+ // location: [1, 2] or location: {x: 1, y:2}
+ if (Array == elem.type() || obj.firstElement().isNumber()) {
+ // Legacy point
+ return GeoParser::parseLegacyPoint(elem, out, allowAddlFields);
}
- BSONElement coordElt = obj.getFieldDotted(GEOJSON_COORDINATES);
- if (coordElt.eoo() || (Array != coordElt.type())) { return false; }
-
- return isValidLineString(coordElt.Array());
+ // GeoJSON point. location: { type: "Point", coordinates: [1, 2] }
+ return GeoParser::parseGeoJSONPoint(obj, out);
}
- bool GeoParser::parseLine(const BSONObj& obj, LineWithCRS* out) {
- vector<S2Point> vertices;
- if (!parsePoints(obj.getFieldDotted(GEOJSON_COORDINATES).Array(), &vertices)) {
- return false;
- }
- eraseDuplicatePoints(&vertices);
- out->line.Init(vertices);
- out->crs = SPHERE;
- return true;
+ /** exported **/
+ Status GeoParser::parseStoredPoint(const BSONElement &elem, PointWithCRS *out) {
+ return parsePoint(elem, out, true);
}
- bool GeoParser::isBox(const BSONObj &obj) {
- BSONObjIterator typeIt(obj);
- BSONElement type = typeIt.next();
- if (!type.isABSONObj()) { return false; }
- if (!mongoutils::str::equals(type.fieldName(), "$box")) { return false; }
- BSONObjIterator coordIt(type.embeddedObject());
- BSONElement minE = coordIt.next();
- if (!minE.isABSONObj()) { return false; }
- if (!isLegacyPoint(minE.Obj(), false)) { return false; }
- if (!coordIt.more()) { return false; }
- BSONElement maxE = coordIt.next();
- if (!maxE.isABSONObj()) { return false; }
- if (!isLegacyPoint(maxE.Obj(), false)) { return false; }
- // XXX: VERIFY AREA >= 0
- return true;
+ Status GeoParser::parseQueryPoint(const BSONElement &elem, PointWithCRS *out) {
+ return parsePoint(elem, out, false);
}
- bool GeoParser::parseBox(const BSONObj &obj, BoxWithCRS *out) {
- BSONObjIterator typeIt(obj);
- BSONElement type = typeIt.next();
- BSONObjIterator coordIt(type.embeddedObject());
- BSONElement minE = coordIt.next();
- BSONElement maxE = coordIt.next();
+ Status GeoParser::parseLegacyBox(const BSONObj& obj, BoxWithCRS *out) {
Point ptA, ptB;
- if (!parseLegacyPoint(minE.Obj(), &ptA) ||
- !parseLegacyPoint(maxE.Obj(), &ptB)) { return false; }
+ Status status = Status::OK();
+
+ BSONObjIterator coordIt(obj);
+ status = parseFlatPoint(coordIt.next(), &ptA);
+ if (!status.isOK()) { return status; }
+ status = parseFlatPoint(coordIt.next(), &ptB);
+ if (!status.isOK()) { return status; }
+ // XXX: VERIFY AREA >= 0
+
out->box.init(ptA, ptB);
out->crs = FLAT;
- return true;
+ return status;
}
- bool GeoParser::parsePolygon(const BSONObj &obj, PolygonWithCRS *out) {
- if (isGeoJSONPolygon(obj)) {
- const vector<BSONElement>& coordinates = obj.getFieldDotted(GEOJSON_COORDINATES).Array();
-
- 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();
- BSONObjIterator coordIt(type.embeddedObject());
- vector<Point> points;
- while (coordIt.more()) {
- Point p;
- if (!parseLegacyPoint(coordIt.next().Obj(), &p)) { return false; }
- points.push_back(p);
- }
- out->oldPolygon.init(points);
- out->crs = FLAT;
+ Status GeoParser::parseLegacyPolygon(const BSONObj& obj, PolygonWithCRS *out) {
+ BSONObjIterator coordIt(obj);
+ vector<Point> points;
+ while (coordIt.more()) {
+ Point p;
+ // A coordinate
+ Status status = parseFlatPoint(coordIt.next(), &p);
+ if (!status.isOK()) return status;
+ points.push_back(p);
}
- return true;
+ if (points.size() < 3) return BAD_VALUE_STATUS;
+ out->oldPolygon.init(points);
+ out->crs = FLAT;
+ return Status::OK();
}
- bool GeoParser::isMultiPoint(const BSONObj &obj) {
- BSONElement type = obj.getFieldDotted(GEOJSON_TYPE);
- if (type.eoo() || (String != type.type())) { return false; }
- if (GEOJSON_TYPE_MULTI_POINT != type.String()) { return false; }
-
- if (!crsIsOK(obj)) {
- warning() << "Invalid CRS: " << obj.toString() << endl;
- return false;
- }
+ // { "type": "Point", "coordinates": [100.0, 0.0] }
+ Status GeoParser::parseGeoJSONPoint(const BSONObj &obj, PointWithCRS *out) {
+ Status status = Status::OK();
+ // "crs"
+ status = parseGeoJSONCRS(obj, &out->crs);
+ if (!status.isOK()) return status;
- BSONElement coordElt = obj.getFieldDotted(GEOJSON_COORDINATES);
- if (coordElt.eoo() || (Array != coordElt.type())) { return false; }
+ // "coordinates"
+ status = parseFlatPoint(obj[GEOJSON_COORDINATES], &out->oldPoint);
+ if (!status.isOK()) return status;
- const vector<BSONElement>& coordinates = coordElt.Array();
- if (0 == coordinates.size()) { return false; }
- return isArrayOfCoordinates(coordinates);
+ // Projection
+ out->crs = FLAT;
+ if (!ShapeProjection::supportsProject(*out, SPHERE))
+ return BAD_VALUE_STATUS;
+ ShapeProjection::projectInto(out, SPHERE);
+ return Status::OK();
}
- bool GeoParser::parseMultiPoint(const BSONObj &obj, MultiPointWithCRS *out) {
- out->points.clear();
- BSONElement coordElt = obj.getFieldDotted(GEOJSON_COORDINATES);
- const vector<BSONElement>& coordinates = coordElt.Array();
- out->points.resize(coordinates.size());
- out->cells.resize(coordinates.size());
- for (size_t i = 0; i < coordinates.size(); ++i) {
- const vector<BSONElement>& thisCoord = coordinates[i].Array();
- out->points[i] = coordToPoint(thisCoord[0].Number(), thisCoord[1].Number());
- out->cells[i] = S2Cell(out->points[i]);
- }
- out->crs = SPHERE;
+ // { "type": "LineString", "coordinates": [ [100.0, 0.0], [101.0, 1.0] ] }
+ Status GeoParser::parseGeoJSONLine(const BSONObj& obj, LineWithCRS* out) {
+ Status status = Status::OK();
+ // "crs"
+ status = parseGeoJSONCRS(obj, &out->crs);
+ if (!status.isOK()) return status;
- return true;
- }
+ // "coordinates"
+ status = parseGeoJSONLineCoordinates(obj[GEOJSON_COORDINATES], &out->line);
+ if (!status.isOK()) return status;
- bool GeoParser::isMultiLine(const BSONObj &obj) {
- BSONElement type = obj.getFieldDotted(GEOJSON_TYPE);
- if (type.eoo() || (String != type.type())) { return false; }
- if (GEOJSON_TYPE_MULTI_LINESTRING != type.String()) { return false; }
+ return Status::OK();
+ }
- if (!crsIsOK(obj)) {
- warning() << "Invalid CRS: " << obj.toString() << endl;
- return false;
- }
+ Status GeoParser::parseGeoJSONPolygon(const BSONObj &obj, PolygonWithCRS *out) {
+ const BSONElement coordinates = obj[GEOJSON_COORDINATES];
- BSONElement coordElt = obj.getFieldDotted(GEOJSON_COORDINATES);
- if (coordElt.eoo() || (Array != coordElt.type())) { return false; }
+ Status status = Status::OK();
+ // "crs", allow strict sphere
+ status = parseGeoJSONCRS(obj, &out->crs, true);
+ if (!status.isOK()) return status;
- const vector<BSONElement>& coordinates = coordElt.Array();
- if (0 == coordinates.size()) { return false; }
-
- for (size_t i = 0; i < coordinates.size(); ++i) {
- if (coordinates[i].eoo() || (Array != coordinates[i].type())) { return false; }
- if (!isValidLineString(coordinates[i].Array())) { return false; }
+ // "coordinates"
+ if (out->crs == SPHERE) {
+ out->s2Polygon.reset(new S2Polygon());
+ status = parseGeoJSONPolygonCoordinates(coordinates, out->s2Polygon.get());
}
-
- return true;
- }
-
- bool GeoParser::parseMultiLine(const BSONObj &obj, MultiLineWithCRS *out) {
- vector<BSONElement> coordElt = obj.getFieldDotted(GEOJSON_COORDINATES).Array();
- out->lines.mutableVector().clear();
- out->lines.mutableVector().resize(coordElt.size());
-
- for (size_t i = 0; i < coordElt.size(); ++i) {
- vector<S2Point> vertices;
- if (!parsePoints(coordElt[i].Array(), &vertices)) { return false; }
- out->lines.mutableVector()[i] = new S2Polyline();
- out->lines.mutableVector()[i]->Init(vertices);
+ else if (out->crs == STRICT_SPHERE) {
+ out->bigPolygon.reset(new BigSimplePolygon());
+ status = parseBigSimplePolygonCoordinates(coordinates, out->bigPolygon.get());
}
- out->crs = SPHERE;
-
- return true;
+ return status;
}
- bool GeoParser::isMultiPolygon(const BSONObj &obj) {
- BSONElement type = obj.getFieldDotted(GEOJSON_TYPE);
- if (type.eoo() || (String != type.type())) { return false; }
- if (GEOJSON_TYPE_MULTI_POLYGON != type.String()) { return false; }
-
- if (!crsIsOK(obj)) {
- warning() << "Invalid CRS: " << obj.toString() << endl;
- return false;
- }
+ Status GeoParser::parseMultiPoint(const BSONObj &obj, MultiPointWithCRS *out) {
+ Status status = Status::OK();
+ status = parseGeoJSONCRS(obj, &out->crs);
+ if (!status.isOK()) return status;
+ out->points.clear();
BSONElement coordElt = obj.getFieldDotted(GEOJSON_COORDINATES);
- if (coordElt.eoo() || (Array != coordElt.type())) { return false; }
+ status = parseArrayOfCoodinates(coordElt, &out->points);
+ if (!status.isOK()) return status;
- const vector<BSONElement>& coordinates = coordElt.Array();
- if (0 == coordinates.size()) { return false; }
- for (size_t i = 0; i < coordinates.size(); ++i) {
- if (coordinates[i].eoo() || (Array != coordinates[i].type())) { return false; }
- if (!isGeoJSONPolygonCoordinates(coordinates[i].Array())) { return false; }
+ if (0 == out->points.size()) return BAD_VALUE_STATUS;
+ out->cells.resize(out->points.size());
+ for (size_t i = 0; i < out->points.size(); ++i) {
+ out->cells[i] = S2Cell(out->points[i]);
}
- return true;
+ return Status::OK();
}
- bool GeoParser::parseMultiPolygon(const BSONObj &obj, MultiPolygonWithCRS *out) {
- vector<BSONElement> coordElt = obj.getFieldDotted(GEOJSON_COORDINATES).Array();
- out->polygons.mutableVector().clear();
- out->polygons.mutableVector().resize(coordElt.size());
-
- for (size_t i = 0; i < coordElt.size(); ++i) {
- out->polygons.mutableVector()[i] = new S2Polygon();
- if (!parseGeoJSONPolygonCoordinates(
- coordElt[i].Array(), obj, out->polygons.vector()[i])) {
- return false;
- }
- }
- out->crs = SPHERE;
-
- return true;
- }
+ Status GeoParser::parseMultiLine(const BSONObj &obj, MultiLineWithCRS *out) {
+ Status status = Status::OK();
+ status = parseGeoJSONCRS(obj, &out->crs);
+ if (!status.isOK()) return status;
- bool GeoParser::isGeometryCollection(const BSONObj &obj) {
- BSONElement type = obj.getFieldDotted(GEOJSON_TYPE);
- if (type.eoo() || (String != type.type())) { return false; }
- if (GEOJSON_TYPE_GEOMETRY_COLLECTION != type.String()) { return false; }
+ BSONElement coordElt = obj.getFieldDotted(GEOJSON_COORDINATES);
+ if (Array != coordElt.type()) return BAD_VALUE_STATUS;
- BSONElement coordElt = obj.getFieldDotted(GEOJSON_GEOMETRIES);
- if (coordElt.eoo() || (Array != coordElt.type())) { return false; }
+ vector<S2Polyline*>& lines = out->lines.mutableVector();
+ lines.clear();
- const vector<BSONElement>& coordinates = coordElt.Array();
- if (0 == coordinates.size()) { return false; }
+ BSONObjIterator it(coordElt.Obj());
- for (size_t i = 0; i < coordinates.size(); ++i) {
- if (coordinates[i].eoo() || (Object != coordinates[i].type())) { return false; }
- BSONObj obj = coordinates[i].Obj();
- if (!isGeoJSONPoint(obj) && !isLine(obj) && !isGeoJSONPolygon(obj)
- && !isMultiPoint(obj) && !isMultiPolygon(obj) && !isMultiLine(obj)) {
- return false;
- }
+ // Iterate array
+ while (it.more()) {
+ lines.push_back(new S2Polyline());
+ status = parseGeoJSONLineCoordinates(it.next(), lines.back());
+ if (!status.isOK()) return status;
}
+ if (0 == lines.size()) { return BAD_VALUE_STATUS; }
- return true;
+ return Status::OK();
}
- bool GeoParser::isPolygon(const BSONObj &obj) {
- return isGeoJSONPolygon(obj) || isLegacyPolygon(obj);
- }
+ Status GeoParser::parseMultiPolygon(const BSONObj &obj, MultiPolygonWithCRS *out) {
+ Status status = Status::OK();
+ status = parseGeoJSONCRS(obj, &out->crs);
+ if (!status.isOK()) return status;
- bool GeoParser::crsIsOK(const BSONObj &obj) {
- if (!obj.hasField("crs")) { return true; }
-
- if (!obj["crs"].isABSONObj()) { return false; }
+ BSONElement coordElt = obj.getFieldDotted(GEOJSON_COORDINATES);
+ if (Array != coordElt.type()) return BAD_VALUE_STATUS;
- BSONObj crsObj = obj["crs"].embeddedObject();
- if (!crsObj.hasField("type")) { return false; }
- if (String != crsObj["type"].type()) { return false; }
- if ("name" != crsObj["type"].String()) { return false; }
- if (!crsObj.hasField("properties")) { return false; }
- if (!crsObj["properties"].isABSONObj()) { return false; }
+ vector<S2Polygon*>& polygons = out->polygons.mutableVector();
+ polygons.clear();
- BSONObj propertiesObj = crsObj["properties"].embeddedObject();
- if (!propertiesObj.hasField("name")) { return false; }
- if (String != propertiesObj["name"].type()) { return false; }
- const string& name = propertiesObj["name"].String();
+ BSONObjIterator it(coordElt.Obj());
+ // Iterate array
+ while (it.more()) {
+ polygons.push_back(new S2Polygon());
+ status = parseGeoJSONPolygonCoordinates(it.next(), polygons.back());
+ if (!status.isOK()) return status;
+ }
+ if (0 == polygons.size()) { return BAD_VALUE_STATUS; }
- // 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) ||
- ("urn:mongodb:strictwindingcrs:EPSG:4326" == name);
+ return Status::OK();
}
- bool GeoParser::parseGeoJSONCRS(const BSONObj& obj, CRS* crs) {
-
- dassert(crsIsOK(obj));
+ Status GeoParser::parseLegacyCenter(const BSONObj& obj, CapWithCRS *out) {
+ BSONObjIterator objIt(obj);
- *crs = SPHERE;
+ // Center
+ BSONElement center = objIt.next();
+ Status status = parseFlatPoint(center, &out->circle.center);
+ if (!status.isOK()) return status;
- if (!obj["crs"].eoo()) {
- const string name = obj["crs"].Obj()["properties"].Obj()["name"].String();
+ // Radius
+ BSONElement radius = objIt.next();
+ // radius >= 0 and is not NaN
+ if (!radius.isNumber() || !(radius.number() >= 0)) { return BAD_VALUE_STATUS; }
- if (name == "urn:mongodb:strictwindingcrs:EPSG:4326")
- *crs = STRICT_SPHERE;
- else
- *crs = SPHERE;
- }
+ // No more
+ if (objIt.more()) return BAD_VALUE_STATUS;
- return true;
- }
-
- bool GeoParser::isCap(const BSONObj &obj) {
- return isLegacyCenter(obj) || isLegacyCenterSphere(obj);
+ out->circle.radius = radius.number();
+ out->crs = FLAT;
+ return Status::OK();
}
- bool GeoParser::parseCap(const BSONObj& obj, CapWithCRS *out) {
- if (isLegacyCenter(obj)) {
- BSONObjIterator typeIt(obj);
- BSONElement type = typeIt.next();
- BSONObjIterator objIt(type.embeddedObject());
- BSONElement center = objIt.next();
- if (!parseLegacyPoint(center.Obj(), &out->circle.center)) { return false; }
- BSONElement radius = objIt.next();
- out->circle.radius = radius.number();
- // radius >= 0 and is not NaN
- if (!(out->circle.radius >= 0))
- return false;
- out->crs = FLAT;
- } else {
- verify(isLegacyCenterSphere(obj));
- BSONObjIterator typeIt(obj);
- BSONElement type = typeIt.next();
- BSONObjIterator objIt(type.embeddedObject());
- BSONObj centerObj = objIt.next().Obj();
-
- S2Point centerPoint;
- BSONObjIterator it(centerObj);
- BSONElement x = it.next();
- BSONElement y = it.next();
- centerPoint = coordToPoint(x.Number(), y.Number());
- BSONElement radiusElt = objIt.next();
- double radius = radiusElt.number();
- // radius >= 0 and is not NaN
- if (!(radius >= 0))
- return false;
- out->cap = S2Cap::FromAxisAngle(centerPoint, S1Angle::Radians(radius));
- out->circle.radius = radius;
- out->circle.center = Point(x.Number(), y.Number());
- out->crs = SPHERE;
- }
- return true;
- }
+ Status GeoParser::parseCenterSphere(const BSONObj& obj, CapWithCRS *out) {
+ BSONObjIterator objIt(obj);
- bool GeoParser::parseGeometryCollection(const BSONObj &obj, GeometryCollection *out) {
+ // Center
+ BSONElement center = objIt.next();
+ Point p;
+ // Check the object has and only has 2 numbers.
+ Status status = parseFlatPoint(center, &p);
+ if (!status.isOK()) return status;
+ if (!isValidLngLat(p.x, p.y)) { return BAD_VALUE_STATUS; }
+ S2Point centerPoint = coordToPoint(p.x, p.y);
+
+ // Radius
+ BSONElement radiusElt = objIt.next();
+ // radius >= 0 and is not NaN
+ if (!radiusElt.isNumber() || !(radiusElt.number() >= 0)) { return BAD_VALUE_STATUS; }
+ double radius = radiusElt.number();
+
+ // No more elements
+ if (objIt.more()) return BAD_VALUE_STATUS;
+
+ out->cap = S2Cap::FromAxisAngle(centerPoint, S1Angle::Radians(radius));
+ out->circle.radius = radius;
+ out->circle.center = p;
+ out->crs = SPHERE;
+ return Status::OK();
+ }
+
+ // { "type": "GeometryCollection",
+ // "geometries": [
+ // { "type": "Point",
+ // "coordinates": [100.0, 0.0]
+ // },
+ // { "type": "LineString",
+ // "coordinates": [ [101.0, 0.0], [102.0, 1.0] ]
+ // }
+ // ]
+ // }
+ Status GeoParser::parseGeometryCollection(const BSONObj &obj, GeometryCollection *out) {
BSONElement coordElt = obj.getFieldDotted(GEOJSON_GEOMETRIES);
+ if (Array != coordElt.type()) { return BAD_VALUE_STATUS; }
+
const vector<BSONElement>& geometries = coordElt.Array();
+ if (0 == geometries.size()) { return BAD_VALUE_STATUS; }
for (size_t i = 0; i < geometries.size(); ++i) {
+ if (Object != geometries[i].type()) return BAD_VALUE_STATUS;
+
const BSONObj& geoObj = geometries[i].Obj();
+ GeoJSONType type = parseGeoJSONType(geoObj);
+
+ if (GEOJSON_UNKNOWN == type || GEOJSON_GEOMETRY_COLLECTION == type) return BAD_VALUE_STATUS;
- if (isGeoJSONPoint(geoObj)) {
- PointWithCRS point;
- if (!parsePoint(geoObj, &point)) { return false; }
- out->points.push_back(point);
- } else if (isLine(geoObj)) {
+ Status status = Status::OK();
+ if (GEOJSON_POINT == type) {
+ out->points.resize(out->points.size() + 1);
+ status = parseGeoJSONPoint(geoObj, &out->points.back());
+ } else if (GEOJSON_LINESTRING == type) {
out->lines.mutableVector().push_back(new LineWithCRS());
- if (!parseLine(geoObj, out->lines.vector().back())) { return false; }
- } else if (isGeoJSONPolygon(geoObj)) {
+ status = parseGeoJSONLine(geoObj, out->lines.vector().back());
+ } else if (GEOJSON_POLYGON == type) {
out->polygons.mutableVector().push_back(new PolygonWithCRS());
- if (!parsePolygon(geoObj, out->polygons.vector().back())) { return false; }
- } else if (isMultiPoint(geoObj)) {
+ status = parseGeoJSONPolygon(geoObj, out->polygons.vector().back());
+ } else if (GEOJSON_MULTI_POINT == type) {
out->multiPoints.mutableVector().push_back(new MultiPointWithCRS());
- if (!parseMultiPoint(geoObj, out->multiPoints.mutableVector().back())) {
- return false;
- }
- } else if (isMultiPolygon(geoObj)) {
+ status = parseMultiPoint(geoObj, out->multiPoints.mutableVector().back());
+ } else if (GEOJSON_MULTI_LINESTRING == type) {
+ out->multiLines.mutableVector().push_back(new MultiLineWithCRS());
+ status = parseMultiLine(geoObj, out->multiLines.mutableVector().back());
+ } else if (GEOJSON_MULTI_POLYGON == type) {
out->multiPolygons.mutableVector().push_back(new MultiPolygonWithCRS());
- if (!parseMultiPolygon(geoObj, out->multiPolygons.mutableVector().back())) {
- return false;
- }
+ status = parseMultiPolygon(geoObj, out->multiPolygons.mutableVector().back());
} else {
- verify(isMultiLine(geoObj));
- out->multiLines.mutableVector().push_back(new MultiLineWithCRS());
- if (!parseMultiLine(geoObj, out->multiLines.mutableVector().back())) {
- return false;
- }
+ // Should not reach here.
+ invariant(false);
}
+
+ // Check parsing result.
+ if (!status.isOK()) return status;
}
- return true;
+ return Status::OK();
}
bool GeoParser::parsePointWithMaxDistance(const BSONObj& obj, PointWithCRS* out, double* maxOut) {
@@ -767,4 +613,43 @@ namespace mongo {
return true;
}
+ GeoParser::GeoSpecifier GeoParser::parseGeoSpecifier(const BSONElement& type) {
+ if (!type.isABSONObj()) { return GeoParser::UNKNOWN; }
+ const char* fieldName = type.fieldName();
+ if (mongoutils::str::equals(fieldName, "$box")) {
+ return GeoParser::BOX;
+ } else if (mongoutils::str::equals(fieldName, "$center")) {
+ return GeoParser::CENTER;
+ } else if (mongoutils::str::equals(fieldName, "$polygon")) {
+ return GeoParser::POLYGON;
+ } else if (mongoutils::str::equals(fieldName, "$centerSphere")) {
+ return GeoParser::CENTER_SPHERE;
+ } else if (mongoutils::str::equals(fieldName, "$geometry")) {
+ return GeoParser::GEOMETRY;
+ }
+ return GeoParser::UNKNOWN;
+ }
+
+ GeoParser::GeoJSONType GeoParser::parseGeoJSONType(const BSONObj& obj) {
+ BSONElement type = obj.getFieldDotted(GEOJSON_TYPE);
+ if (String != type.type()) { return GeoParser::GEOJSON_UNKNOWN; }
+ const string& typeString = type.String();
+ if (GEOJSON_TYPE_POINT == typeString) {
+ return GeoParser::GEOJSON_POINT;
+ } else if (GEOJSON_TYPE_LINESTRING == typeString) {
+ return GeoParser::GEOJSON_LINESTRING;
+ } else if (GEOJSON_TYPE_POLYGON == typeString) {
+ return GeoParser::GEOJSON_POLYGON;
+ } else if (GEOJSON_TYPE_MULTI_POINT == typeString) {
+ return GeoParser::GEOJSON_MULTI_POINT;
+ } else if (GEOJSON_TYPE_MULTI_LINESTRING == typeString) {
+ return GeoParser::GEOJSON_MULTI_LINESTRING;
+ } else if (GEOJSON_TYPE_MULTI_POLYGON == typeString) {
+ return GeoParser::GEOJSON_MULTI_POLYGON;
+ } else if (GEOJSON_TYPE_GEOMETRY_COLLECTION == typeString) {
+ return GeoParser::GEOJSON_GEOMETRY_COLLECTION;
+ }
+ return GeoParser::GEOJSON_UNKNOWN;
+ }
+
} // namespace mongo
diff --git a/src/mongo/db/geo/geoparser.h b/src/mongo/db/geo/geoparser.h
index d996de84664..796db6b087f 100644
--- a/src/mongo/db/geo/geoparser.h
+++ b/src/mongo/db/geo/geoparser.h
@@ -44,45 +44,53 @@ namespace mongo {
class GeoParser {
public:
- static bool isPoint(const BSONObj &obj);
- // Legacy points can contain extra data as extra fields - these are valid to index
- static bool isIndexablePoint(const BSONObj& obj);
- static bool parsePoint(const BSONObj &obj, PointWithCRS *out);
-
- static bool isLine(const BSONObj &obj);
- static bool parseLine(const BSONObj &obj, LineWithCRS *out);
-
- static bool isBox(const BSONObj &obj);
- static bool parseBox(const BSONObj &obj, BoxWithCRS *out);
-
- static bool isPolygon(const BSONObj &obj);
- static bool parsePolygon(const BSONObj &obj, PolygonWithCRS *out);
-
- // AKA $center or $centerSphere
- static bool isCap(const BSONObj &obj);
- static bool parseCap(const BSONObj &obj, CapWithCRS *out);
-
- static bool isMultiPoint(const BSONObj &obj);
- static bool parseMultiPoint(const BSONObj &obj, MultiPointWithCRS *out);
-
- static bool isMultiLine(const BSONObj &obj);
- static bool parseMultiLine(const BSONObj &obj, MultiLineWithCRS *out);
-
- static bool isMultiPolygon(const BSONObj &obj);
- static bool parseMultiPolygon(const BSONObj &obj, MultiPolygonWithCRS *out);
-
- static bool isGeometryCollection(const BSONObj &obj);
- static bool parseGeometryCollection(const BSONObj &obj, GeometryCollection *out);
+ // Geospatial specifier after $geoWithin / $geoIntersects predicates.
+ // i.e. "$box" in { $box: [[1, 2], [3, 4]] }
+ enum GeoSpecifier {
+ UNKNOWN = 0,
+ BOX, // $box
+ CENTER, // $center
+ POLYGON, // $polygon
+ CENTER_SPHERE, // $centerSphere
+ GEOMETRY // GeoJSON geometry, $geometry
+ };
+
+ // GeoJSON type defined in GeoJSON document.
+ // i.e. "Point" in { type: "Point", coordinates: [1, 2] }
+ enum GeoJSONType {
+ GEOJSON_UNKNOWN = 0,
+ GEOJSON_POINT,
+ GEOJSON_LINESTRING,
+ GEOJSON_POLYGON,
+ GEOJSON_MULTI_POINT,
+ GEOJSON_MULTI_LINESTRING,
+ GEOJSON_MULTI_POLYGON,
+ GEOJSON_GEOMETRY_COLLECTION
+ };
+
+ static GeoSpecifier parseGeoSpecifier(const BSONElement& elem);
+ static GeoJSONType parseGeoJSONType(const BSONObj& obj);
+ // Legacy points can contain extra data as extra fields - these are valid to index
+ // e.g. { x: 1, y: 1, z: 1 }
+ static Status parseLegacyPoint(const BSONElement &elem, PointWithCRS *out, bool allowAddlFields = false);
+ // Parse the BSON object after $box, $center, etc.
+ static Status parseLegacyBox(const BSONObj& obj, BoxWithCRS *out);
+ static Status parseLegacyCenter(const BSONObj& obj, CapWithCRS *out);
+ static Status parseLegacyPolygon(const BSONObj& obj, PolygonWithCRS *out);
+ static Status parseCenterSphere(const BSONObj& obj, CapWithCRS *out);
+ static Status parseGeoJSONPolygon(const BSONObj &obj, PolygonWithCRS *out);
+ static Status parseGeoJSONPoint(const BSONObj &obj, PointWithCRS *out);
+ static Status parseGeoJSONLine(const BSONObj& obj, LineWithCRS* out);
+ static Status parseMultiPoint(const BSONObj &obj, MultiPointWithCRS *out);
+ static Status parseMultiLine(const BSONObj &obj, MultiLineWithCRS *out);
+ static Status parseMultiPolygon(const BSONObj &obj, MultiPolygonWithCRS *out);
+ static Status parseGeometryCollection(const BSONObj &obj, GeometryCollection *out);
+
+ // For geo near
+ static Status parseQueryPoint(const BSONElement &elem, PointWithCRS *out);
+ static Status parseStoredPoint(const BSONElement &elem, PointWithCRS *out);
static bool parsePointWithMaxDistance(const BSONObj& obj, PointWithCRS* out, double* maxOut);
-
- // Return true if the CRS field is 1. missing, or 2. is well-formed and
- // has a datum we accept. Otherwise, return false.
- // NOTE(hk): If this is ever used anywhere but internally, consider
- // 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 e3f124d24c9..fd863447563 100644
--- a/src/mongo/db/geo/geoparser_test.cpp
+++ b/src/mongo/db/geo/geoparser_test.cpp
@@ -40,132 +40,145 @@
#include "mongo/unittest/unittest.h"
#include "mongo/util/assert_util.h"
+// Wrap a BSON object to a BSON element.
+#define BSON_ELT(bson) BSON("" << (bson)).firstElement()
+
using namespace mongo;
namespace {
- TEST(GeoParser, isValidPoint) {
- ASSERT_TRUE(GeoParser::isPoint(fromjson("{'type':'Point', 'coordinates': [40, 5]}")));
- ASSERT_TRUE(GeoParser::isPoint(
- fromjson("{'type':'Point', 'coordinates': [-40.3, -5.0]}")));
- ASSERT_FALSE(GeoParser::isPoint(fromjson("{'typo':'Point', 'coordinates': [40, -5]}")));
- ASSERT_FALSE(GeoParser::isPoint(fromjson("{'type':'Point', 'coordhats': [40, -5]}")));
- ASSERT_FALSE(GeoParser::isPoint(
- fromjson("{'type':['Point'], 'coordinates': [40, -5]}")));
- ASSERT_FALSE(GeoParser::isPoint(fromjson("{'type':'Point', 'coordinates': 40}")));
- ASSERT_FALSE(GeoParser::isPoint(
- fromjson("{'type':'Point', 'coordinates': [40, -5, 7]}")));
+ TEST(GeoParser, parseGeoSpecifier) {
+ ASSERT_EQUALS(GeoParser::parseGeoSpecifier(
+ fromjson("{$box : [[1, 2], [3, 4]]}").firstElement()),
+ GeoParser::BOX);
+ ASSERT_EQUALS(GeoParser::parseGeoSpecifier(
+ fromjson("{$center : [[0, 0], 4]}").firstElement()),
+ GeoParser::CENTER);
+ ASSERT_EQUALS(GeoParser::parseGeoSpecifier(
+ fromjson("{$centerSphere : [[0, 0], 1]}").firstElement()),
+ GeoParser::CENTER_SPHERE);
+ ASSERT_EQUALS(GeoParser::parseGeoSpecifier(
+ fromjson("{$geometry : {'type':'Point', 'coordinates': [40, 5]}}").firstElement()),
+ GeoParser::GEOMETRY);
+ }
+
+ TEST(GeoParser, parseGeoJSONPoint) {
+ PointWithCRS point;
+
+ ASSERT_OK(GeoParser::parseGeoJSONPoint(
+ fromjson("{'type':'Point', 'coordinates': [40, 5]}"), &point));
+ ASSERT_OK(GeoParser::parseGeoJSONPoint(
+ fromjson("{'type':'Point', 'coordinates': [-40.3, -5.0]}"), &point));
+ ASSERT_NOT_OK(GeoParser::parseGeoJSONPoint(
+ fromjson("{'type':'Point', 'coordhats': [40, -5]}"), &point));
+ ASSERT_NOT_OK(GeoParser::parseGeoJSONPoint(
+ fromjson("{'type':'Point', 'coordinates': 40}"), &point));
+ ASSERT_NOT_OK(GeoParser::parseGeoJSONPoint(
+ fromjson("{'type':'Point', 'coordinates': [40, -5, 7]}"), &point));
// Make sure lat is in range
- ASSERT_TRUE(GeoParser::isPoint(fromjson("{'type':'Point', 'coordinates': [0, 90.0]}")));
- ASSERT_TRUE(GeoParser::isPoint(fromjson("{'type':'Point', 'coordinates': [0, -90.0]}")));
- ASSERT_TRUE(GeoParser::isPoint(fromjson("{'type':'Point', 'coordinates': [180, 90.0]}")));
- ASSERT_TRUE(GeoParser::isPoint(fromjson("{'type':'Point', 'coordinates': [-180, -90.0]}")));
- ASSERT_FALSE(GeoParser::isPoint(fromjson("{'type':'Point', 'coordinates': [180.01, 90.0]}")));
- ASSERT_FALSE(GeoParser::isPoint(fromjson("{'type':'Point', 'coordinates': [-180.01, -90.0]}")));
- ASSERT_FALSE(GeoParser::isPoint(fromjson("{'type':'Point', 'coordinates': [0, 90.1]}")));
- ASSERT_FALSE(GeoParser::isPoint(fromjson("{'type':'Point', 'coordinates': [0, -90.1]}")));
+ ASSERT_OK(GeoParser::parseGeoJSONPoint(
+ fromjson("{'type':'Point', 'coordinates': [0, 90.0]}"), &point));
+ ASSERT_OK(GeoParser::parseGeoJSONPoint(
+ fromjson("{'type':'Point', 'coordinates': [0, -90.0]}"), &point));
+ ASSERT_OK(GeoParser::parseGeoJSONPoint(
+ fromjson("{'type':'Point', 'coordinates': [180, 90.0]}"), &point));
+ ASSERT_OK(GeoParser::parseGeoJSONPoint(
+ fromjson("{'type':'Point', 'coordinates': [-180, -90.0]}"), &point));
+ ASSERT_NOT_OK(GeoParser::parseGeoJSONPoint(
+ fromjson("{'type':'Point', 'coordinates': [180.01, 90.0]}"), &point));
+ ASSERT_NOT_OK(GeoParser::parseGeoJSONPoint(
+ fromjson("{'type':'Point', 'coordinates': [-180.01, -90.0]}"), &point));
+ ASSERT_NOT_OK(GeoParser::parseGeoJSONPoint(
+ fromjson("{'type':'Point', 'coordinates': [0, 90.1]}"), &point));
+ ASSERT_NOT_OK(GeoParser::parseGeoJSONPoint(
+ fromjson("{'type':'Point', 'coordinates': [0, -90.1]}"), &point));
}
- TEST(GeoParser, isValidLineString) {
- ASSERT_TRUE(GeoParser::isLine(
- fromjson("{'type':'LineString', 'coordinates':[[1,2], [3,4]]}")));
- ASSERT_TRUE(GeoParser::isLine(
- fromjson("{'type':'LineString', 'coordinates':[[0,-90], [0,90]]}")));
- ASSERT_TRUE(GeoParser::isLine(
- fromjson("{'type':'LineString', 'coordinates':[[180,-90], [-180,90]]}")));
- ASSERT_FALSE(GeoParser::isLine(
- fromjson("{'type':'LineString', 'coordinates':[[180.1,-90], [-180.1,90]]}")));
- ASSERT_FALSE(GeoParser::isLine(
- fromjson("{'type':'LineString', 'coordinates':[[0,-91], [0,90]]}")));
- ASSERT_FALSE(GeoParser::isLine(
- fromjson("{'type':'LineString', 'coordinates':[[0,-90], [0,91]]}")));
- ASSERT_TRUE(GeoParser::isLine(
- fromjson("{'type':'LineString', 'coordinates':[[1,2], [3,4], [5,6]]}")));
- ASSERT_FALSE(GeoParser::isLine(
- fromjson("{'type':'LineString', 'coordinates':[[1,2]]}")));
- ASSERT_FALSE(GeoParser::isLine(
- fromjson("{'type':'LineString', 'coordinates':[['chicken','little']]}")));
- ASSERT_FALSE(GeoParser::isLine(
- fromjson("{'type':'LineString', 'coordinates':[1,2, 3, 4]}")));
- ASSERT_FALSE(GeoParser::isLine(
- fromjson("{'type':'LineString', 'coordinates':[[1,2, 3], [3,4, 5], [5,6]]}")));
+ TEST(GeoParser, parseGeoJSONLine) {
+ LineWithCRS polyline;
+
+ ASSERT_OK(GeoParser::parseGeoJSONLine(
+ fromjson("{'type':'LineString', 'coordinates':[[1,2], [3,4]]}"), &polyline));
+ ASSERT_OK(GeoParser::parseGeoJSONLine(
+ fromjson("{'type':'LineString', 'coordinates':[[0,-90], [0,90]]}"), &polyline));
+ ASSERT_OK(GeoParser::parseGeoJSONLine(
+ fromjson("{'type':'LineString', 'coordinates':[[180,-90], [-180,90]]}"), &polyline));
+ ASSERT_NOT_OK(GeoParser::parseGeoJSONLine(
+ fromjson("{'type':'LineString', 'coordinates':[[180.1,-90], [-180.1,90]]}"), &polyline));
+ ASSERT_NOT_OK(GeoParser::parseGeoJSONLine(
+ fromjson("{'type':'LineString', 'coordinates':[[0,-91], [0,90]]}"), &polyline));
+ ASSERT_NOT_OK(GeoParser::parseGeoJSONLine(
+ fromjson("{'type':'LineString', 'coordinates':[[0,-90], [0,91]]}"), &polyline));
+ ASSERT_OK(GeoParser::parseGeoJSONLine(
+ fromjson("{'type':'LineString', 'coordinates':[[1,2], [3,4], [5,6]]}"), &polyline));
+ ASSERT_NOT_OK(GeoParser::parseGeoJSONLine(
+ fromjson("{'type':'LineString', 'coordinates':[[1,2]]}"), &polyline));
+ ASSERT_NOT_OK(GeoParser::parseGeoJSONLine(
+ fromjson("{'type':'LineString', 'coordinates':[['chicken','little']]}"), &polyline));
+ ASSERT_NOT_OK(GeoParser::parseGeoJSONLine(
+ fromjson("{'type':'LineString', 'coordinates':[1,2, 3, 4]}"), &polyline));
+ ASSERT_NOT_OK(GeoParser::parseGeoJSONLine(
+ fromjson("{'type':'LineString', 'coordinates':[[1,2, 3], [3,4, 5], [5,6]]}"), &polyline));
}
- TEST(GeoParser, isValidPolygon) {
- ASSERT_TRUE(GeoParser::isPolygon(
- fromjson("{'type':'Polygon', 'coordinates':[ [[0,0],[5,0],[5,5],[0,5],[0,0]] ]}")));
+ TEST(GeoParser, parseGeoJSONPolygon) {
+ PolygonWithCRS polygon;
+
+ ASSERT_OK(GeoParser::parseGeoJSONPolygon(
+ fromjson("{'type':'Polygon', 'coordinates':[ [[0,0],[5,0],[5,5],[0,5],[0,0]] ]}"), &polygon));
// No out of bounds points
- ASSERT_FALSE(GeoParser::isPolygon(
- fromjson("{'type':'Polygon', 'coordinates':[ [[0,0],[5,0],[5,91],[0,5],[0,0]] ]}")));
- ASSERT_TRUE(GeoParser::isPolygon(
- fromjson("{'type':'Polygon', 'coordinates':[ [[0,0],[180,0],[5,5],[0,5],[0,0]] ]}")));
- ASSERT_FALSE(GeoParser::isPolygon(
- fromjson("{'type':'Polygon', 'coordinates':[ [[0,0],[181,0],[5,5],[0,5],[0,0]] ]}")));
+ ASSERT_NOT_OK(GeoParser::parseGeoJSONPolygon(
+ fromjson("{'type':'Polygon', 'coordinates':[ [[0,0],[5,0],[5,91],[0,5],[0,0]] ]}"), &polygon));
+ ASSERT_OK(GeoParser::parseGeoJSONPolygon(
+ fromjson("{'type':'Polygon', 'coordinates':[ [[0,0],[180,0],[5,5],[0,5],[0,0]] ]}"), &polygon));
+ ASSERT_NOT_OK(GeoParser::parseGeoJSONPolygon(
+ fromjson("{'type':'Polygon', 'coordinates':[ [[0,0],[181,0],[5,5],[0,5],[0,0]] ]}"), &polygon));
// And one with a hole.
- ASSERT_TRUE(GeoParser::isPolygon(
+ ASSERT_OK(GeoParser::parseGeoJSONPolygon(
fromjson("{'type':'Polygon', 'coordinates':[ [[0,0],[5,0],[5,5],[0,5],[0,0]],"
- " [[1,1],[4,1],[4,4],[1,4],[1,1]] ]}")));
+ " [[1,1],[4,1],[4,4],[1,4],[1,1]] ]}"), &polygon));
// Latitudes must be OK
- ASSERT_FALSE(GeoParser::isPolygon(
+ ASSERT_NOT_OK(GeoParser::parseGeoJSONPolygon(
fromjson("{'type':'Polygon', 'coordinates':[ [[0,0],[5,0],[5,91],[0,91],[0,0]],"
- " [[1,1],[4,1],[4,4],[1,4],[1,1]] ]}")));
+ " [[1,1],[4,1],[4,4],[1,4],[1,1]] ]}"), &polygon));
// First point must be the same as the last.
- ASSERT_FALSE(GeoParser::isPolygon(
- fromjson("{'type':'Polygon', 'coordinates':[ [[1,2],[3,4],[5,6]] ]}")));
- }
-
- TEST(GeoParser, parsePoint) {
- PointWithCRS point;
- GeoParser::parsePoint(fromjson("{'type':'Point', 'coordinates': [40, 5]}"), &point);
- GeoParser::parsePoint(fromjson("{'type':'Point', 'coordinates': [-4.3, -5.0]}"), &point);
- }
+ ASSERT_NOT_OK(GeoParser::parseGeoJSONPolygon(
+ fromjson("{'type':'Polygon', 'coordinates':[ [[1,2],[3,4],[5,6]] ]}"), &polygon));
- TEST(GeoParser, parseLine) {
- LineWithCRS polyline;
- GeoParser::parseLine(
- fromjson("{'type':'LineString', 'coordinates':[[1,2],[3,4]]}"),
- &polyline);
- GeoParser::parseLine(
- fromjson("{'type':'LineString', 'coordinates':[[1,2], [3,4], [5,6]]}"),
- &polyline);
- GeoParser::parseLine(
- fromjson("{'type':'LineString', 'coordinates':[[1,2], [3,4], [5,6]]}"),
- &polyline);
- }
- TEST(GeoParser, parsePolygon) {
+ // Test functionality of polygon
PointWithCRS point;
- GeoParser::parsePoint(fromjson("{'type':'Point', 'coordinates': [2, 2]}"),
- &point);
+ ASSERT_OK(GeoParser::parseGeoJSONPoint(
+ fromjson("{'type':'Point', 'coordinates': [2, 2]}"), &point));
PolygonWithCRS polygonA;
- GeoParser::parsePolygon(
+ ASSERT_OK(GeoParser::parseGeoJSONPolygon(
fromjson("{'type':'Polygon', 'coordinates':[ [[0,0],[5,0],[5,5],[0,5],[0,0]] ]}"),
- &polygonA);
+ &polygonA));
ASSERT_TRUE(polygonA.s2Polygon->Contains(point.point));
PolygonWithCRS polygonB;
- GeoParser::parsePolygon(
+ ASSERT_OK(GeoParser::parseGeoJSONPolygon(
fromjson("{'type':'Polygon', 'coordinates':[ [[0,0],[5,0],[5,5],[0,5],[0,0]],"
" [[1,1],[1,4],[4,4],[4,1],[1,1]] ]}"),
- &polygonB);
+ &polygonB));
// We removed this in the hole.
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).
PolygonWithCRS polygonC;
- GeoParser::parsePolygon(
+ ASSERT_OK(GeoParser::parseGeoJSONPolygon(
fromjson("{'type':'Polygon', 'coordinates':[ [[0,0],[0,5],[5,5],[5,0],[0,0]] ]}"),
- &polygonC);
+ &polygonC));
ASSERT_TRUE(polygonC.s2Polygon->Contains(point.point));
PolygonWithCRS polygonD;
- GeoParser::parsePolygon(
+ ASSERT_OK(GeoParser::parseGeoJSONPolygon(
fromjson("{'type':'Polygon', 'coordinates':[ [[0,0],[0,5],[5,5],[5,0],[0,0]],"
" [[1,1],[1,4],[4,4],[4,1],[1,1]] ]}"),
- &polygonD);
+ &polygonD));
// Also removed in the loop.
ASSERT_FALSE(polygonD.s2Polygon->Contains(point.point));
@@ -175,23 +188,13 @@ namespace {
// Polygon with not enough points, because some are duplicated
PolygonWithCRS polygonBad;
- ASSERT_FALSE(GeoParser::parsePolygon(
+ ASSERT_NOT_OK(GeoParser::parseGeoJSONPolygon(
fromjson("{'type':'Polygon', 'coordinates':[[ [0,0], [0,0], [5,5], [5,5], [0,0] ]]}"),
&polygonBad));
}
- TEST(GeoParser, legacyPoint) {
- PointWithCRS point;
- ASSERT(GeoParser::isPoint(BSON_ARRAY(0 << 1)));
- ASSERT_FALSE(GeoParser::isPoint(BSON_ARRAY(0)));
- ASSERT_FALSE(GeoParser::isPoint(BSON_ARRAY(0 << 1 << 2)));
- ASSERT(GeoParser::isPoint(fromjson("{x: 50, y:40}")));
- ASSERT_FALSE(GeoParser::isPoint(fromjson("{x: '50', y:40}")));
- ASSERT_FALSE(GeoParser::isPoint(fromjson("{x: 5, y:40, z:50}")));
- ASSERT_FALSE(GeoParser::isPoint(fromjson("{x: 5}")));
- }
- TEST(GeoParser, verifyCRS) {
+ TEST(GeoParser, parseGeoJSONCRS) {
string goodCRS1 = "crs:{ type: 'name', properties:{name:'EPSG:4326'}}";
string goodCRS2 = "crs:{ type: 'name', properties:{name:'urn:ogc:def:crs:OGC:1.3:CRS84'}}";
string badCRS1 = "crs:{ type: 'name', properties:{name:'EPSG:2000'}}";
@@ -199,145 +202,136 @@ namespace {
BSONObj point1 = fromjson("{'type':'Point', 'coordinates': [40, 5], " + goodCRS1 + "}");
BSONObj point2 = fromjson("{'type':'Point', 'coordinates': [40, 5], " + goodCRS2 + "}");
- ASSERT(GeoParser::isPoint(point1));
- ASSERT(GeoParser::crsIsOK(point1));
- ASSERT(GeoParser::isPoint(point2));
- ASSERT(GeoParser::crsIsOK(point2));
+ PointWithCRS point;
+ ASSERT_OK(GeoParser::parseGeoJSONPoint(point1, &point));
+ ASSERT_OK(GeoParser::parseGeoJSONPoint(point2, &point));
BSONObj point3 = fromjson("{'type':'Point', 'coordinates': [40, 5], " + badCRS1 + "}");
BSONObj point4 = fromjson("{'type':'Point', 'coordinates': [40, 5], " + badCRS2 + "}");
- ASSERT_FALSE(GeoParser::isPoint(point3));
- ASSERT_FALSE(GeoParser::crsIsOK(point3));
- ASSERT_FALSE(GeoParser::isPoint(point4));
- ASSERT_FALSE(GeoParser::crsIsOK(point4));
+ ASSERT_NOT_OK(GeoParser::parseGeoJSONPoint(point3, &point));
+ ASSERT_NOT_OK(GeoParser::parseGeoJSONPoint(point4, &point));
+ PolygonWithCRS polygon;
BSONObj polygon1 = fromjson("{'type':'Polygon', 'coordinates':[ [[0,0],[5,0],[5,5],[0,5],[0,0]],"
" [[1,1],[1,4],[4,4],[4,1],[1,1]] ]," + goodCRS1 + "}");
- ASSERT(GeoParser::isPolygon(polygon1));
- ASSERT(GeoParser::crsIsOK(polygon1));
+ ASSERT_OK(GeoParser::parseGeoJSONPolygon(polygon1, &polygon));
BSONObj polygon2 = fromjson("{'type':'Polygon', 'coordinates':[ [[0,0],[5,0],[5,5],[0,5],[0,0]],"
" [[1,1],[1,4],[4,4],[4,1],[1,1]] ]," + badCRS2 + "}");
- ASSERT_FALSE(GeoParser::isPolygon(polygon2));
- ASSERT_FALSE(GeoParser::crsIsOK(polygon2));
+ ASSERT_NOT_OK(GeoParser::parseGeoJSONPolygon(polygon2, &polygon));
+ LineWithCRS line;
BSONObj line1 = fromjson("{'type':'LineString', 'coordinates':[[1,2], [3,4], [5,6]]," + goodCRS2 + "}");
- ASSERT(GeoParser::isLine(line1));
- ASSERT(GeoParser::crsIsOK(line1));
+ ASSERT_OK(GeoParser::parseGeoJSONLine(line1, &line));
BSONObj line2 = fromjson("{'type':'LineString', 'coordinates':[[1,2], [3,4], [5,6]]," + badCRS1 + "}");
- ASSERT_FALSE(GeoParser::isLine(line2));
- ASSERT_FALSE(GeoParser::crsIsOK(line2));
+ ASSERT_NOT_OK(GeoParser::parseGeoJSONLine(line2, &line));
+ }
+
+ TEST(GeoParser, parseLegacyPoint) {
+ PointWithCRS point;
+ ASSERT_OK(GeoParser::parseLegacyPoint(BSON_ELT(BSON_ARRAY(0 << 1)), &point));
+ ASSERT_NOT_OK(GeoParser::parseLegacyPoint(BSON_ELT(BSON_ARRAY(0)), &point));
+ ASSERT_NOT_OK(GeoParser::parseLegacyPoint(BSON_ELT(BSON_ARRAY(0 << 1 << 2)), &point));
+ ASSERT_OK(GeoParser::parseLegacyPoint(BSON_ELT(fromjson("{x: 50, y:40}")), &point));
+ ASSERT_NOT_OK(GeoParser::parseLegacyPoint(BSON_ELT(fromjson("{x: '50', y:40}")), &point));
+ ASSERT_NOT_OK(GeoParser::parseLegacyPoint(BSON_ELT(fromjson("{x: 5, y:40, z:50}")), &point));
+ ASSERT_NOT_OK(GeoParser::parseLegacyPoint(BSON_ELT(fromjson("{x: 5}")), &point));
}
- TEST(GeoParser, legacyPolygon) {
+ TEST(GeoParser, parseLegacyPolygon) {
PolygonWithCRS polygon;
- GeoParser::parsePolygon(fromjson("{$polygon: [[10,20],[10,40],[30,40],[30,20]]}"),
- &polygon);
- ASSERT(polygon.crs == FLAT);
- GeoParser::parsePolygon(fromjson("{$polygon: [[10,20], [10,40], [30,40]]}"), &polygon);
+ // Parse the object after field name "$polygon"
+ ASSERT_OK(GeoParser::parseLegacyPolygon(
+ fromjson("[[10,20],[10,40],[30,40],[30,20]]"), &polygon));
ASSERT(polygon.crs == FLAT);
- ASSERT_FALSE(GeoParser::isPolygon(fromjson("{$polygon: [[10,20],[10,40]]}")));
- ASSERT_FALSE(GeoParser::isPolygon(fromjson("{$polygon: [['10',20],[10,40],[30,40],[30,20]]}")));
- ASSERT_FALSE(GeoParser::isPolygon(fromjson("{$polygon: [[10,20,30],[10,40],[30,40],[30,20]]}")));
- ASSERT(GeoParser::isPolygon(fromjson("{$polygon: {a:{x:40,y:5},b:{x:40,y:6},c:{x:41,y:6},d:{x:41,y:5}}}")));
- }
+ ASSERT_OK(GeoParser::parseLegacyPolygon(
+ fromjson("[[10,20], [10,40], [30,40]]"), &polygon));
+ ASSERT(polygon.crs == FLAT);
- TEST(GeoParser, multiPoint) {
- ASSERT(GeoParser::isMultiPoint(
- fromjson("{'type':'MultiPoint','coordinates':[[1,2],[3,4]]}")));
- ASSERT(GeoParser::isMultiPoint(
- fromjson("{'type':'MultiPoint','coordinates':[[3,4]]}")));
- ASSERT(GeoParser::isMultiPoint(
- fromjson("{'type':'MultiPoint','coordinates':[[1,2],[3,4],[5,6],[7,8]]}")));
-
- ASSERT_FALSE(GeoParser::isMultiPoint(
- fromjson("{'type':'MultiPoint','coordinates':[]}")));
- ASSERT_FALSE(GeoParser::isMultiPoint(
- fromjson("{'type':'MultiPoint','coordinates':[[181,2],[3,4]]}")));
- ASSERT_FALSE(GeoParser::isMultiPoint(
- fromjson("{'type':'MultiPoint','coordinates':[[1,-91],[3,4]]}")));
- ASSERT_FALSE(GeoParser::isMultiPoint(
- fromjson("{'type':'MultiPoint','coordinates':[[181,2],[3,'chicken']]}")));
+ ASSERT_NOT_OK(GeoParser::parseLegacyPolygon(
+ fromjson("[[10,20],[10,40]]"), &polygon));
+ ASSERT_NOT_OK(GeoParser::parseLegacyPolygon(
+ fromjson("[['10',20],[10,40],[30,40],[30,20]]"), &polygon));
+ ASSERT_NOT_OK(GeoParser::parseLegacyPolygon(
+ fromjson("[[10,20,30],[10,40],[30,40],[30,20]]"), &polygon));
+ ASSERT_OK(GeoParser::parseLegacyPolygon(
+ fromjson("{a:{x:40,y:5},b:{x:40,y:6},c:{x:41,y:6},d:{x:41,y:5}}"), &polygon));
}
TEST(GeoParser, parseMultiPoint) {
mongo::MultiPointWithCRS mp;
- GeoParser::parseMultiPoint(fromjson("{'type':'MultiPoint','coordinates':[[1,2],[3,4]]}"),
- &mp);
- GeoParser::parseMultiPoint(fromjson("{'type':'MultiPoint','coordinates':[[3,4]]}"),
- &mp);
- GeoParser::parseMultiPoint(
- fromjson("{'type':'MultiPoint','coordinates':[[1,2],[3,4],[5,6],[7,8]]}"), &mp);
+
+ ASSERT_OK(GeoParser::parseMultiPoint(
+ fromjson("{'type':'MultiPoint','coordinates':[[1,2],[3,4]]}"), &mp));
+ ASSERT_EQUALS(mp.points.size(), (size_t)2);
+
+ ASSERT_OK(GeoParser::parseMultiPoint(
+ fromjson("{'type':'MultiPoint','coordinates':[[3,4]]}"), &mp));
+ ASSERT_EQUALS(mp.points.size(), (size_t)1);
+
+ ASSERT_OK(GeoParser::parseMultiPoint(
+ fromjson("{'type':'MultiPoint','coordinates':[[1,2],[3,4],[5,6],[7,8]]}"), &mp));
+ ASSERT_EQUALS(mp.points.size(), (size_t)4);
+
+ ASSERT_NOT_OK(GeoParser::parseMultiPoint(
+ fromjson("{'type':'MultiPoint','coordinates':[]}"), &mp));
+ ASSERT_NOT_OK(GeoParser::parseMultiPoint(
+ fromjson("{'type':'MultiPoint','coordinates':[[181,2],[3,4]]}"), &mp));
+ ASSERT_NOT_OK(GeoParser::parseMultiPoint(
+ fromjson("{'type':'MultiPoint','coordinates':[[1,-91],[3,4]]}"), &mp));
+ ASSERT_NOT_OK(GeoParser::parseMultiPoint(
+ fromjson("{'type':'MultiPoint','coordinates':[[181,2],[3,'chicken']]}"), &mp));
}
- TEST(GeoParser, multiLineString) {
- ASSERT(GeoParser::isMultiLine(
+ TEST(GeoParser, parseMultiLine) {
+ mongo::MultiLineWithCRS ml;
+
+ ASSERT_OK(GeoParser::parseMultiLine(
fromjson("{'type':'MultiLineString','coordinates':[ [[1,1],[2,2],[3,3]],"
- "[[4,5],[6,7]]]}")));
- ASSERT(GeoParser::isMultiLine(
+ "[[4,5],[6,7]]]}"), &ml));
+ ASSERT_EQUALS(ml.lines.size(), (size_t)2);
+
+ ASSERT_OK(GeoParser::parseMultiLine(
fromjson("{'type':'MultiLineString','coordinates':[ [[1,1],[2,2]],"
- "[[4,5],[6,7]]]}")));
- ASSERT(GeoParser::isMultiLine(
- fromjson("{'type':'MultiLineString','coordinates':[ [[1,1],[2,2]]]}")));
+ "[[4,5],[6,7]]]}"), &ml));
+ ASSERT_EQUALS(ml.lines.size(), (size_t)2);
+
+ ASSERT_OK(GeoParser::parseMultiLine(
+ fromjson("{'type':'MultiLineString','coordinates':[ [[1,1],[2,2]]]}"), &ml));
+ ASSERT_EQUALS(ml.lines.size(), (size_t)1);
- ASSERT(GeoParser::isMultiLine(
+ ASSERT_OK(GeoParser::parseMultiLine(
fromjson("{'type':'MultiLineString','coordinates':[ [[1,1],[2,2]],"
- "[[2,2],[1,1]]]}")));
- ASSERT_FALSE(GeoParser::isMultiLine(
- fromjson("{'type':'MultiLineString','coordinates':[ [[1,1]]]}")));
- ASSERT_FALSE(GeoParser::isMultiLine(
- fromjson("{'type':'MultiLineString','coordinates':[ [[1,1]],[[1,2],[3,4]]]}")));
- ASSERT_FALSE(GeoParser::isMultiLine(
- fromjson("{'type':'MultiLineString','coordinates':[ [[181,1],[2,2]]]}")));
- ASSERT_FALSE(GeoParser::isMultiLine(
- fromjson("{'type':'MultiLineString','coordinates':[ [[181,1],[2,-91]]]}")));
+ "[[2,2],[1,1]]]}"), &ml));
+ ASSERT_EQUALS(ml.lines.size(), (size_t)2);
+
+ ASSERT_NOT_OK(GeoParser::parseMultiLine(
+ fromjson("{'type':'MultiLineString','coordinates':[ [[1,1]]]}"), &ml));
+ ASSERT_NOT_OK(GeoParser::parseMultiLine(
+ fromjson("{'type':'MultiLineString','coordinates':[ [[1,1]],[[1,2],[3,4]]]}"), &ml));
+ ASSERT_NOT_OK(GeoParser::parseMultiLine(
+ fromjson("{'type':'MultiLineString','coordinates':[ [[181,1],[2,2]]]}"), &ml));
+ ASSERT_NOT_OK(GeoParser::parseMultiLine(
+ fromjson("{'type':'MultiLineString','coordinates':[ [[181,1],[2,-91]]]}"), &ml));
}
- TEST(GeoParser, parseMultiLine) {
- mongo::MultiLineWithCRS mls;
-
- GeoParser::parseMultiLine(
- 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]]]}"),
- &mls);
-
- GeoParser::parseMultiLine(
- fromjson("{'type':'MultiLine','coordinates':[ [[1,1],[2,2]]]}"),
- &mls);
-
- GeoParser::parseMultiLine(
- fromjson("{'type':'MultiLine','coordinates':[ [[1,1],[2,2]],"
- "[[2,2],[1,1]]]}"),
- &mls);
- }
+ TEST(GeoParser, parseMultiPolygon) {
+ mongo::MultiPolygonWithCRS mp;
- TEST(GeoParser, multiPolygon) {
- ASSERT(GeoParser::isMultiPolygon(
+ ASSERT_OK(GeoParser::parseMultiPolygon(
fromjson("{'type':'MultiPolygon','coordinates':["
"[[[102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0]]],"
"[[[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]],"
"[[100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2]]]"
- "]}")));
- ASSERT(GeoParser::isMultiPolygon(
- fromjson("{'type':'MultiPolygon','coordinates':["
- "[[[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]],"
- "[[100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2]]]"
- "]}")));
- }
+ "]}"), &mp));
+ ASSERT_EQUALS(mp.polygons.size(), (size_t)2);
- TEST(GeoParser, parseMultiPolygon) {
- mongo::MultiPolygonWithCRS mp;
- GeoParser::parseMultiPolygon(
+ ASSERT_OK(GeoParser::parseMultiPolygon(
fromjson("{'type':'MultiPolygon','coordinates':["
- "[[[102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0]]],"
"[[[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]],"
"[[100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2]]]"
- "]}"), &mp);
+ "]}"), &mp));
+ ASSERT_EQUALS(mp.polygons.size(), (size_t)1);
}
TEST(GeoParser, parseGeometryCollection) {
@@ -348,8 +342,7 @@ namespace {
"{ 'type': 'Point','coordinates': [100.0,0.0]},"
"{ 'type': 'LineString', 'coordinates': [ [101.0, 0.0], [102.0, 1.0] ]}"
"]}");
- ASSERT(GeoParser::isGeometryCollection(obj));
- GeoParser::parseGeometryCollection(obj, &gc);
+ ASSERT_OK(GeoParser::parseGeometryCollection(obj, &gc));
ASSERT_FALSE(gc.supportsContains());
}
@@ -363,11 +356,11 @@ namespace {
"]}"
"]}");
- ASSERT(GeoParser::isGeometryCollection(obj));
mongo::GeometryCollection gc;
- GeoParser::parseGeometryCollection(obj, &gc);
+ ASSERT_OK(GeoParser::parseGeometryCollection(obj, &gc));
ASSERT_TRUE(gc.supportsContains());
}
+
{
BSONObj obj = fromjson(
"{ 'type': 'GeometryCollection', 'geometries': ["
@@ -378,8 +371,8 @@ namespace {
"[[100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2]]]"
"]}"
"]}");
-
- ASSERT_FALSE(GeoParser::isGeometryCollection(obj));
+ mongo::GeometryCollection gc;
+ ASSERT_NOT_OK(GeoParser::parseGeometryCollection(obj, &gc));
}
{
@@ -393,9 +386,8 @@ namespace {
"]}"
"]}");
- ASSERT(GeoParser::isGeometryCollection(obj));
mongo::GeometryCollection gc;
- GeoParser::parseGeometryCollection(obj, &gc);
+ ASSERT_OK(GeoParser::parseGeometryCollection(obj, &gc));
ASSERT_TRUE(gc.supportsContains());
}
}
diff --git a/src/mongo/db/geo/r2_region_coverer_test.cpp b/src/mongo/db/geo/r2_region_coverer_test.cpp
index 34c91d13634..6a812364a6b 100644
--- a/src/mongo/db/geo/r2_region_coverer_test.cpp
+++ b/src/mongo/db/geo/r2_region_coverer_test.cpp
@@ -242,11 +242,11 @@ namespace {
// Format: { $center : [ [-74, 40.74], 10 ] }
GeometryContainer* container = new GeometryContainer();
- container->parseFrom(BSON("$center"
+ container->parseFromQuery(BSON("$center"
<< BSON_ARRAY(
BSON_ARRAY(randDouble(radius, MAXBOUND - radius)
<< randDouble(radius, MAXBOUND - radius))
- << radius)));
+ << radius)).firstElement());
return container;
}
diff --git a/src/mongo/db/index/expression_keys_private.cpp b/src/mongo/db/index/expression_keys_private.cpp
index b5e14d9ce30..8c41bc21c4f 100644
--- a/src/mongo/db/index/expression_keys_private.cpp
+++ b/src/mongo/db/index/expression_keys_private.cpp
@@ -83,14 +83,14 @@ namespace {
}
- bool S2GetKeysForObject(const BSONObj& obj,
+ bool S2GetKeysForObject(const BSONElement& element,
const S2IndexingParams& params,
vector<string>* out) {
S2RegionCoverer coverer;
params.configureCoverer(&coverer);
GeometryContainer geoContainer;
- if (!geoContainer.parseFrom(obj)) { return false; }
+ if (!geoContainer.parseFromStorage(element).isOK()) { return false; }
// Don't index big polygon
if (geoContainer.getNativeCRS() == STRICT_SPHERE) {
@@ -125,10 +125,9 @@ namespace {
for (BSONElementSet::iterator i = elements.begin(); i != elements.end(); ++i) {
uassert(16754, "Can't parse geometry from element: " + i->toString(),
i->isABSONObj());
- const BSONObj &geoObj = i->Obj();
vector<string> cells;
- bool succeeded = S2GetKeysForObject(geoObj, params, &cells);
+ bool succeeded = S2GetKeysForObject(*i, params, &cells);
uassert(16755, "Can't extract geo keys from object, malformed geometry?: "
+ document.toString(), succeeded);
diff --git a/src/mongo/db/matcher/expression_geo.cpp b/src/mongo/db/matcher/expression_geo.cpp
index 1fbc834bdb6..b4f635351d5 100644
--- a/src/mongo/db/matcher/expression_geo.cpp
+++ b/src/mongo/db/matcher/expression_geo.cpp
@@ -47,97 +47,71 @@ namespace mongo {
GeoExpression::GeoExpression() : field(""), predicate(INVALID) {}
GeoExpression::GeoExpression(const std::string& f) : field(f), predicate(INVALID) {}
- bool GeoExpression::parseLegacyQuery(const BSONObj &obj) {
- // The only legacy syntax is {$within: {.....}}
+ Status GeoExpression::parseQuery(const BSONObj &obj) {
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;
+ // "within" / "geoWithin" / "geoIntersects"
+ BSONElement queryElt = outerIt.next();
+ if (outerIt.more()) {
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "can't parse extra field: " << outerIt.next());
}
- 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 = GeoExpression::WITHIN;
-
- return hasGeometry;
- }
-
- bool GeoExpression::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());
+ BSONObj::MatchType matchType = static_cast<BSONObj::MatchType>(queryElt.getGtLtOp());
if (BSONObj::opGEO_INTERSECTS == matchType) {
predicate = GeoExpression::INTERSECT;
} else if (BSONObj::opWITHIN == matchType) {
predicate = GeoExpression::WITHIN;
} else {
- return false;
+ // eoo() or unknown query predicate.
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "invalid geo query predicate: " << obj);
}
- 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;
- }
- }
+ // Parse geometry after predicates.
+ if (Object != queryElt.type()) return Status(ErrorCodes::BadValue, "geometry must be an object");
+ BSONObj geoObj = queryElt.Obj();
+
+ BSONObjIterator geoIt(geoObj);
+
+ while (geoIt.more()) {
+ BSONElement elt = geoIt.next();
+ if (str::equals(elt.fieldName(), "$uniqueDocs")) {
+ // Deprecated "$uniqueDocs" field
+ warning() << "deprecated $uniqueDocs option: " << obj.toString() << endl;
+ } else {
+ // The element must be a geo specifier. "$box", "$center", "$geometry", etc.
+ geoContainer.reset(new GeometryContainer());
+ Status status = geoContainer->parseFromQuery(elt);
+ if (!status.isOK()) return status;
}
}
- // Don't want to give the error below if we could not pull any geometry out.
- if (!hasGeometry) { return false; }
-
- if (GeoExpression::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());
+ if (geoContainer == NULL) {
+ return Status(ErrorCodes::BadValue, "geo query doesn't have any geometry");
}
- return hasGeometry;
+ return Status::OK();
}
bool GeoExpression::parseFrom(const BSONObj &obj) {
- geoContainer.reset(new GeometryContainer());
- if (!(parseLegacyQuery(obj) || parseNewQuery(obj)))
+ // Initialize geoContainer and parse BSON object
+ if (!parseQuery(obj).isOK()) {
return false;
+ }
+
+ // 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.
+ if (GeoExpression::WITHIN == predicate && !geoContainer->supportsContains()) {
+ 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
@@ -196,7 +170,7 @@ namespace mongo {
if (!e.isABSONObj()) { return false; }
BSONObj embeddedObj = e.embeddedObject();
- if ((GeoParser::isPoint(embeddedObj) && GeoParser::parsePoint(embeddedObj, centroid.get()))
+ if (GeoParser::parseQueryPoint(e, centroid.get()).isOK()
|| GeoParser::parsePointWithMaxDistance(embeddedObj, centroid.get(), &maxDistance)) {
uassert(18522, "max distance must be non-negative", maxDistance >= 0.0);
hasGeometry = true;
@@ -252,12 +226,12 @@ namespace mongo {
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.get())) {
- return Status(ErrorCodes::BadValue, mongoutils::str::stream() <<
- "invalid point in geo near query $geometry argument: " <<
- embeddedObj);
+ Status status = GeoParser::parseQueryPoint(e, centroid.get());
+ if (!status.isOK()) {
+ return Status(ErrorCodes::BadValue,
+ str::stream()
+ << "invalid point in geo near query $geometry argument: "
+ << embeddedObj << " " << status.reason());
}
uassert(16681, "$near requires geojson point, given " + embeddedObj.toString(),
(SPHERE == centroid->crs));
@@ -343,7 +317,7 @@ namespace mongo {
return false;
GeometryContainer geometry;
- if ( !geometry.parseFrom( e.Obj() ) )
+ if ( !geometry.parseFromStorage( e ).isOK() )
return false;
// Never match big polygon
diff --git a/src/mongo/db/matcher/expression_geo.h b/src/mongo/db/matcher/expression_geo.h
index ec8711c2119..ca08a77d894 100644
--- a/src/mongo/db/matcher/expression_geo.h
+++ b/src/mongo/db/matcher/expression_geo.h
@@ -62,9 +62,10 @@ namespace mongo {
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);
+ // Parse geospatial query
+ // e.g.
+ // { "$intersect" : { "$geometry" : { "type" : "Point", "coordinates": [ 40, 5 ] } } }
+ Status parseQuery(const BSONObj &obj);
// Name of the field in the query.
std::string field;