sql >> Base de Datos >  >> NoSQL >> MongoDB

MongoDB - Intersección geoespacial de dos polígonos

Así que mirando esto con una mente fresca, la respuesta me está mirando a la cara. La clave que ya ha dicho es que desea encontrar la "intersección" de dos consultas en una sola respuesta.

Otra forma de ver esto es que desea que todos los puntos vinculados por la primera consulta sean "entradas" para la segunda consulta, y así sucesivamente según sea necesario. Eso es esencialmente lo que hace una intersección, pero la lógica en realidad es literal.

Así que simplemente use el marco de agregación para encadenar las consultas coincidentes. Para un ejemplo simple, considere los siguientes documentos:

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

Y la tubería de agregación encadenada, solo dos consultas:

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

Entonces, si considera eso lógicamente, el primer resultado encontrará los puntos que se encuentran dentro de los límites del cuadro inicial o los dos primeros elementos. Luego, la segunda consulta actúa sobre esos resultados, y dado que los nuevos límites del cuadro comienzan en [5,5] que excluye el primer punto. El tercer punto ya estaba excluido, pero si se invirtieran las restricciones de las casillas, el resultado sería solo el mismo documento del medio.

Cómo funciona esto es bastante exclusivo de $geoWithin operador de consulta en comparación con otras funciones geográficas:

Así que los resultados son tanto buenos como malos. Bueno porque puede hacer este tipo de operación sin un índice en su lugar, pero malo porque una vez que la canalización de agregación ha alterado los resultados de la recopilación después de la primera operación de consulta, no se puede usar ningún índice adicional. Por lo tanto, cualquier beneficio de rendimiento de un índice se pierde al fusionar los resultados del "conjunto" de cualquier cosa posterior al Polígono/MultiPolígono inicial, según se admita.

Por esta razón, aún recomendaría que calcule los límites de intersección "fuera" de la consulta emitida a MongoDB. Aunque el marco de agregación puede hacer esto debido a la naturaleza "encadenada" de la canalización, y aunque las intersecciones resultantes serán cada vez más pequeñas, su mejor rendimiento es una sola consulta con los límites correctos que pueden usar todos los beneficios del índice.

Hay varios métodos para hacerlo, pero como referencia, aquí hay una implementación que usa el JSTS biblioteca, que es un puerto JavaScript del popular JTS biblioteca para Java. Puede haber otros u otros puertos de idioma, pero esto tiene un análisis GeoJSON simple y métodos integrados para cosas como obtener los límites de intersección:

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

  });

});

Usando las representaciones completas de GeoJSON "Polygon" allí, ya que esto se traduce en lo que JTS puede entender y trabajar. Lo más probable es que cualquier entrada que pueda recibir para una aplicación real también esté en este formato en lugar de aplicar conveniencias como $box .

Por lo tanto, se puede hacer con el marco de agregación, o incluso consultas paralelas que fusionan el "conjunto" de resultados. Pero si bien el marco de agregación puede hacerlo mejor que fusionar conjuntos de resultados externamente, los mejores resultados siempre se obtendrán al calcular primero los límites.