sql >> Databáze >  >> NoSQL >> MongoDB

MongoDB - Geoprostorový průnik dvou polygonů

Takže když se na to podívám s čerstvou myslí, odpověď mi zírá do tváře. Klíčová věc, kterou jste již uvedli, je, že chcete najít "průnik" dvou dotazů v jediné odpovědi.

Jiný způsob, jak se na to podívat, je, že chcete, aby všechny body vázané prvním dotazem byly „vstupem“ pro druhý dotaz a tak dále podle potřeby. To je v podstatě to, co dělá křižovatka, ale logika je ve skutečnosti doslovná.

Stačí tedy použít rámec agregace k řetězení odpovídajících dotazů. Pro jednoduchý příklad zvažte následující dokumenty:

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

A zřetězený agregační kanál, jen dva dotazy:

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

Takže pokud to logicky uvážíte, první výsledek najde body, které spadají do mezí počátečního pole nebo prvních dvou položek. Na tyto výsledky se pak reaguje druhým dotazem, a protože nové hranice rámečku začínají na [5,5] to vylučuje první bod. Třetí bod byl již vyloučen, ale pokud by se omezení polí obrátilo, výsledkem by byl pouze stejný prostřední dokument.

Jak to funguje, je zcela unikátní pro $geoWithin operátor dotazu ve srovnání s různými jinými geo funkcemi:

Takže výsledky jsou dobré i špatné. Dobré v tom, že tento typ operace můžete provést bez indexu, ale špatné, protože jakmile agregační kanál po první operaci dotazu změní výsledky kolekce, nelze použít žádný další index. Takže jakákoliv výkonnost indexu je ztracena při sloučení „setových“ výsledků z čehokoli po počátečním polygonu/multipolygonu, jak je podporováno.

Z tohoto důvodu bych stále doporučil, abyste vypočítali hranice křižovatky "mimo" dotazu zaslaného MongoDB. Přestože to agregační rámec dokáže díky „zřetězené“ povaze potrubí, a přestože se výsledné průniky budou stále zmenšovat, nejlepším výkonem je jediný dotaz se správnými hranicemi, který dokáže využít všechny výhody indexu.

Existují různé metody, jak toho dosáhnout, ale zde je pro referenci implementace pomocí JSTS knihovna, což je JavaScriptový port populárního JTS knihovna pro Javu. Mohou existovat další nebo jiné jazykové porty, ale toto má jednoduchou analýzu GeoJSON a vestavěné metody pro takové věci, jako je získání hranic křižovatky:

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

  });

});

Použití úplných reprezentací GeoJSON "Polygon" tam, protože to znamená to, čemu JTS rozumí a s čím může pracovat. Je pravděpodobné, že jakýkoli vstup, který byste mohli získat pro skutečnou aplikaci, bude také v tomto formátu, spíše než s použitím vymožeností, jako je $box .

Takže to lze udělat pomocí agregačního rámce nebo dokonce paralelních dotazů slučujících „množinu“ výsledků. Ale i když to agregační rámec může dělat lépe než externí slučování sad výsledků, nejlepší výsledky vždy pocházejí nejprve z výpočtu hranic.



  1. Migrace z Tokumx 1.5 na Percona Server pro mongodb 3.11

  2. Proč se KEYS nedoporučuje používat v Redis?

  3. Proč mongoDB používá objectID?

  4. Spuštění PHP Nelze načíst dynamickou knihovnu php_mongo.dll