diff options
-rw-r--r-- | jstests/geo_s2holesameasshell.js | 31 | ||||
-rw-r--r-- | jstests/geo_s2intersection.js | 141 | ||||
-rw-r--r-- | jstests/geo_s2meridian.js | 108 | ||||
-rw-r--r-- | jstests/geo_s2nearComplex.js | 237 | ||||
-rw-r--r-- | jstests/geo_s2oddshapes.js | 144 | ||||
-rw-r--r-- | jstests/geo_s2overlappingpolys.js | 213 | ||||
-rwxr-xr-x | jstests/geo_s2polywithholes.js | 51 | ||||
-rw-r--r-- | jstests/geo_s2selfintersectingpoly.js | 12 |
8 files changed, 931 insertions, 6 deletions
diff --git a/jstests/geo_s2holesameasshell.js b/jstests/geo_s2holesameasshell.js index e40a32c5214..1605684828e 100644 --- a/jstests/geo_s2holesameasshell.js +++ b/jstests/geo_s2holesameasshell.js @@ -1,10 +1,10 @@ -t = db.geo_s2holessameasshell +var t = db.geo_s2holessameasshell t.drop(); t.ensureIndex({geo: "2dsphere"}); -centerPoint = {"type": "Point", "coordinates": [0.5, 0.5]}; -edgePoint = {"type": "Point", "coordinates": [0, 0.5]}; -cornerPoint = {"type": "Point", "coordinates": [0, 0]}; +var centerPoint = {"type": "Point", "coordinates": [0.5, 0.5]}; +var edgePoint = {"type": "Point", "coordinates": [0, 0.5]}; +var cornerPoint = {"type": "Point", "coordinates": [0, 0]}; // Various "edge" cases. None of them should be returned by the non-polygon // polygon below. @@ -13,7 +13,7 @@ t.insert({geo : edgePoint}); t.insert({geo : cornerPoint}); // This generates an empty covering. -polygonWithFullHole = { "type" : "Polygon", "coordinates": [ +var polygonWithFullHole = { "type" : "Polygon", "coordinates": [ [[0,0], [0,1], [1, 1], [1, 0], [0, 0]], [[0,0], [0,1], [1, 1], [1, 0], [0, 0]] ] @@ -24,5 +24,24 @@ t.insert({geo: polygonWithFullHole}) assert(db.getLastError()) // No covering to search over should give an empty result set. -res = t.find({geo: {$within: {$geometry: polygonWithFullHole}}}); +var res = t.find({geo: {$within: {$geometry: polygonWithFullHole}}}); assert.eq(res.count(), 0) + + +// Similar polygon to the one above, but is covered by two holes instead of +// one. +var polygonWithTwoHolesCoveringWholeArea = {"type" : "Polygon", "coordinates": [ + [[0,0], [0,1], [1, 1], [1, 0], [0, 0]], + [[0,0], [0,0.5], [1, 0.5], [1, 0], [0, 0]], + [[0,0.5], [0,1], [1, 1], [1, 0.5], [0, 0.5]] + ] +}; + +// No keys for insert should error. +t.insert({geo: polygonWithTwoHolesCoveringWholeArea}); +assert(db.getLastError()); + +// No covering to search over should give an empty result set. +res = t.find({geo: {$within: {$geometry: ppolygonWithTwoHolesCoveringWholeArea}}}); +assert.eq(res.count(), 0); + diff --git a/jstests/geo_s2intersection.js b/jstests/geo_s2intersection.js new file mode 100644 index 00000000000..42abacca98d --- /dev/null +++ b/jstests/geo_s2intersection.js @@ -0,0 +1,141 @@ +var t = db.geo_s2intersectinglines +t.drop() +t.ensureIndex( { geo : "2dsphere" } ); + +/* All the tests in this file are generally confirming intersections based upon + * these three geo objects. + */ +var canonLine = { + name: 'canonLine', + geo: { + type: "LineString", + coordinates: [[0.0, 0.0], [1.0, 0.0]] + } +}; + +var canonPoint = { + name: 'canonPoint', + geo: { + type: "Point", + coordinates: [10.0, 10.0] + } +}; + +var canonPoly = { + name: 'canonPoly', + geo: { + type: "Polygon", + coordinates: [ + [[50.0, 50.0], [51.0, 50.0], [51.0, 51.0], [50.0, 51.0], [50.0, 50.0]] + ] + } +}; + +t.insert(canonLine); +t.insert(canonPoint); +t.insert(canonPoly); + + +//Case 1: Basic sanity intersection. +var testLine = {type: "LineString", + coordinates: [[0.5, 0.5], [0.5, -0.5]]}; + +var result = t.find({geo: {$geoIntersects: {$geometry: testLine}}}); +assert.eq(result.count(), 1); +assert.eq(result[0]['name'], 'canonLine'); + + +//Case 2: Basic Polygon intersection. +// we expect that the canonLine should intersect with this polygon. +var testPoly = {type: "Polygon", + coordinates: [ + [[0.4, -0.1],[0.4, 0.1], [0.6, 0.1], [0.6, -0.1], [0.4, -0.1]] + ]} + +result = t.find({geo: {$geoIntersects: {$geometry: testPoly}}}); +assert.eq(result.count(), 1); +assert.eq(result[0]['name'], 'canonLine'); + + +//Case 3: Intersects the vertex of a line. +// When a line intersects the vertex of a line, we expect this to +// count as a geoIntersection. +testLine = {type: "LineString", + coordinates: [[0.0, 0.5], [0.0, -0.5]]}; + +result = t.find({geo: {$geoIntersects: {$geometry: testLine}}}); +assert.eq(result.count(), 1); +assert.eq(result[0]['name'], 'canonLine'); + +// Case 4: Sanity no intersection. +// This line just misses the canonLine in the negative direction. This +// should not count as a geoIntersection. +testLine = {type: "LineString", + coordinates: [[-0.1, 0.5], [-0.1, -0.5]]}; + +result = t.find({geo: {$geoIntersects: {$geometry: testLine}}}); +assert.eq(result.count(), 0); + + +// Case 5: Overlapping line - only partially overlaps. +// Undefined behaviour: does intersect +testLine = {type: "LineString", + coordinates: [[-0.5, 0.0], [0.5, 0.0]]}; + +var result = t.find({geo: {$geoIntersects: {$geometry: testLine}}}); +assert.eq(result.count(), 1); +assert.eq(result[0]['name'], 'canonLine'); + + +// Case 6: Contained line - this line is fully contained by the canonLine +// Undefined behaviour: doesn't intersect. +testLine = {type: "LineString", + coordinates: [[0.1, 0.0], [0.9, 0.0]]}; + +result = t.find({geo: {$geoIntersects: {$geometry: testLine}}}); +assert.eq(result.count(), 0); + +// Case 7: Identical line in the identical position. +// Undefined behaviour: does intersect. +testLine = {type: "LineString", + coordinates: [[0.0, 0.0], [1.0, 0.0]]}; + +result = t.find({geo: {$geoIntersects: {$geometry: testLine}}}); +assert.eq(result.count(), 1); +assert.eq(result[0]['name'], 'canonLine'); + +// Case 8: Point intersection - we search with a line that intersects +// with the canonPoint. +testLine = {type: "LineString", + coordinates: [[10.0, 11.0], [10.0, 9.0]]}; + +result = t.find({geo: {$geoIntersects: {$geometry: testLine}}}); +assert.eq(result.count(), 1); +assert.eq(result[0]['name'], 'canonPoint'); + +// Case 9: Point point intersection +// as above but with an identical point to the canonPoint. We expect an +// intersection here. +testPoint = {type: "Point", + coordinates: [10.0, 10.0]} + +result = t.find({geo: {$geoIntersects: {$geometry: testPoint}}}); +assert.eq(result.count(), 1); +assert.eq(result[0]['name'], 'canonPoint'); + + +//Case 10: Sanity point non-intersection. +var testPoint = {type: "Point", + coordinates: [12.0, 12.0]} + +result = t.find({geo: {$geoIntersects: {$geometry: testPoint}}}); +assert.eq(result.count(), 0); + +// Case 11: Point polygon intersection +// verify that a point inside a polygon $geoIntersects. +testPoint = {type: "Point", + coordinates: [50.5, 50.5]} + +result = t.find({geo: {$geoIntersects: {$geometry: testPoint}}}); +assert.eq(result.count(), 1); +assert.eq(result[0]['name'], 'canonPoly'); diff --git a/jstests/geo_s2meridian.js b/jstests/geo_s2meridian.js new file mode 100644 index 00000000000..0114f36505e --- /dev/null +++ b/jstests/geo_s2meridian.js @@ -0,0 +1,108 @@ +t = db.geo_s2meridian; +t.drop(); +t.ensureIndex({geo: "2dsphere"}); + +/* + * Test 1: check that intersection works on the meridian. We insert a line + * that crosses the meridian, and then run a geoIntersect with a line + * that runs along the meridian. + */ + +meridianCrossingLine = { + geo: { + type: "LineString", + coordinates: [ + [-178.0, 10.0], + [178.0, 10.0]] + } +}; + +t.insert(meridianCrossingLine); +assert(! db.getLastError()); + +lineAlongMeridian = { + type: "LineString", + coordinates: [ + [180.0, 11.0], + [180.0, 9.0] + ] +} + +result = t.find({geo: {$geoIntersects: {$geometry: lineAlongMeridian}}}); +assert.eq(result.count(), 1); + +t.drop(); +t.ensureIndex({geo: "2dsphere"}); +/* + * Test 2: check that within work across the meridian. We insert points + * on the meridian, and immediately on either side, and confirm that a poly + * covering all of them returns them all. + */ +pointOnNegativeSideOfMeridian = { + geo: { + type: "Point", + coordinates: [-179.0, 1.0] + } +}; +pointOnMeridian = { + geo: { + type: "Point", + coordinates: [180.0, 1.0] + } +}; +pointOnPositiveSideOfMeridian = { + geo: { + type: "Point", + coordinates: [179.0, 1.0] + } +}; + +t.insert(pointOnMeridian); +t.insert(pointOnNegativeSideOfMeridian); +t.insert(pointOnPositiveSideOfMeridian); + +meridianCrossingPoly = { + type: "Polygon", + coordinates: [ + [[-178.0, 10.0], [178.0, 10.0], [178.0, -10.0], [-178.0, -10.0], [-178.0, 10.0]] + ] +}; + +result = t.find({geo: {$within: {$geometry: meridianCrossingPoly}}}); +assert.eq(result.count(), 3); + +t.drop(); +t.ensureIndex({geo: "2dsphere"}); +/* + * Test 3: Check that near works around the meridian. Insert two points, one + * closer, but across the meridian, and confirm they both come back, and + * that the order is correct. + */ +pointOnNegativeSideOfMerid = { + name: "closer", + geo: { + type: "Point", + coordinates: [-179.0, 0.0] + } +}; + +pointOnPositiveSideOfMerid = { + name: "farther", + geo: { + type: "Point", + coordinates: [176.0, 0.0] + } +}; + +t.insert(pointOnNegativeSideOfMerid); +t.insert(pointOnPositiveSideOfMerid); + +pointOnPositiveSideOfMeridian = { + type: "Point", + coordinates: [179.0, 0.0] +}; + +result = t.find({geo: {$near: pointOnPositiveSideOfMeridian}}); +assert.eq(result.count(), 2); +assert.eq(result[0].name, "closer"); +assert.eq(result[1].name, "farther"); diff --git a/jstests/geo_s2nearComplex.js b/jstests/geo_s2nearComplex.js new file mode 100644 index 00000000000..61bb5551fb8 --- /dev/null +++ b/jstests/geo_s2nearComplex.js @@ -0,0 +1,237 @@ +var t = db.get_s2nearcomplex +t.drop() +t.ensureIndex({geo: "2dsphere"}) + +/* Short names for math operations */ +var random = Math.random; +var PI = Math.PI; +var asin = Math.asin; +var sin = Math.sin; +var cos = Math.cos; +var atan2 = Math.atan2 + + +var originGeo = {type: "Point", coordinates: [20.0, 20.0]}; +// Center point for all tests. +var origin = { + name: "origin", + geo: originGeo +} + + +/* + * Convenience function for checking that coordinates match. threshold let's you + * specify how accurate equals should be. + */ +function coordinateEqual(first, second, threshold){ + threshold = threshold || 0.001 + first = first['geo']['coordinates'] + second = second['geo']['coordinates'] + if(Math.abs(first[0] - second[0]) <= threshold){ + if(Math.abs(first[1] - second[1]) <= threshold){ + return true; + } + } + return false; +} + +/* + * Creates `count` random and uniformly distributed points centered around `origin` + * no points will be closer to origin than minDist, and no points will be further + * than maxDist. Points will be inserted into the global `t` collection, and will + * be returned. + * based on this algorithm: http://williams.best.vwh.net/avform.htm#LL + */ +function uniformPoints(origin, count, minDist, maxDist){ + var i; + var lng = origin['geo']['coordinates'][0]; + var lat = origin['geo']['coordinates'][1]; + var distances = []; + var points = []; + for(i=0; i < count; i++){ + distances.push((random() * (maxDist - minDist)) + minDist); + } + distances.sort(); + while(points.length < count){ + var angle = random() * 2 * PI; + var distance = distances[points.length]; + var pointLat = asin((sin(lat) * cos(distance)) + (cos(lat) * sin(distance) * cos(angle))); + var pointDLng = atan2(sin(angle) * sin(distance) * cos(lat), cos(distance) - sin(lat) * sin(pointLat)); + var pointLng = ((lng - pointDLng + PI) % 2*PI) - PI; + var newPoint = { + geo: { + type: "Point", + coordinates: [lng + pointLng, lat + pointLat] + } + }; + if(lat + pointLat > 90.0){ + continue; + } + points.push(newPoint); + } + for(i=0; i < points.length; i++){ + t.insert(points[i]); + assert(!db.getLastError()); + } + return points; +} + +/* + * Creates a random uniform field as above, excepting for `numberOfHoles` gaps that + * have `sizeOfHoles` points missing centered around a random point. + */ +function uniformPointsWithGaps(origin, count, minDist, maxDist, numberOfHoles, sizeOfHoles){ + var points = uniformPoints(origin, count, minDist, maxDist); + var i; + for(i=0; i<numberOfHoles; i++){ + var randomPoint = points[Math.floor(random() * points.length)]; + removeNearest(randomPoint, sizeOfHoles); + } +} + +/* + * Creates a random uniform field as above, expcepting for `numberOfClusters` clusters, + * which will consist of N points where `minClusterSize` <= N <= `maxClusterSize. + * you may specify an optional `distRatio` parameter which will specify the area that the cluster + * covers as a fraction of the full area that points are created on. Defaults to 10. + */ +function uniformPointsWithClusters(origin, count, minDist, maxDist, numberOfClusters, minClusterSize, maxClusterSize, distRatio){ + distRatio = distRatio || 10 + var points = uniformPoints(origin, count, minDist, maxDist); + for(j=0; j<numberOfClusters; j++){ + var randomPoint = points[Math.floor(random() * points.length)]; + var clusterSize = (random() * (maxClusterSize - minClusterSize)) + minClusterSize; + uniformPoints(randomPoint, clusterSize, minDist / distRatio, maxDist / distRatio); + } +} +/* + * Function used to create gaps in existing point field. Will remove the `number` nearest + * geo objects to the specified `point`. + */ +function removeNearest(point, number){ + var pointsToRemove = t.find({geo: {$near: {$geometry: point['geo']}}}).limit(number); + var idsToRemove = []; + while(pointsToRemove.hasNext()){ + point = pointsToRemove.next(); + idsToRemove.push(point['_id']); + } + + t.remove({_id: {$in: idsToRemove}}); +} +/* + * Validates the ordering of the nearest results is the same no matter how many + * geo objects are requested. This could fail if two points have the same dist + * from origin, because they may not be well-ordered. If we see strange failures, + * we should consider that. + */ +function validateOrdering(query){ + var near10 = t.find(query).limit(10); + var near20 = t.find(query).limit(20); + var near30 = t.find(query).limit(30); + var near40 = t.find(query).limit(40); + + for(i=0;i<10;i++){ + assert(coordinateEqual(near10[i], near20[i])); + assert(coordinateEqual(near10[i], near30[i])); + assert(coordinateEqual(near10[i], near40[i])); + } + + for(i=0;i<20;i++){ + assert(coordinateEqual(near20[i], near30[i])); + assert(coordinateEqual(near20[i], near40[i])); + } + + for(i=0;i<30;i++){ + assert(coordinateEqual(near30[i], near40[i])); + } +} + +var query = {geo: {$near: {$geometry: originGeo}}}; + +// Test a uniform distribution of 10000 points. +uniformPoints(origin, 10000, 0.5, 1.5); + +validateOrdering({geo: {$near: {$geometry: originGeo}}}) + +print("Millis for uniform:") +print(t.find(query).explain().millis) +print("Total points:"); +print(t.find(query).count()); + +t.drop() +t.ensureIndex({geo: "2dsphere"}) +// Test a uniform distribution with 5 gaps each with 10 points missing. +uniformPointsWithGaps(origin, 10000, 1, 10.0, 5, 10); + +validateOrdering({geo: {$near: {$geometry: originGeo}}}) + +print("Millis for uniform with gaps:") +print(t.find(query).explain().millis) +print("Total points:"); +print(t.find(query).count()); + +t.drop() +t.ensureIndex({geo: "2dsphere"}) + +// Test a uniform distribution with 5 clusters each with between 10 and 100 points. +uniformPointsWithClusters(origin, 10000, 1, 10.0, 5, 10, 100); + +validateOrdering({geo: {$near: {$geometry: originGeo}}}) + +print("Millis for uniform with clusters:"); +print(t.find(query).explain().millis); +print("Total points:"); +print(t.find(query).count()); + +t.drop() +t.ensureIndex({geo: "2dsphere"}) + +// Test a uniform near search with origin around the pole. + +// Center point near pole. +originGeo = {type: "Point", coordinates: [0.0, 89.0]}; +origin = { + name: "origin", + geo: originGeo +} +uniformPoints(origin, 50, 0.5, 1.5); + +validateOrdering({geo: {$near: {$geometry: originGeo}}}) + +print("Millis for uniform near pole:") +print(t.find({geo: {$near: {$geometry: originGeo}}}).explain().millis) +assert.eq(t.find({geo: {$near: {$geometry: originGeo}}}).count(), 50); + +t.drop() +t.ensureIndex({geo: "2dsphere"}) + +// Center point near the meridian +originGeo = {type: "Point", coordinates: [179.0, 0.0]}; +origin = { + name: "origin", + geo: originGeo +} +uniformPoints(origin, 50, 0.5, 1.5); + +validateOrdering({geo: {$near: {$geometry: originGeo}}}) + +print("Millis for uniform on meridian:") +print(t.find({geo: {$near: {$geometry: originGeo}}}).explain().millis) +assert.eq(t.find({geo: {$near: {$geometry: originGeo}}}).count(), 50); + +t.drop() +t.ensureIndex({geo: "2dsphere"}) + +// Center point near the negative meridian +originGeo = {type: "Point", coordinates: [-179.0, 0.0]}; +origin = { + name: "origin", + geo: originGeo +} +uniformPoints(origin, 50, 0.5, 1.5); + +validateOrdering({geo: {$near: {$geometry: originGeo}}}) + +print("Millis for uniform on negative meridian:"); +print(t.find({geo: {$near: {$geometry: originGeo}}}).explain().millis); +assert.eq(t.find({geo: {$near: {$geometry: originGeo}}}).count(), 50); diff --git a/jstests/geo_s2oddshapes.js b/jstests/geo_s2oddshapes.js new file mode 100644 index 00000000000..1b975f996de --- /dev/null +++ b/jstests/geo_s2oddshapes.js @@ -0,0 +1,144 @@ +var t = db.geo_s2oddshapes +t.drop() +t.ensureIndex( { geo : "2dsphere" } ); + +var testPoint = { + name: "origin", + geo: { + type: "Point", + coordinates: [0.0, 0.0] + } +}; + +var testHorizLine = { + name: "horiz", + geo: { + type: "LineString", + coordinates: [[-2.0, 10.0], [2.0, 10.0]] + } +}; + +var testVertLine = { + name: "vert", + geo: { + type: "LineString", + coordinates: [[10.0, -2.0], [10.0, 2.0]] + } +}; + +t.insert(testPoint); +t.insert(testHorizLine); +t.insert(testVertLine); + +//Test a poly that runs vertically all the way along the meridian. + +var tallPoly = {type: "Polygon", + coordinates: [ + [[1.0, 89.0], [-1.0, 89.0], [-1.0, -89.0], [1.0, -89.0], [1.0, 89.0]] + ]}; +//We expect that the testPoint (at the origin) will be within this poly. +var result = t.find({geo: {$within: {$geometry: tallPoly}}}); +assert.eq(result.count(), 1); +assert.eq(result[0].name, 'origin'); + +//We expect that the testPoint, and the testHorizLine should geoIntersect +//with this poly. +result = t.find({geo: {$geoIntersects: {$geometry: tallPoly}}}); +assert.eq(result.count(), 2); +assert.eq(result[0].name, 'horiz'); +assert.eq(result[1].name, 'origin'); + +//Test a poly that runs horizontally along the equator. + +var longPoly = {type: "Polygon", + coordinates: [ + [[89.0, 1.0], [-89.0, 1.0], [-89.0, -1.0], [89.0, -1.0], [89.0, 1.0]] + ]}; + +//We expect that the testPoint (at the origin) will be within this poly. +/* + * Tests commented out because they are currently failing. There is a + * bug tracked https://jira.mongodb.org/browse/SERVER-8180 . + */ +/* +result = t.find({geo: {$within: {$geometry: longPoly}}}); +assert.eq(result.count(), 1); +assert.eq(result[0].name, 'origin'); + +//We expect that the testPoint, and the testVertLine should geoIntersect +//with this poly. +result = t.find({geo: {$geoIntersects: {$geometry: longPoly}}}); +assert.eq(result.count(), 2); +assert.eq(result[0].name, 'vert'); +assert.eq(result[1].name, 'origin');*/ + +//Test a poly that is the size of half the earth. + +t.drop() +t.ensureIndex( { geo : "2dsphere" } ); + +var insidePoint = { + name: "inside", + geo: { + type: "Point", + name: "inside", + coordinates: [100.0, 0.0] + } +}; + +var outsidePoint = { + name: "inside", + geo: { + type: "Point", + name: "inside", + coordinates: [-100.0, 0.0] + } +}; + +t.insert(insidePoint); +t.insert(outsidePoint); + +var largePoly = {type: "Polygon", + coordinates: [ + [[0.0, -90.0], [0.0, 90.0], [180.0, 0], [0.0, -90.0]] + ]}; + +result = t.find({geo: {$within: {$geometry: largePoly}}}); +assert.eq(result.count(), 1); +var point = result[0] +assert.eq(point.name, 'inside'); + +//Test a poly that is very small. A couple meters around. + +t.drop() +t.ensureIndex( { geo : "2dsphere" } ); + +insidePoint = { + name: "inside", + geo: { + type: "Point", + name: "inside", + coordinates: [0.01, 0.0] + }}; + +outsidePoint = { + name: "inside", + geo: { + type: "Point", + name: "inside", + coordinates: [0.2, 0.0] + }}; + +t.insert(insidePoint); +t.insert(outsidePoint); + +smallPoly = {type: "Polygon", + coordinates: [ + [[0.0, -0.01], [0.015, -0.01], [0.015, 0.01], [0.0, 0.01], [0.0, -0.01]] + ]}; + +result = t.find({geo: {$within: {$geometry: smallPoly}}}); +assert.eq(result.count(), 1); +point = result[0] +assert.eq(point.name, 'inside'); + diff --git a/jstests/geo_s2overlappingpolys.js b/jstests/geo_s2overlappingpolys.js new file mode 100644 index 00000000000..197792a1f03 --- /dev/null +++ b/jstests/geo_s2overlappingpolys.js @@ -0,0 +1,213 @@ +var t = db.geo_s2overlappingpolys +t.drop() + +t.ensureIndex( { geo : "2dsphere" } ); + +var minError = 0.8e-13; + +var canonPoly = {type: "Polygon", + coordinates: [ + [[-1.0, -1.0], [1.0, -1.0], [1.0, 1.0], [-1.0, 1.0], [-1.0, -1.0]] + ]}; +t.insert({geo: canonPoly}); + +// Test 1: If a poly completely encloses the canonPoly, we expect the canonPoly +// to be returned for both $within and $geoIntersect + +var outerPoly = {type: "Polygon", + coordinates: [ + [[-2.0, -2.0], [2.0, -2.0], [2.0, 2.0], [-2.0, 2.0], [-2.0, -2.0]] + ]}; +var result = t.find({geo: {$within: {$geometry: outerPoly}}}); +assert.eq(result.count(), 1); +result = t.find({geo: {$geoIntersects: {$geometry: outerPoly}}}); +assert.eq(result.count(), 1); + + +// Test 2: If a poly that covers half of the canonPoly, we expect that it should +// geoIntersect, but should not be within. + +var partialPoly = {type: "Polygon", + coordinates: [ + [[-2.0, -2.0], [2.0, -2.0], [2.0, 0.0], [-2.0, 0.0], [-2.0, -2.0]] + ]}; + +//Should not be within +result = t.find({geo: {$within: {$geometry: partialPoly}}}); +assert.eq(result.count(), 0); + +//This should however count as a geoIntersect +result = t.find({geo: {$geoIntersects: {$geometry: partialPoly}}}); +assert.eq(result.count(), 1); + + +// Test 3: Polygons that intersect at a point or an edge have undefined +// behaviour in s2 The s2 library we're using appears to have +// the following behaviour. + +// Case (a): Polygons that intersect at one point (not a vertex). +// behaviour: geoIntersects. + +var sharedPointPoly = {type: "Polygon", + coordinates: [ + [[0.0, -2.0], [0.0, -1.0], [1.0, -2.0], [0.0, -2.0]] + ]}; + +result = t.find({geo: {$geoIntersects: {$geometry: sharedPointPoly}}}); +assert.eq(result.count(), 1); + +// Case (b): Polygons that intersect at one point (a vertex). +// behaviour: not geoIntersect + +var sharedVertexPoly = {type: "Polygon", + coordinates: [ + [[0.0, -2.0], [1.0, -1.0], [1.0, -2.0], [0.0, -2.0]] + ]}; + +result = t.find({geo: {$geoIntersects: {$geometry: sharedVertexPoly}}}); +assert.eq(result.count(), 0); + +// Case (c): Polygons that intesersect at one point that is very close to a +// vertex should have the same behaviour as Case (b). + +var almostSharedVertexPoly = {type: "Polygon", + coordinates: [ + [[0.0, -2.0], [1.0 - minError, -1.0], [1.0, -2.0], [0.0, -2.0]] + ]}; + +result = t.find({geo: {$geoIntersects: {$geometry: almostSharedVertexPoly}}}); +assert.eq(result.count(), 0); + + +// Case (d): Polygons that intesersect at one point that is not quite as close +// to a vertex should behave as though it were not a vertex, and should +// geoIntersect + +var notCloseEnoughSharedVertexPoly = {type: "Polygon", + coordinates: [ + [[0.0, -2.0], [1.0 - (10 * minError), -1.0], [1.0, -2.0], [0.0, -2.0]] + ]}; + +result = t.find({geo: {$geoIntersects: {$geometry: notCloseEnoughSharedVertexPoly}}}); +assert.eq(result.count(), 1); + +// Case (e): Polygons that come very close to having a point intersection +// on a non-vertex coordinate should intersect. + +var almostSharedPointPoly = {type: "Polygon", + coordinates: [ + [[0.0, -2.0], [0.0, (-1.0 - minError)], [1.0, -2.0], [0.0, -2.0]] + ]}; + +result = t.find({geo: {$geoIntersects: {$geometry: almostSharedPointPoly}}}); +assert.eq(result.count(), 1); + + +// Case (f): If we increase the error a little, it should no longer act +// as though it's intersecting. +// NOTE: I think this error bound seems odd. Going to 0.000152297 will break this test. +// I've confirmed there is an error bound, but it's a lot larger than we experienced above. +var errorBound = 0.000152298 +var notCloseEnoughSharedPointPoly = {type: "Polygon", + coordinates: [ + [[0.0, -2.0], [0.0, -1.0 - errorBound], [1.0, -2.0], [0.0, -2.0]] + ]}; + +result = t.find({geo: {$geoIntersects: {$geometry: notCloseEnoughSharedPointPoly}}}); +assert.eq(result.count(), 0); + +/* Test 3: Importantly, polygons with shared edges have undefined intersection + * under s2. Therefore these test serve more to make sure nothing changes than + * to confirm an expected behaviour. + */ + +// Case 1: A polygon who shares an edge with another polygon, where the searching +// polygon's edge is fully covered by the canon polygon's edge. +// Result: No intersection. +var fullyCoveredEdgePoly = {type: "Polygon", + coordinates: [ + [[-2.0, -0.5], [-1.0, -0.5], [-1.0, 0.5], [-2.0, 0.5], [-2.0, -0.5]] + ]}; + +result = t.find({geo: {$geoIntersects: {$geometry: fullyCoveredEdgePoly}}}); +assert.eq(result.count(), 0); + +// Case 2: A polygon who shares an edge with another polygon, where the searching +// polygon's edge fully covers the canon polygon's edge. +// Result: Intersection. +var coveringEdgePoly = {type: "Polygon", + coordinates: [ + [[-2.0, -1.5], [-1.0, -1.5], [-1.0, 1.5], [-2.0, 1.5], [-2.0, -1.5]] + ]}; + +result = t.find({geo: {$geoIntersects: {$geometry: coveringEdgePoly}}}); +assert.eq(result.count(), 1); + +// Case 2a: same as Case 2, except pulled slightly away from the polygon. +// Result: Intersection. +// NOTE: Scales of errors? +var closebyCoveringEdgePoly = {type: "Polygon", + coordinates: [ + [[-2.0, -1.5], [-1.0 - (minError / 1000), -1.5], [-1.0 - (minError / 1000), 1.5], [-2.0, 1.5], [-2.0, -1.5]] + ]}; + +result = t.find({geo: {$geoIntersects: {$geometry: closebyCoveringEdgePoly}}}); +assert.eq(result.count(), 1); + +// Case 2b: same as Case 4, except pulled slightly away from the polygon, so that it's not intersecting. +// Result: No Intersection. +// NOTE: Scales of errors? +var notCloseEnoughCoveringEdgePoly = {type: "Polygon", + coordinates: [ + [[-2.0, -1.5], [-1.0 - (minError / 100), -1.5], [-1.0 - (minError / 100), 1.5], [-2.0, 1.5], [-2.0, -1.5]] + ]}; + +result = t.find({geo: {$geoIntersects: {$geometry: notCloseEnoughCoveringEdgePoly}}}); +assert.eq(result.count(), 0); + +// Case 3: A polygon who shares an edge with another polygon, where the searching +// polygon's edge partially covers by the canon polygon's edge. +// Result: No intersection. +var partiallyCoveringEdgePoly = {type: "Polygon", + coordinates: [ + [[-2.0, -1.5], [-1.0, -1.5], [-1.0, 0.5], [-2.0, 0.5], [-2.0, -1.5]] + ]}; + +result = t.find({geo: {$geoIntersects: {$geometry: partiallyCoveringEdgePoly}}}); +assert.eq(result.count(), 0); + + +//Polygons that intersect at three non-co-linear points should geoIntersect +var sharedPointsPoly = {type: "Polygon", + coordinates: [ + [[0.0, -3.0], [0.0, -1.0], [2.0, -2.0], [1.0, 0.0], [2.0, 2.0], [0.0, 1.0], [0.0, 3.0], [3.0, 3.0], [3.0, -3.0], [0.0, -3.0]] + ]}; + +result = t.find({geo: {$geoIntersects: {$geometry: sharedPointsPoly}}}); +assert.eq(result.count(), 1); + +//If a polygon contains a hole, and another polygon is within that hole, it should not be within or intersect. + +var bigHolePoly = {type: "Polygon", + coordinates: [ + [[-3.0, -3.0], [3.0, -3.0], [3.0, 3.0], [-3.0, 3.0], [-3.0, -3.0]], + [[-2.0, -2.0], [2.0, -2.0], [2.0, 2.0], [-2.0, 2.0], [-2.0, -2.0]] + ]}; +result = t.find({geo: {$within: {$geometry: bigHolePoly}}}); +assert.eq(result.count(), 0); +result = t.find({geo: {$geoIntersects: {$geometry: bigHolePoly}}}); +assert.eq(result.count(), 0); + +// If a polygon has a hole, and another polygon is contained partially by that hole, it should be an intersection +// but not a within. + +var internalOverlapPoly = {type: "Polygon", + coordinates: [ + [[-3.0, -3.0], [3.0, -3.0], [3.0, 3.0], [-3.0, 3.0], [-3.0, -3.0]], + [[-2.0, 0.0], [2.0, 0.0], [2.0, 2.0], [-2.0, 2.0], [-2.0, 0.0]] + ]}; + +result = t.find({geo: {$geoIntersects: {$geometry: internalOverlapPoly}}}); +assert.eq(result.count(), 1); +result = t.find({geo: {$within: {$geometry: internalOverlapPoly}}}); +assert.eq(result.count(), 0); diff --git a/jstests/geo_s2polywithholes.js b/jstests/geo_s2polywithholes.js new file mode 100755 index 00000000000..83dec71ac26 --- /dev/null +++ b/jstests/geo_s2polywithholes.js @@ -0,0 +1,51 @@ +var t = db.geo_s2weirdpolys; +t.drop(); +t.ensureIndex({geo: "2dsphere"}); + +var centerPoint = {"type": "Point", "coordinates": [0.5, 0.5]}; +var edgePoint = {"type": "Point", "coordinates": [0, 0.5]}; +var cornerPoint = {"type": "Point", "coordinates": [0, 0]}; + +t.insert({geo : centerPoint}); +t.insert({geo : edgePoint}); +t.insert({geo : cornerPoint}); + +var polygonWithNoHole = {"type" : "Polygon", "coordinates": [ + [[0,0], [0,1], [1, 1], [1, 0], [0, 0]] + ] +}; + +// Test 1: Sanity check. Expect all three points. +var sanityResult = t.find({geo: {$within: {$geometry: polygonWithNoHole}}}); + +assert.eq(sanityResult.count(), 3); + +// Test 2: Polygon with a hole that isn't contained byt the poly shell. +var polygonWithProtrudingHole = {"type" : "Polygon", "coordinates": [ + [[0,0], [0,1], [1, 1], [1, 0], [0, 0]], + [[0.4,0.9], [0.4,1.1], [0.5, 1.1], [0.5, 0.9], [0.4, 0.9]] + ] +}; + +// Bad shell, should error. +t.insert({geo: polygonWithProtrudingHole}); +assert(db.getLastError()); + +// When used for a within search, the shell seems to be respected, and all points +// are returned. Since this errors on insert, I don't think it's behaviour +// actually matters much. +var protrudingResult = t.find({geo: {$within: {$geometry: polygonWithProtrudingHole}}}); +assert(protrudingResult, 3); + +// Test 3: This test will confirm that a polygon with overlapping holes throws +// an error. + +var polyWithOverlappingHoles = {"type" : "Polygon", "coordinates": [ + [[0,0], [0,1], [1, 1], [1, 0], [0, 0]], + [[0.2,0.6], [0.2,0.9], [0.6, 0.9], [0.6, 0.6], [0.2, 0.6]], + [[0.5,0.4], [0.5,0.7], [0.8, 0.7], [0.8, 0.4], [0.5, 0.4]] + ] +}; + +t.insert({geo: polyWithOverlappingHoles}); +assert(db.getLastError()); diff --git a/jstests/geo_s2selfintersectingpoly.js b/jstests/geo_s2selfintersectingpoly.js new file mode 100644 index 00000000000..4b7e0d4eff3 --- /dev/null +++ b/jstests/geo_s2selfintersectingpoly.js @@ -0,0 +1,12 @@ +var t = db.geo_s2selfintersectingpoly; +t.drop(); +t.ensureIndex({geo: "2dsphere"}); + +var intersectingPolygon = {"type": "Polygon", "coordinates": [ + [[0.0, 0.0], [0.0, 4.0], [-3.0, 2.0], [1.0, 2.0], [0.0, 0.0]] +]}; +/* + * Self intersecting polygons should cause a parse exception. + */ +t.insert({geo : intersectingPolygon}); +assert(db.getLastError()); |