Files
mongo/jstests/core/geo_s2nearComplex.js
2016-05-28 17:55:12 -04:00

290 lines
8.8 KiB
JavaScript

var t = db.get_s2nearcomplex;
t.drop();
t.ensureIndex({geo: "2dsphere"});
/* Short names for math operations */
Random.setRandomSeed();
var random = Random.rand;
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;
// Latitude must be [-90, 90]
var newLat = lat + pointLat;
if (newLat > 90)
newLat -= 180;
if (newLat < -90)
newLat += 180;
// Longitude must be [-180, 180]
var newLng = lng + pointLng;
if (newLng > 180)
newLng -= 360;
if (newLng < -180)
newLng += 360;
var newPoint = {
geo: {
type: "Point",
// coordinates: [lng + pointLng, lat + pointLat]
coordinates: [newLng, newLat]
}
};
points.push(newPoint);
}
for (i = 0; i < points.length; i++) {
t.insert(points[i]);
}
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: {$geoNear: {$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: {$geoNear: {$geometry: originGeo}}};
// Test a uniform distribution of 1000 points.
uniformPoints(origin, 1000, 0.5, 1.5);
validateOrdering({geo: {$geoNear: {$geometry: originGeo}}});
print("Millis for uniform:");
print(t.find(query).explain("executionStats").executionStats.executionTimeMillis);
print("Total points:");
print(t.find(query).itcount());
t.drop();
t.ensureIndex({geo: "2dsphere"});
// Test a uniform distribution with 5 gaps each with 10 points missing.
uniformPointsWithGaps(origin, 1000, 1, 10.0, 5, 10);
validateOrdering({geo: {$geoNear: {$geometry: originGeo}}});
print("Millis for uniform with gaps:");
print(t.find(query).explain("executionStats").executionStats.executionTimeMillis);
print("Total points:");
print(t.find(query).itcount());
t.drop();
t.ensureIndex({geo: "2dsphere"});
// Test a uniform distribution with 5 clusters each with between 10 and 100 points.
uniformPointsWithClusters(origin, 1000, 1, 10.0, 5, 10, 100);
validateOrdering({geo: {$geoNear: {$geometry: originGeo}}});
print("Millis for uniform with clusters:");
print(t.find(query).explain("executionStats").executionStats.executionTimeMillis);
print("Total points:");
print(t.find(query).itcount());
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: {$geoNear: {$geometry: originGeo}}});
print("Millis for uniform near pole:");
print(t.find({geo: {$geoNear: {$geometry: originGeo}}})
.explain("executionStats")
.executionStats.executionTimeMillis);
assert.eq(t.find({geo: {$geoNear: {$geometry: originGeo}}}).itcount(), 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: {$geoNear: {$geometry: originGeo}}});
print("Millis for uniform on meridian:");
print(t.find({geo: {$geoNear: {$geometry: originGeo}}})
.explain("executionStats")
.executionStats.executionTimeMillis);
assert.eq(t.find({geo: {$geoNear: {$geometry: originGeo}}}).itcount(), 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: {$geoNear: {$geometry: originGeo}}})
.explain("executionStats")
.executionStats.executionTimeMillis);
assert.eq(t.find({geo: {$near: {$geometry: originGeo}}}).itcount(), 50);
// Near search with points that are really far away.
t.drop();
t.ensureIndex({geo: "2dsphere"});
originGeo = {
type: "Point",
coordinates: [0.0, 0.0]
};
origin = {
name: "origin",
geo: originGeo
};
uniformPoints(origin, 10, 89, 90);
cur = t.find({geo: {$near: {$geometry: originGeo}}});
assert.eq(cur.itcount(), 10);
cur = t.find({geo: {$near: {$geometry: originGeo}}});
print("Near search on very distant points:");
print(t.find({geo: {$geoNear: {$geometry: originGeo}}})
.explain("executionStats")
.executionStats.executionTimeMillis);
pt = cur.next();
assert(pt);