Wenn ich das also mit einem frischen Verstand betrachte, starrt mir die Antwort ins Gesicht. Das Wichtigste, was Sie bereits gesagt haben, ist, dass Sie die "Schnittmenge" zweier Abfragen in einer einzigen Antwort finden möchten.
Sie können dies auch anders betrachten, indem Sie möchten, dass alle durch die erste Abfrage gebundenen Punkte dann für die zweite Abfrage "eingegeben" werden, und so weiter, wie erforderlich. Das ist im Wesentlichen das, was eine Kreuzung tut, aber die Logik ist eigentlich wörtlich.
Verwenden Sie also einfach das Aggregations-Framework um die übereinstimmenden Abfragen zu verketten. Betrachten Sie als einfaches Beispiel die folgenden Dokumente:
{ "loc" : { "type" : "Point", "coordinates" : [ 4, 4 ] } }
{ "loc" : { "type" : "Point", "coordinates" : [ 8, 8 ] } }
{ "loc" : { "type" : "Point", "coordinates" : [ 12, 12 ] } }
Und die verkettete Aggregationspipeline, nur zwei Abfragen:
db.geotest.aggregate([
{ "$match": {
"loc": {
"$geoWithin": {
"$box": [ [0,0], [10,10] ]
}
}
}},
{ "$match": {
"loc": {
"$geoWithin": {
"$box": [ [5,5], [20,20] ]
}
}
}}
])
Wenn Sie das also logisch betrachten, findet das erste Ergebnis die Punkte, die innerhalb der Grenzen des Anfangskästchens oder der ersten beiden Elemente liegen. Diese Ergebnisse werden dann von der zweiten Abfrage verarbeitet, und da die neuen Boxgrenzen bei [5,5]
beginnen das schließt den ersten Punkt aus. Der dritte Punkt wurde bereits ausgeschlossen, aber wenn die Boxbeschränkungen umgekehrt würden, wäre das Ergebnis nur das gleiche mittlere Dokument.
Wie dies funktioniert, ist ziemlich einzigartig für $geoWithin
Abfrageoperator im Vergleich zu verschiedenen anderen Geofunktionen:
Die Ergebnisse sind also sowohl gut als auch schlecht. Gut, dass Sie diese Art von Vorgang ohne einen vorhandenen Index durchführen können, aber schlecht, da, sobald die Aggregationspipeline die Erfassungsergebnisse nach dem ersten Abfragevorgang geändert hat, kein weiterer Index verwendet werden kann. Daher gehen alle Leistungsvorteile eines Indexes verloren, wenn die "Set"-Ergebnisse von allem nach dem anfänglichen Polygon/MultiPolygon wie unterstützt zusammengeführt werden.
Aus diesem Grund würde ich dennoch empfehlen, dass Sie die Schnittpunktgrenzen „außerhalb“ der an MongoDB ausgegebenen Abfrage berechnen. Obwohl das Aggregationsframework dies aufgrund der „verketteten“ Natur der Pipeline tun kann und die resultierenden Schnittpunkte immer kleiner werden, ist Ihre beste Leistung eine einzelne Abfrage mit den richtigen Grenzen, die alle Indexvorteile nutzen kann.
Dafür gibt es verschiedene Methoden, aber als Referenz dient hier eine Implementierung mit JSTS Bibliothek, die eine JavaScript-Portierung des beliebten JTS ist Bibliothek für Java. Es mag andere oder andere Sprachports geben, aber dieser hat ein einfaches GeoJSON-Parsing und eingebaute Methoden für Dinge wie das Abrufen der Schnittpunktgrenzen:
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();
}
);
});
});
Verwenden Sie dort die vollständigen GeoJSON "Polygon" -Darstellungen, da dies zu dem führt, was JTS verstehen und damit arbeiten kann. Es besteht die Möglichkeit, dass alle Eingaben, die Sie für eine echte Anwendung erhalten, ebenfalls in diesem Format vorliegen, anstatt Annehmlichkeiten wie $box
.
Es kann also mit dem Aggregations-Framework oder sogar mit parallelen Abfragen durchgeführt werden, die den "Satz" von Ergebnissen zusammenführen. Aber während das Aggregations-Framework es vielleicht besser macht, als Sätze von Ergebnissen extern zusammenzuführen, werden die besten Ergebnisse immer erzielt, wenn zuerst die Grenzen berechnet werden.