Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MongoDB/Mongoose - Aggregation with geoNear & subdocuments

I'm using the node-geoip module and performing an aggregation query. The schema against which I am performing the query looks like this:

var mongoose = require('mongoose');
require('./location.js');

module.exports = mongoose.model('Region',{
    attr1: Number,
    attr2: String,
    attr3: String,
    locations:[mongoose.model('Location').schema]
});

and

var mongoose = require('mongoose');

module.exports = mongoose.model('Location',{
    attr1: Number,
    latlong: { type: [Number], index: '2d' },
});

I need to perform a $geoNear operation in an aggregation query, but I'm running into a few problems. First, here is my aggregation method:

var region = require('../models/region');

var geo = geoip.lookup(req.ip);

region.aggregate([
    {$unwind: "$locations"},
    {$project: {
        attr1 : 1,
        attr2 : 1,
        locations : 1,
        lower : {"$cond" : [{$lt: [ '$locations.attr1', '$attr1']}, 1, 0]}
    }},
     {
      $geoNear: {
         near: { type:"Point", '$locations.latlong': geo.ll },
         maxDistance: 40000,
         distanceField: "dist.calculated"
      }
     },
    { $sort: { 'locations.attr1': -1 } },
    {$match : {lower : 1}},
    { $limit: 1 }
], function(err,f){...});

The first problem I'm getting is that apparently geoNear must be in the first stage of the pipeline: exception: $geoNear is only allowed as the first pipeline stage. So my question is, can I perform a geoNear search in the subdocuments without unwinding them? If so, how?

The other error message I get is errmsg: \"exception: 'near' field must be point\". What does this mean and what does it imply for my code? I have tried using near as:

near: { type:"Point", '$locations.latlong': geo.ll },
like image 836
jordan Avatar asked Mar 25 '15 16:03

jordan


1 Answers

First a disclaimer: I am not a Node/Mongoose expert so I am hoping you can translate the general formats to Node/Mongoose.

For the error:

 errmsg: "exception: 'near' field must be point"

For a '2d' index this cannot be a GeoJson point and instead needs to be a "legacy coordinate pair". e.g.,

{
  "$geoNear": {
    "near": geo.ll,
    "maxDistance": 40000,
    "distanceField": "dist.calculated"
  }
}

If you want to use GeoJSON you will need to use a '2dsphere' index.

With that change the $geoNear query will work with the array of points in the query. An example in the shell:

> db.test.createIndex({ "locations": "2d" })
> db.test.insert({ "locations": [ [1, 2], [10, 20] ] });
> db.test.insert({ "locations": [ [100, 100], [180, 180] ] });
> db.test.aggregate([{
  "$geoNear": {
    "near": [10, 10],
    "maxDistance": 40000,
    "distanceField": "dist.calculated",
    num: 1
  }
}]);
{
  "result": [{
    "_id": ObjectId("552aaf7478dd9c25a3472a2a"),
    "locations": [
      [
        1,
        2
      ],
      [
        10,
        20
      ]
    ],
    "dist": {
      "calculated": 10
    }
  }],
  "ok": 1
}

Note that you only get a single distance per document (the closest point) which is semantically not the same as doing the unwind and then determining the distance to every point. I cannot be sure if that is important for your use case.

like image 119
Rob Moore Avatar answered Sep 30 '22 23:09

Rob Moore