sql >> Databasteknik >  >> NoSQL >> MongoDB

MongoDB - Geospatial skärningspunkt mellan två polygoner

Så när jag ser på det här med ett nytt sinne är svaret att stirra mig i ansiktet. Det viktigaste som du redan har sagt är att du vill hitta "skärningspunkten" mellan två frågor i ett enda svar.

Ett annat sätt att se på detta är att du vill att alla punkter som är bundna av den första frågan ska "inmatas" för den andra frågan, och så vidare efter behov. Det är i huvudsak vad en korsning gör, men logiken är faktiskt bokstavlig.

Så använd bara aggregationsramverket för att kedja de matchande frågorna. För ett enkelt exempel, överväg följande dokument:

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

Och den kedjade aggregeringspipelinen, bara två frågor:

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

Så om du anser det logiskt, kommer det första resultatet att hitta de punkter som faller inom gränserna för den initiala rutan eller de två första objekten. Dessa resultat åtgärdas sedan av den andra frågan, och eftersom de nya boxgränserna börjar på [5,5] det utesluter den första punkten. Den tredje punkten var redan utesluten, men om boxbegränsningarna ändrades skulle resultatet bara bli samma mellandokument.

Hur detta fungerar är ganska unikt för $geoWithin frågeoperator jämfört med olika andra geofunktioner:

Så resultaten är både bra och dåliga. Bra eftersom du kan göra den här typen av operation utan ett index på plats, men dåligt eftersom när aggregeringspipelinen har ändrat insamlingsresultaten efter den första frågeoperationen kan inget ytterligare index användas. Så alla prestandafördelar med ett index går förlorade vid sammanslagning av "set"-resultaten från allt efter den initiala polygonen/multipolygonen som stöds.

Av denna anledning skulle jag fortfarande rekommendera att du beräknar skärningsgränserna "utanför" för frågan som skickas till MongoDB. Även om aggregeringsramverket kan göra detta på grund av pipelinens "kedjade" karaktär, och även om resulterande korsningar blir mindre och mindre, är ditt bästa resultat en enda fråga med rätt gränser som kan använda alla indexfördelar.

Det finns olika metoder för att göra det, men här är en implementering som använder JSTS . bibliotek, som är en JavaScript-port för den populära JTS bibliotek för Java. Det kan finnas andra eller andra språkportar, men detta har enkel GeoJSON-parsning och inbyggda metoder för sådant som att få skärningsgränserna:

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

  });

});

Att använda den fullständiga GeoJSON "Polygon"-representationerna där eftersom detta översätter till vad JTS kan förstå och arbeta med. Chansen är stor att all input som du kan få för en riktig applikation också är i detta format snarare än att använda bekvämligheter som $box .

Så det kan göras med aggregeringsramverket, eller till och med parallella frågor som slår samman "uppsättningen" av resultat. Men även om aggregeringsramverket kan göra det bättre än att slå samman uppsättningar av resultat externt, kommer de bästa resultaten alltid från att beräkna gränserna först.



  1. MongoDB samlar fält utan att känna till alla fält innan

  2. Kopiera och byt namn på ett dokumentfält i MongoDB

  3. MongoDB, lägga till nytt { field :value } i befintligt inbäddat dokument med flernivåpunktsnotation?

  4. Mongoose:Sortera efter kapslat fält