diff options
author | Siyuan Zhou <siyuan.zhou@mongodb.com> | 2014-08-27 18:31:04 -0400 |
---|---|---|
committer | Siyuan Zhou <siyuan.zhou@mongodb.com> | 2014-09-15 15:37:28 -0400 |
commit | 13617e4a80f2d1be45f2b6f237e0f13b1d1c68a0 (patch) | |
tree | 20cf5601dfc7399ddfd46dd9c8a215257563e9ba /src/mongo/db | |
parent | 8ffb9fae4fc1dbc6519bf4b04ac9316616c2efdf (diff) | |
download | mongo-13617e4a80f2d1be45f2b6f237e0f13b1d1c68a0.tar.gz |
SERVER-14508 Rewrite geo parsing using Status
Diffstat (limited to 'src/mongo/db')
-rw-r--r-- | src/mongo/db/commands/geo_near_cmd.cpp | 4 | ||||
-rw-r--r-- | src/mongo/db/exec/geo_near.cpp | 8 | ||||
-rw-r--r-- | src/mongo/db/geo/geometry_container.cpp | 203 | ||||
-rw-r--r-- | src/mongo/db/geo/geometry_container.h | 11 | ||||
-rw-r--r-- | src/mongo/db/geo/geoparser.cpp | 881 | ||||
-rw-r--r-- | src/mongo/db/geo/geoparser.h | 82 | ||||
-rw-r--r-- | src/mongo/db/geo/geoparser_test.cpp | 408 | ||||
-rw-r--r-- | src/mongo/db/geo/r2_region_coverer_test.cpp | 4 | ||||
-rw-r--r-- | src/mongo/db/index/expression_keys_private.cpp | 7 | ||||
-rw-r--r-- | src/mongo/db/matcher/expression_geo.cpp | 134 | ||||
-rw-r--r-- | src/mongo/db/matcher/expression_geo.h | 7 |
11 files changed, 853 insertions, 896 deletions
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(®ions)); + } + + 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; |