使用 geonear 的 Mongoose 聚合

Posted

技术标签:

【中文标题】使用 geonear 的 Mongoose 聚合【英文标题】:Mongoose aggregation with geonear 【发布时间】:2014-08-09 10:59:03 【问题描述】:

我正在尝试使用 Mongoose geoNear 命令实现分页。 geoNear 似乎不支持跳过,我知道聚合会起作用(分页会带来性能成本)。如何将其转换为聚合查询以跳过多个文档?

exports.near = function(req, res) 
    if(req.query.lat && req.query.lng) 
        var point = 
            type: "Point", coordinates : [Number(req.query.lng), Number(req.query.lat)]
        ;
        var queryParams = 
            spherical: true,
            skip: 0,
            limit: 10,
            distanceMultiplier: 6371 // radians to kilometers. (The radius of the Earth is approximately 3,959 miles or 6,371 kilometers.)
        ;
        if(req.query.q) 
            var matcher = new RegExp(req.query.q, "i");
            queryParams.query = 
                $or: [
                    'name': matcher ,
                    'address.full': matcher
                ]
            ;
        
        if(req.query.limit) 
            queryParams.limit = parseInt(req.query.limit, 10);
        
        if(req.query.offset) 
            queryParams.skip = parseInt(req.query.offset, 10);
        
        Venue.geoNear(point, queryParams, function(error, results, stats) 
            // TODO
        );
    
;

【问题讨论】:

【参考方案1】:
location.aggregate("$geoNear":  "near":  "type": "Point", "coordinates": [parseFloat(req.params.lon), parseFloat(req.params.lat)] , "maxDistance":500, 'distanceField' : 'distance', spherical: true
      , 
    function(err, places) 
   if(!err)
           console.log(places);
            res.send('("records":' + JSON.stringify(places) + ');');
        
        else
            console.log(err);
            res.send("error coming")
        
    );`

Above Query working fine in mongo shell but not working in mongoose 

【讨论】:

【参考方案2】:

您可以为此使用聚合框架,并且没有真正的惩罚,因为操作本质上是相同的。

但是,虽然目前 mongoose .find() 方法与等效的 $nearSphere 运算符存在问题,但您始终可以获取原始节点驱动程序连接对象并进行查询。

如果您准备实施一点处理,您甚至不需要丢弃诸如“人口”之类的东西。

这是我的测试数据:

 
    "_id" : "P1",
    "amenity" : "restaurant", 
    "shape" :  
        "type" : "Point", 
        "coordinates" : [ 2, 2 ] 
    

 
    "_id" : "P3",
    "amenity" : "police",
    "shape" :  
        "type" : "Point", 
        "coordinates" : [ 4, 2 ]
    

 
    "_id" : "P4",
    "amenity" : "police",
    "shape" : 
        "type" : "Point",
        "coordinates" : [ 4, 4 ]
    

 
    "_id" : "P2",
    "amenity" : "restaurant",
    "shape" :  
        "type" : "Point",
        "coordinates" : [ 2, 4 ]
    , 
    "info" : ObjectId("539b90543249ff8d18e863fb")

以及处理这个的基本代码:

var mongoose = require('mongoose'),
    async = require('async'),
    Schema = mongoose.Schema;


mongoose.connect('mongodb://localhost');

var infoSchema = new Schema(
  "description": String
);

var shapeSchema = new Schema(
  "_id": String,
  "amenity": String,
  "shape": 
    "type":  "type": String ,
    "coordinates": []
  ,
  "info":  "type": Schema.Types.ObjectId, "ref": "Info" 
);

var Shape = mongoose.model( "Shape", shapeSchema );
var Info = mongoose.model( "Info", infoSchema );


Shape.collection.find(
  
    "shape": 
      "$nearSphere": 
        "$geometry": 
          "type": "Point",
          "coordinates": [ 2, 4 ]
        
      
    
  ,
  
    "skip": 0, "limit": 2
  ,
  function(err,cursor) 

    cursor.toArray(function(err,shapes) 

      Shape.populate( shapes,  path: "info" , function(err,docs) 
        if (err) throw err;

        console.log( JSON.stringify( docs, undefined, 4 ) );
      );

    );

  
);

所以你在光标上使用了 skiplimit 操作,返回了一个光标,甚至将文档处理回“Mongoose Documents”,所以你可以在它们上面调用.populate()之类的函数。

我希望$nearSphere 的当前问题能够很快得到解决。

或者使用聚合代替:

Shape.aggregate(
  [
     "$geoNear": 
        "near": 
          "type": "Point",
          "coordinates": [ 2, 4 ]
        ,
        "spherical": true,
        "distanceField": "dis"
    ,
     "$skip": 0 ,
     "$limit": 2 

  ],
  function(err,shapes) 
    if (err) throw err;
    //console.log( shapes );

    shapes = shapes.map(function(x) 
      delete x.dis;
      return new Shape( x );
    );

    Shape.populate( shapes,  path: "info" , function(err,docs) 
      if (err) throw err;

      console.log( JSON.stringify( docs, undefined, 4 ) );
    );

  
);

您可以在其中执行相同的操作,例如使用 .populate()。两种情况都返回与“填充”字段匹配的结果:


    "_id": "P2",
    "amenity": "restaurant",
    "info": 
        "_id": "539b90543249ff8d18e863fb",
        "description": "Jamies Restaurant",
        "__v": 0
    ,
    "shape": 
        "type": "Point",
        "coordinates": [
            2,
            4
        ]
    
,

    "info": null,
    "_id": "P4",
    "amenity": "police",
    "shape": 
        "type": "Point",
        "coordinates": [
            4,
            4
        ]
    

当然,如果您不需要球面几何计算,那么 $near 运算符与 .find() 的 Mongoose 实现完美配合

【讨论】:

感谢 Neil,我已经更新了我的代码以使用您的聚合示例:pastie.org/9335513 这解决了我的分页问题。但是计算出来的距离发生了变化,你知道为什么会这样吗?另外,我所说的性能成本是这样的:emptysqua.re/blog/paging-geo-mongodb你认为这种分页方式有同样的性能损失吗? @strada 不太清楚你所说的距离变化是什么意思,甚至不知道为什么在除了选择和分页之外没有执行其他操作时需要使用聚合命令。您可以使用标准查询来执行此操作。也许通过“距离”,您没有考虑到聚合方法会发出袭击。分页性能始终与您实际访问的“页面深度”数量有关。跳过的成本通常可以通过“范围查询”来减轻,或者通过将所见文档“列入黑名单”,正如所提到的帖子中所指出的那样。 如何通过查找查询获得距离?

以上是关于使用 geonear 的 Mongoose 聚合的主要内容,如果未能解决你的问题,请参考以下文章

使用 2d 索引,与 $geoNear 聚合

Mongoose/MongoDb 收到错误 geoNear 不是函数

Mongo (+Mongoose) 错误:无法找到 $geoNear 查询的索引

当查询字段为objectId时,geoNear聚合是不是不起作用?

MongoDB $geoNear 聚合管道(使用查询选项和使用 $match 管道操作)给出不同的结果

GraphQL和MongoDB` $ geoNear`聚合