航点匹配查询

时间:2015-03-08 08:21:10

标签: mongodb mongodb-query geospatial

我们收集如下。每个文档代表一个驱动程序的行程,loc属性包含way-points,time属性包含对应于way-points的时间。例如,在旅行A中,驱动程序将在tripA.loc.coordinates[0]

时位于GeoLocation tripA.time[0]
{
    tripId : "Trip A",
    time : [
        "2015-03-08T04:47:43.589Z",
        "2015-03-08T04:48:43.589Z",
        "2015-03-08T04:49:43.589Z",
        "2015-03-08T04:50:43.589Z",
    ],
    loc: {
        type: "MultiPoint",
        coordinates: [
            [ -73.9580, 40.8003 ],
            [ -73.9498, 40.7968 ],
            [ -73.9737, 40.7648 ],
            [ -73.9814, 40.7681 ]
        ]
    }
}
{
    tripId : "Trip B",
    time : [
        "2015-03-08T04:47:43.589Z",
        "2015-03-08T04:48:43.589Z",
        "2015-03-08T04:49:43.589Z",
        "2015-03-08T04:50:43.589Z",
    ],
    loc: {
        type: "MultiPoint",
        coordinates: [
            [ -72.9580, 41.8003 ],
            [ -72.9498, 41.7968 ],
            [ -72.9737, 41.7648 ],
            [ -72.9814, 41.7681 ]
        ]
    }
}

我们想查询从(1km)位置附近开始的旅行" [long1,lat1]"在时间t(+ -10分钟)附近,以[long2,lat2]结束。

是否有简单有效的方法为MongoDB或Elasticsearch制定上述查询?

如果可以,请提供查询。在MongoDB或Elasticsearch中。 (MongoDB更喜欢)

1 个答案:

答案 0 :(得分:1)

这确实是从评论开始的,但显然已经很久了。因此,这是对限制和方法的长期解释。

你要求在这里实现的底线实际上是一个"联合查询"通常定义为两个单独的查询,其中最终结果是" set intersection"每个结果。更简单的说,所选择的"旅行"来自你的"起源"查询匹配您的"目的地中的结果"查询。

在一般的数据库术语中,我们指的是" union"作为"加入"或至少选择一套标准"和"选择另一个必须满足共同的基本分组标识符。

MongoDB中的基点我认为也适用于弹性搜索索引,因为数据存储机制都不支持" join"以任何方式来自直接的单一查询。

此处有另一个MongoDB原则,考虑到您的建议或现有建模,即使使用" array"中指定的项目也是如此。条款,没有办法实现"和"在坐标上进行地理空间搜索的条件,也考虑到您选择建模为GeoJSON" MultiPoint"查询不能选择"该对象的哪个元素匹配"最近的"至。因此"所有点"在考虑"最接近的匹配"时会考虑。

你的意图很清楚。所以我们可以看到"起源"在两个数组"两个数组"在你的文档结构中作为"第一个"每个数组中的元素。代表性数据是"位置"和"时间"对于每个进步的航点#34;在"旅行"。自然地以你的"目的地结束"在每个数组的结尾元素处,当然考虑数据点是"配对"。

我认为逻辑是认为这是存储事物的好方法,但它不遵循您在此处提到的任何存储解决方案的允许查询模式。

正如我已经提到的,这确实是一个"联盟"在意图中,当我看到导致设计的想法时,最好存储这样的东西:

{
    "tripId" : "Trip A",
    "time" : ISODate("2015-03-08T04:47:43.589Z"),
    "loc": {
        "type": "Point",
        "coordinates": [ -73.9580, 40.8003 ]
    },
    "seq": 0
},
{
    "tripId" : "Trip A",
    "time" : ISODate("2015-03-08T04:48:43.589Z"),
    "loc": {
        "type": "Point",
        "coordinates": [ -73.9498, 40.7968 ]
    },
    "seq": 1
},
{
    "tripId" : "Trip A",
    "time" : ISODate("2015-03-08T04:49:43.589Z"),
    "loc": {
        "type": "Point",
        "coordinates": [ -73.9737, 40.7648 ]
    },
    "seq": 2
},
{
    "tripId" : "Trip A",
    "time" : ISODate("2015-03-08T04:50:43.589Z"),
    "loc": {
        "type": "Point",
        "coordinates": [ -73.9814, 40.7681 ]
    },
    "seq": 3,
    "isEnd": true
}

在示例中,我只是将这些文档插入名为" geojunk"的集合中,然后为" loc"发布2dsphere索引。字段:

db.geojunk.ensureIndex({ "loc": "2dsphere" })

