Mongoose aggregation with geonear

2019-02-15 23:37发布

I'm trying to implement paging with Mongoose geoNear command. It seem skip is not supported with geoNear, and I understand aggregation would work (with paging with a performance cost). How can I convert this to aggregation query to skip a number of documents?

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

2条回答
手持菜刀,她持情操
2楼-- · 2019-02-16 00:17

You can use the aggregation framework for this and there is no real penalty as the operations are essentially the same.

But while the mongoose .find() method presently has a problem with the $nearSphere operator which is equivalent, you can always grab the raw node driver connection object and do your query.

You do not even need to throw away things like "population" if you are prepared to implement a little handling.

Here is my test data:

{ 
    "_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")
}

And the basic code to handle this:

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

    });

  }
);

So there you got usage of both the skip and limit operations on the cursor, had a cursor returned and even processed the documents back into "Mongoose Documents" so you can call functions like .populate() on them.

I would expect the current issue with $nearSphere to be fixed relatively soon though.

Or using aggregate instead:

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

  }
);

Where you can do the same things such as use .populate(). Both cases return results like this with the "populated" field matched:

{
    "_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
        ]
    }
}

Of course if you do not need the spherical geometry calculation then the $near operator works perfectly fine with the Mongoose implementation of .find()

查看更多
在下西门庆
3楼-- · 2019-02-16 00:40
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 
查看更多
登录 后发表回答