MongoDB
 sql >> Base de Dados >  >> NoSQL >> MongoDB

MongoDB - Interseção geoespacial de dois polígonos


Então, olhando para isso com uma mente fresca, a resposta está me encarando. A principal coisa que você já declarou é que deseja encontrar a "interseção" de duas consultas em uma única resposta.

Outra maneira de ver isso é que você deseja que todos os pontos vinculados à primeira consulta sejam "entrada" para a segunda consulta e assim por diante, conforme necessário. Isso é essencialmente o que uma interseção faz, mas a lógica é realmente literal.

Então, basta usar a estrutura de agregação para encadear as consultas correspondentes. Para um exemplo simples, considere os seguintes documentos:
{ "loc" : { "type" : "Point", "coordinates" : [ 4, 4 ] } }
{ "loc" : { "type" : "Point", "coordinates" : [ 8, 8 ] } }
{ "loc" : { "type" : "Point", "coordinates" : [ 12, 12 ] } }

E o pipeline de agregação encadeado, apenas duas consultas:
db.geotest.aggregate([
    { "$match": {
        "loc": {
            "$geoWithin": {
                "$box": [ [0,0], [10,10] ]
            }
        }
    }},
    { "$match": {
        "loc": {
            "$geoWithin": {
                "$box": [ [5,5], [20,20] ]
            }
        }
    }}
])

Portanto, se você considerar isso logicamente, o primeiro resultado encontrará os pontos que estão dentro dos limites da caixa inicial ou dos dois primeiros itens. Esses resultados são então atuados pela segunda consulta e, como os novos limites de caixa começam em [5,5] que exclui o primeiro ponto. O terceiro ponto já foi excluído, mas se as restrições de caixa fossem revertidas, o resultado seria apenas o mesmo documento do meio.

Como isso funciona é bastante exclusivo para o $geoWithin operador de consulta em comparação com várias outras funções geográficas:

Assim, os resultados são bons e ruins. Bom porque você pode fazer esse tipo de operação sem um índice no local, mas ruim porque uma vez que o pipeline de agregação alterou os resultados da coleta após a primeira operação de consulta, nenhum índice adicional pode ser usado. Portanto, qualquer benefício de desempenho de um índice é perdido ao mesclar os resultados "conjunto" de qualquer coisa após o Polygon/MultiPolygon inicial conforme suportado.

Por esse motivo, ainda recomendo que você calcule os limites de interseção "fora" da consulta emitida para o MongoDB. Mesmo que a estrutura de agregação possa fazer isso devido à natureza "encadeada" do pipeline, e mesmo que as interseções resultantes fiquem cada vez menores, seu melhor desempenho é uma única consulta com os limites corretos que podem usar todos os benefícios do índice.

Existem vários métodos para fazer isso, mas para referência aqui está uma implementação usando o JSTS biblioteca, que é uma porta JavaScript do popular JTS biblioteca para Java. Pode haver outras ou outras portas de linguagem, mas isso tem uma análise simples de GeoJSON e métodos integrados para coisas como obter os limites de interseção:
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 as representações completas do GeoJSON "Polygon", pois isso se traduz no que o JTS pode entender e trabalhar. É provável que qualquer entrada que você possa receber para uma inscrição real também esteja neste formato, em vez de aplicar conveniências como $box .

Assim pode ser feito com o framework de agregação, ou até mesmo consultas paralelas mesclando o “conjunto” de resultados. Mas, embora a estrutura de agregação possa fazer isso melhor do que mesclar conjuntos de resultados externamente, os melhores resultados sempre virão da computação dos limites primeiro.