因此,以全新的心态看待这个问题,答案就在我眼前。您已经说过的关键是您希望在单个响应中找到两个查询的“交集”。
查看此问题的另一种方法是,您希望所有由第一个查询绑定的点都成为第二个查询的“输入”,依此类推。这本质上是交集的作用,但逻辑实际上是字面的。
所以只需使用aggregation framework 链接匹配的查询。举个简单的例子,考虑以下文档:
{ "loc" : { "type" : "Point", "coordinates" : [ 4, 4 ] } }
{ "loc" : { "type" : "Point", "coordinates" : [ 8, 8 ] } }
{ "loc" : { "type" : "Point", "coordinates" : [ 12, 12 ] } }
而链式聚合管道,只有两个查询:
db.geotest.aggregate([
{ "$match": {
"loc": {
"$geoWithin": {
"$box": [ [0,0], [10,10] ]
}
}
}},
{ "$match": {
"loc": {
"$geoWithin": {
"$box": [ [5,5], [20,20] ]
}
}
}}
])
因此,如果您从逻辑上考虑,第一个结果将找到落在初始框或前两项范围内的点。然后,第二个查询对这些结果进行处理,并且由于新的框边界从 [5,5] 开始,不包括第一个点。第三点已经被排除了,但是如果颠倒框限制,那么结果将只是相同的中间文档。
与其他各种地理功能相比,$geoWithin 查询运算符的工作原理非常独特:
$geoWithin 不需要地理空间索引。但是,地理空间索引将提高查询性能。 2dsphere 和 2d 地理空间索引都支持 $geoWithin。
所以结果有好有坏。好处在于您可以在没有索引的情况下执行这种类型的操作,但不好的是,一旦聚合管道在第一次查询操作后更改了集合结果,就无法使用进一步的索引。因此,在支持初始 Polygon/MultiPolygon 之后的任何内容合并“集合”结果时,索引的任何性能优势都会丢失。
出于这个原因,我仍然建议您计算向 MongoDB 发出的查询“外部”的交集边界。即使聚合框架由于管道的“链式”性质而可以做到这一点,并且即使产生的交叉点会变得越来越小,但最好的性能是具有正确边界的单个查询,可以使用所有索引优势。
有多种方法可以做到这一点,但这里的参考是使用JSTS 库的实现,它是流行的Java 的JTS 库的JavaScript 端口。可能还有其他或其他语言端口,但它具有简单的 GeoJSON 解析和内置方法,用于获取交叉边界:
var async = require('async');
util = require('util'),
jsts = require('jsts'),
mongo = require('mongodb'),
MongoClient = mongo.MongoClient;
var parser = new jsts.io.GeoJSONParser();
var polys= [
{
type: 'Polygon',
coordinates: [[
[ 0, 0 ], [ 0, 10 ], [ 10, 10 ], [ 10, 0 ], [ 0, 0 ]
]]
},
{
type: 'Polygon',
coordinates: [[
[ 5, 5 ], [ 5, 20 ], [ 20, 20 ], [ 20, 5 ], [ 5, 5 ]
]]
}
];
var points = [
{ type: 'Point', coordinates: [ 4, 4 ] },
{ type: 'Point', coordinates: [ 8, 8 ] },
{ type: 'Point', coordinates: [ 12, 12 ] }
];
MongoClient.connect('mongodb://localhost/test',function(err,db) {
db.collection('geotest',function(err,geo) {
if (err) throw err;
async.series(
[
// Insert some data
function(callback) {
var bulk = geo.initializeOrderedBulkOp();
bulk.find({}).remove();
async.each(points,function(point,callback) {
bulk.insert({ "loc": point });
callback();
},function(err) {
bulk.execute(callback);
});
},
// Run each version of the query
function(callback) {
async.parallel(
[
// Aggregation
function(callback) {
var pipeline = [];
polys.forEach(function(poly) {
pipeline.push({
"$match": {
"loc": {
"$geoWithin": {
"$geometry": poly
}
}
}
});
});
geo.aggregate(pipeline,callback);
},
// Using external set resolution
function(callback) {
var geos = polys.map(function(poly) {
return parser.read( poly );
});
var bounds = geos[0];
for ( var x=1; x<geos.length; x++ ) {
bounds = bounds.intersection( geos[x] );
}
var coords = parser.write( bounds );
geo.find({
"loc": {
"$geoWithin": {
"$geometry": coords
}
}
}).toArray(callback);
}
],
callback
);
}
],
function(err,results) {
if (err) throw err;
console.log(
util.inspect( results.slice(-1), false, 12, true ) );
db.close();
}
);
});
});
在那里使用完整的 GeoJSON“多边形”表示,因为这可以转化为 JTS 可以理解和使用的内容。您可能收到的任何实际应用程序的输入都可能采用这种格式,而不是应用诸如$box 之类的便利。
所以它可以通过聚合框架来完成,甚至可以通过合并“集合”结果的并行查询来完成。但是,虽然聚合框架可能比在外部合并结果集做得更好,但最好的结果总是来自首先计算边界。