MongoDB
 sql >> Database >  >> NoSQL >> MongoDB

MongoDB - Intersezione geospaziale di due poligoni

Quindi, guardando questo con una mente fresca, la risposta è fissarmi in faccia. La cosa fondamentale che hai già affermato è che vuoi trovare l'"intersezione" di due domande in un'unica risposta.

Un altro modo per vedere questo è che tutti i punti vincolati dalla prima query siano "input" per la seconda query e così via come richiesto. Questo è essenzialmente ciò che fa un'intersezione, ma la logica è in realtà letterale.

Quindi usa il framework di aggregazione per concatenare le query corrispondenti. Per un semplice esempio, considera i seguenti documenti:

{ "loc" : { "type" : "Point", "coordinates" : [ 4, 4 ] } }
{ "loc" : { "type" : "Point", "coordinates" : [ 8, 8 ] } }
{ "loc" : { "type" : "Point", "coordinates" : [ 12, 12 ] } }

E la pipeline di aggregazione concatenata, solo due query:

db.geotest.aggregate([
    { "$match": {
        "loc": {
            "$geoWithin": {
                "$box": [ [0,0], [10,10] ]
            }
        }
    }},
    { "$match": {
        "loc": {
            "$geoWithin": {
                "$box": [ [5,5], [20,20] ]
            }
        }
    }}
])

Quindi, se lo consideri logicamente, il primo risultato troverà i punti che rientrano nei limiti della casella iniziale o dei primi due elementi. Questi risultati vengono quindi attivati ​​dalla seconda query e poiché i nuovi limiti di casella iniziano a [5,5] che esclude il primo punto. Il terzo punto era già escluso, ma se le restrizioni del riquadro fossero state annullate, il risultato sarebbe stato solo lo stesso documento centrale.

Come funziona in modo del tutto unico per $geoWithin operatore di query rispetto a varie altre funzioni geografiche:

Quindi i risultati sono sia buoni che cattivi. Buono in quanto è possibile eseguire questo tipo di operazione senza un indice in atto, ma negativo perché una volta che la pipeline di aggregazione ha modificato i risultati della raccolta dopo la prima operazione di query, non è possibile utilizzare ulteriori indici. Pertanto, qualsiasi vantaggio in termini di prestazioni di un indice viene perso se si uniscono i risultati del "set" da qualsiasi cosa dopo il Poligono/Multipoligono iniziale come supportato.

Per questo motivo consiglierei comunque di calcolare i limiti di intersezione "esterno" della query inviata a MongoDB. Anche se il framework di aggregazione può farlo a causa della natura "concatenata" della pipeline e anche se le intersezioni risultanti diventeranno sempre più piccole, le prestazioni migliori sono una singola query con i limiti corretti che può utilizzare tutti i vantaggi dell'indice.

Esistono vari metodi per farlo, ma come riferimento ecco un'implementazione che utilizza JSTS library, che è un port JavaScript del popolare JTS libreria per Java. Potrebbero essercene altre o altre porte di lingua, ma questo ha una semplice analisi GeoJSON e metodi integrati per cose come ottenere i limiti di intersezione:

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();
      }
    );

  });

});

L'utilizzo delle rappresentazioni GeoJSON "Polygon" complete in questo modo si traduce in ciò con cui JTS può comprendere e lavorare. È probabile che qualsiasi input che potresti ricevere per un'applicazione reale sarebbe anche in questo formato piuttosto che applicare comodità come $box .

Quindi può essere fatto con il framework di aggregazione o anche con query parallele che uniscono il "set" di risultati. Ma mentre il framework di aggregazione può fare meglio che unire insiemi di risultati esternamente, i risultati migliori verranno sempre calcolando prima i limiti.