然后用" 2"完成此处理。 .aggregate()次查询。 .aggregate()的原因是因为您希望匹配"首先"文件"每次旅行"在每种情况下。这表示查询找到的每个行程的最近航点。那么基本上你想要"合并"这些结果成为某种" hash"结构由" tripId"。

键入

结束逻辑说,如果一个"起源"和目的地"匹配给定"旅行"的查询条件,那么这是整个查询的有效结果。

我在这里给出的代码是一个任意的nodejs实现。主要是因为它是表示在" parallel"中发出查询的良好基础。为了获得最佳性能,我也选择使用nedb作为" hash"的示例。多一点" Mongolike"语法:

var async = require('async'),
    MongoClient = require("mongodb").MongoClient;
    DataStore = require('nedb');


// Common stream upsert handler
function streamProcess(stream,store,callback) {

  stream.on("data",function(data) {
    // Clean "_id" to keep nedb happy
    data.trip = data._id;
    delete data._id;


    // Upsert to store
    store.update(
      { "trip": data.trip },
      {
        "$push": {
          "time": data.time,
          "loc": data.loc
        }
      },
      { "upsert": true },
      function(err,num) {
        if (err) callback(err);
      }
    );

  });

  stream.on("err",callback)

  stream.on("end",callback);

}

MongoClient.connect('mongodb://localhost/test',function(err,db) {
  if (err) throw err;

  db.collection('geojunk',function(err,collection) {
    if (err) throw err;

  var store = new DataStore();

    // Parallel execution
    async.parallel(
      [
        // Match origin trips
        function(callback) {
          var stream = collection.aggregate(
            [
              { "$geoNear": {
                "near": {
                  "type": "Point",
                  "coordinates": [ -73.9580, 40.8003 ],
                },
                "query": {
                  "time": {
                    "$gte": new Date("2015-03-08T04:40:00.000Z"),
                    "$lte": new Date("2015-03-08T04:50:00.000Z")
                  },
                  "seq": 0
                },
                "maxDistance": 1000,
                "distanceField": "distance",
                "spherical": true
              }},
              { "$group": {
                "_id": "$tripId",
                "time": { "$first": "$time" },
                "loc": { "$first": "$loc" }
              }}
            ],
            { "cursor": { "batchSize": 1 } }
          );
          streamProcess(stream,store,callback);
        },

        // Match destination trips
        function(callback) {
          var stream = collection.aggregate(
            [
              { "$geoNear": {
                "near": {
                  "type": "Point",
                  "coordinates": [ -73.9814, 40.7681 ]
                },
                "query": { "isEnd": true },
                "maxDistance": 1000,
                "distanceField": "distance",
                "spherical": true
              }},
              { "$group": {
                "_id": "$tripId",
                "time": { "$first": "$time" },
                "loc": { "$first": "$loc" }
              }}
            ],
            { "cursor": { "batchSize": 25 } }
          );
          streamProcess(stream,store,callback);
        }

      ],
      function(err) {
        if (err) throw err;

        // Just documents that matched origin and destination
        store.find({ "loc": { "$size": 2 }},{ "_id": 0 },function(err,result) {
          if (err) throw err;
          console.log( JSON.stringify( result, undefined, 2 ) );
          db.close();
        });
      }
    );

  });

});

在我列出的样本数据上,这将返回:

[
  {
    "trip": "Trip A",
    "time": [
      "2015-03-08T04:47:43.589Z",
      "2015-03-08T04:50:43.589Z"
    ],
    "loc": [
      {
        "type": "Point",
        "coordinates": [
          -73.958,
          40.8003
        ]
      },
      {
        "type": "Point",
        "coordinates": [
          -73.9814,
          40.7681
        ]
      }
    ]
  }
]

因此它找到了最接近被查询位置的起点和终点,也是一个"起源"在所需的时间内和定义为目的地的东西,即" isEnd"。

因此$geoNear操作与返回的结果匹配是最接近点和其他条件的文档。 $group阶段是必需的,因为同一行程中的其他文件可以"可能"符合条件,所以它只是一种确定方式。 $first运算符确保已经排序"排序"每个"旅行"结果将只包含一个结果。如果你真的"肯定"在条件不会发生的情况下,您可以在聚合之外使用标准$nearSphere查询。所以我在这里安全地玩。

有一点需要注意,即使包含在" nedb"虽然它确实支持将输出转储到磁盘,但数据仍然在内存中累积。如果你期望大的结果,那么而不是这种类型的"哈希表"实现时,您需要以类似于另一个mongodb集合的方式输出,并从那里检索匹配的结果。

但这并没有改变整体逻辑,而另一个原因是使用" nedb"证明,因为你会" upsert"以相同的方式对结果集合中的文档进行处理。