如何使用mongoose从mongodb collection中查找最近的位置数据?

时间:2016-04-10 20:00:39

标签: node.js mongodb mongoose mean-stack node-mongodb-native

  

我正在寻找branchId [Array]的最近分支列表,   例如:[109,110,115]

// My Schema
var BranchSchema = new Schema({
branchId : { type : Schema.Types.ObjectId },
branchName : { type : String },
branchAddress : {
    Address:{ type : String, required : false},
    City :{ type : String, required : false },
    Country : { type : String , required : false}
},
loc: {
    type: [Number],  // [<longitude>, <latitude>]
    index: '2d'      // create the geospatial index
}

});

    clubList.find({
            $and:[{ 
                    loc: { $near: coords, $maxDistance: maxDistance }
                },
                {_id :{ $in: branchId}} //branchId :  [108,109,110]
            ]},function(err, branchData){
            if (err) 
            {
               console.log(err);
            }
           else
           {
               console.log(branchData); 
            // Expecting sort near by branch of array [108,109,110]
           }
  

数据应按近似顺序排序。

1 个答案:

答案 0 :(得分:2)

看起来您确实要求$or条件而不是$and。考虑到你是否真的想要“两个”结果,这是有道理的:

  • $near查询结果
  • “以及”与_id值的$in条件匹配的文档。

作为$and条件,查询可以写为:

clublist.find({
    "loc": { "$near": coords, "$maxDistance": maxDistance },
    "_id": { "$in": branchId }    // branchId is the array [108,109,110]
},function(err,branchData) {
  /* Data needs to be "both" near as well has having the supplied 
     _id values. So at maximum only "three" results, provided all
     "three" are within the maxDistance
   */
})

所以考虑到这一点似乎很可能意味着$or,但是还有另外一个问题。

您不能将$or用于需要地理空间索引的$near等查询操作。所以像这样的操作会出错:

clublist.find({
    "$or": [
        { "loc": { "$near": coords, "$maxDistance": maxDistance } },
        { "_id": { "$in": branchId } }    // branchId is the array [108,109,110]
    ]
},function(err,branchData) {
   // err:  "geoNear must be top-level expr"
})

为此,您实际上需要运行单独的查询,然后合并结果。由于这些实际上是单独的结果,因此您可能还希望包含“距离”以便对组合进行“排序”。

一个很好的工具是async.parallel,因为它允许您从不同的操作中获取结果并在一个回调中工作。还有聚合$geoNear,它实际上将“距离”“投射”到结果中,这是对整体结果进行排序的重要部分:

async.parallel(
  [
    function(callback) {
      clublist.aggregate(
        [
          { "$geoNear": {
            "near": coords,
            "distanceField": "distance",
            "maxDistance": maxDistance
          }},
          { "$limit": 50 }
        ],
        callback
      )
    },
    function(callback) {
      clublist.aggregate(
        [
          { "$geoNear": {
            "near": coords,
            "distanceField": "distance",
            "query": { "_id": { "$in": branchId } }
          }}
        ],
        callback
      )
    }
  ],
  function(err,results) {
    if (err) throw err;
    var branchData = [];
    branchData = results.forEach(function(el) { 
       branchData = branchData.concat(el);
    });

    // Sort and return the 50 nearest
    branchData = branchData.sort(function(a,b) {
      return a.distance > b.distance
    }).slice(0,50)
  }
)

在这种情况下,您将单独运行每个查询,其中一个通过"maxDistance"反对结果,另一个通过$in的参数进行限制。由于“组合”结果将大于设置的“限制”,您需要按距离对结果进行排序,并从组合中返回总限制。

这就是你想要考虑_id选择的方法,但结果实际上可能会返回一个“距离”,否则不会包含在$near内。

但是,如果您的意图总是在结果的顶部_id选择,那么您可以使用常规查询并为这些结果“注入”0的距离:

async.parallel(
  [
    function(callback) {
      clublist.aggregate(
        [
          { "$geoNear": {
            "near": coords,
            "distanceField": "distance",
            "maxDistance": maxDistance
          }},
          { "$limit": 50 }
        ],
        callback
      )
    },
    function(callback) {
      clublist.find({ "_id": { "$in": branchId }}).toArray(function(err,branchData) {
        branchData = branchData.map(function(doc) {
          doc.distance = 0;
        });
        callback(err,branchData);
      })
    }
  ],
  function(err,results) {
    if (err) throw err;
    var branchData = [];
    branchData = results.forEach(function(el) { 
       branchData = branchData.concat(el);
    });

    // Sort and return the 50 nearest
    branchData = branchData.sort(function(a,b) {
      return a.distance > b.distance
    }).slice(0,50)
  }
)

然后,相同的排序将这些值保留在顶部,并返回来自其他查询操作的任何结果。

当然,“两个”查询中返回指定_id值的“机会”。但是,如果是这种情况,甚至可能,那么在实际执行_id操作之前或通常在返回最终结果之前,您可以简单地匹配任何“重复”.slice()值的数组内容。这是一个简单的过程,使用具有唯一键的对象,然后返回到数组。

类似的东西:

function(err,results) {
    if (err) throw err;
    var branchData = [];
    branchData = results.forEach(function(el) { 
       branchData = branchData.concat(el);
    });

    var uniqueBranch = {};

    // Just keep unique _id for smallest distance value
    for ( var idx in branchData ) {
      if ( ( uniqueBranch.hasOwnProperty(branchData[idx]._id) ) 
           && ( branchData[idxx].distance > uniqueBranch[branchData[idx]._id].distance ) )
         continue;
      uniqueBranch[branchData[idx]._id] = branchData[idx];
    }

    // Go back to array, sort and slice
    branchData = Object.keys(uniqueBranch).map(function(k) {
        return uniqueBranch[k];
    }).sort(function(a,b) {
        return a.distance > b.distance;
    }).slice(0,50);

}