Więc patrząc na to ze świeżym umysłem, odpowiedź patrzy mi prosto w twarz. Kluczową rzeczą, o której już wspomniałeś, jest to, że chcesz znaleźć „przecięcie” dwóch zapytań w jednej odpowiedzi.
Innym sposobem spojrzenia na to jest to, że chcesz, aby wszystkie punkty powiązane przez pierwsze zapytanie były następnie "dane wejściowe" dla drugiego zapytania i tak dalej, zgodnie z wymaganiami. Zasadniczo to jest to, co robi skrzyżowanie, ale logika jest w rzeczywistości dosłowna.
Po prostu użyj struktury agregacji aby połączyć pasujące zapytania. Jako prosty przykład rozważ następujące dokumenty:
{ "loc" : { "type" : "Point", "coordinates" : [ 4, 4 ] } }
{ "loc" : { "type" : "Point", "coordinates" : [ 8, 8 ] } }
{ "loc" : { "type" : "Point", "coordinates" : [ 12, 12 ] } }
I połączony potok agregacji, tylko dwa zapytania:
db.geotest.aggregate([
{ "$match": {
"loc": {
"$geoWithin": {
"$box": [ [0,0], [10,10] ]
}
}
}},
{ "$match": {
"loc": {
"$geoWithin": {
"$box": [ [5,5], [20,20] ]
}
}
}}
])
Jeśli więc rozważysz to logicznie, pierwszy wynik znajdzie punkty, które mieszczą się w granicach początkowego pola lub pierwszych dwóch elementów. Te wyniki są następnie przetwarzane przez drugie zapytanie, a ponieważ nowe granice pola zaczynają się od [5,5]
to wyklucza pierwszy punkt. Trzeci punkt został już wykluczony, ale jeśli ograniczenia dotyczące pudełek zostaną odwrócone, wynikiem będzie tylko ten sam środkowy dokument.
Sposób działania jest dość unikalny dla $geoWithin
operator zapytania w porównaniu z różnymi innymi funkcjami geograficznymi:
Więc wyniki są zarówno dobre, jak i złe. Dobrze, że można wykonać tego typu operację bez indeksu, ale źle, ponieważ gdy potok agregacji zmieni wyniki zbierania po pierwszej operacji zapytania, nie można użyć dalszego indeksu. Tak więc wszelkie korzyści związane z wydajnością indeksu są tracone przy łączeniu wyników „zestawu” z czegokolwiek po początkowym obsługiwanym wieloboku/wielokątu.
Z tego powodu nadal zalecałbym obliczenie granic przecięcia „na zewnątrz” zapytania wysłanego do MongoDB. Mimo że struktura agregacji może to zrobić ze względu na „połączony” charakter potoku, i mimo że wynikowe przecięcia będą coraz mniejsze, najlepszą wydajnością jest pojedyncze zapytanie z poprawnymi granicami, które może wykorzystać wszystkie zalety indeksu.
Istnieją różne metody, aby to zrobić, ale dla odniesienia tutaj jest implementacja przy użyciu JSTS biblioteka, która jest portem JavaScript popularnego JTS biblioteka dla Javy. Mogą istnieć inne lub inne porty językowe, ale ma to proste parsowanie GeoJSON i wbudowane metody do takich rzeczy, jak uzyskiwanie granic przecięcia:
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();
}
);
});
});
Korzystanie z pełnych reprezentacji GeoJSON „Polygon”, ponieważ przekłada się to na to, co JTS może zrozumieć i z czym pracować. Możliwe, że wszelkie dane wejściowe, które możesz otrzymać w przypadku rzeczywistej aplikacji, również będą w tym formacie, zamiast stosować udogodnienia, takie jak $box
.
Można to więc zrobić za pomocą frameworka agregującego, a nawet równoległych zapytań łączących „zbiór” wyników. Ale chociaż ramy agregacji mogą zrobić to lepiej niż zewnętrzne łączenie zestawów wyników, najlepsze wyniki zawsze będą pochodzić z obliczenia granic w pierwszej kolejności.