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

MongoDB - Geospatial skæring af to polygoner

Så ser man på dette med et frisk sind, er svaret at stirre mig i ansigtet. Det vigtigste, du allerede har angivet, er, at du ønsker at finde "skæringspunktet" mellem to forespørgsler i et enkelt svar.

En anden måde at se dette på er, at du ønsker, at alle de punkter, der er bundet af den første forespørgsel, skal "input" til den anden forespørgsel, og så videre efter behov. Det er i bund og grund, hvad et kryds gør, men logikken er faktisk bogstavelig.

Så brug bare aggregationsramme at kæde de matchende forespørgsler. Som et simpelt eksempel kan du overveje følgende dokumenter:

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

Og den kædede aggregeringspipeline, kun to forespørgsler:

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

Så hvis du overvejer det logisk, vil det første resultat finde de punkter, der falder inden for grænserne for den indledende boks eller de to første elementer. Disse resultater reageres derefter af den anden forespørgsel, og da de nye boksgrænser starter ved [5,5] det udelukker det første punkt. Det tredje punkt var allerede udelukket, men hvis boksbegrænsningerne blev omvendt, ville resultatet kun være det samme midterste dokument.

Hvordan dette fungerer, er ret unikt for $geoWithin forespørgselsoperatør sammenlignet med forskellige andre geofunktioner:

Så resultaterne er både gode og dårlige. Godt, at du kan udføre denne type operation uden et indeks på plads, men dårligt, fordi når først aggregeringspipelinen har ændret indsamlingsresultaterne efter den første forespørgselsoperation, kan der ikke bruges yderligere indeks. Så enhver præstationsfordel ved et indeks går tabt ved at flette "sæt"-resultaterne fra alt efter den indledende Polygon/MultiPolygon som understøttet.

Af denne grund vil jeg stadig anbefale, at du beregner skæringsgrænserne "uden for" for den forespørgsel, der er udstedt til MongoDB. Selvom aggregeringsrammen kan gøre dette på grund af pipelinens "kædede" karakter, og selvom resulterende skæringspunkter bliver mindre og mindre, er din bedste ydeevne en enkelt forespørgsel med de korrekte grænser, der kan bruge alle indeksfordele.

Der er forskellige metoder til at gøre det, men til reference er her en implementering ved hjælp af JSTS bibliotek, som er en JavaScript-port af den populære JTS bibliotek til Java. Der kan være andre eller andre sprogporte, men dette har simpel GeoJSON-parsing og indbyggede metoder til sådanne ting som at få skæringsgrænserne:

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

  });

});

Brug af de fulde GeoJSON "Polygon"-repræsentationer der, da dette oversættes til, hvad JTS kan forstå og arbejde med. Chancerne er, at ethvert input, du måtte modtage for en rigtig applikation, også ville være i dette format i stedet for at anvende bekvemmeligheder såsom $box .

Så det kan gøres med aggregeringsrammen, eller endda parallelle forespørgsler, der fusionerer "sættet" af resultater. Men selvom aggregeringsrammen kan gøre det bedre end at flette sæt af resultater eksternt, vil de bedste resultater altid komme fra at beregne grænserne først.



  1. Meteor sender et id ind i et link

  2. Opdatering af Nested Array Mongoose

  3. MongoDB regex matching problemer

  4. At finde flere ord med find() i MongoDB