8

This is my controller file locations.js

var mongoose = require('mongoose');
var Loc = mongoose.model('location');

module.exports.locationsListByDistance = function(req, res) {
  var lng = parseFloat(req.query.lng);
  var lat = parseFloat(req.query.lat);
  var point = {
    type: "Point",
    coordinates: [lng, lat]
  };
  var geoOptions = {
    spherical: true,
    maxDistance: 1000
  };

  Loc.geoNear(point, geoOptions, function (err, results, stats) {
    console.log(results);
  });
};

My model file locations.js

var mongoose = require('mongoose');

var reviewSchema = new mongoose.Schema({
    author: String,
    rating: {
        type: Number,
        required: true,
        min: 0,
        max: 5
    },
    reviewText: String,
    createdOn: {
        type: Date,
        "default": Date.now
    }
});

var openingTimeSchema = new mongoose.Schema({
    days: {
        type: String,
        required: true
    },
    opening: String,
    closing: String,
    closed: {
        type: Boolean,
        required: true
    }
});

var locationSchema = new mongoose.Schema({
    name: {
        type: String,
        required: true
    },
    address: String,
    rating: {
        type: Number,
        "default": 0,
        min: 0,
        max: 5
    },
    facilities: [String],
    // Always store coordinates longitude, latitude order.
    coords: {
        type: [Number],
        index: '2dsphere'
    },
    openingTimes: [openingTimeSchema],
    reviews: [reviewSchema]
});

mongoose.model('location', locationSchema, 'locations');

Whenever I run http://localhost:3000/api/locations?lng=-0.9690884&lat=51.455041 I get error geoNear is not a function

TypeError: Loc.geoNear is not a function at module.exports.locationsListByDistance (/home/shackers/Projects/mean/loc8r/app_api/controllers/locations.js:51:7) at Layer.handle [as handle_request] (/home/shackers/Projects/mean/loc8r/node_modules/express/lib/router/layer.js:95:5) at next (/home/shackers/Projects/mean/loc8r/node_modules/express/lib/router/route.js:137:13) at Route.dispatch (/home/shackers/Projects/mean/loc8r/node_modules/express/lib/router/route.js:112:3) at Layer.handle [as handle_request] (/home/shackers/Projects/mean/loc8r/node_modules/express/lib/router/layer.js:95:5) at /home/shackers/Projects/mean/loc8r/node_modules/express/lib/router/index.js:281:22 at Function.process_params (/home/shackers/Projects/mean/loc8r/node_modules/express/lib/router/index.js:335:12) at next (/home/shackers/Projects/mean/loc8r/node_modules/express/lib/router/index.js:275:10) at Function.handle (/home/shackers/Projects/mean/loc8r/node_modules/express/lib/router/index.js:174:3) at router (/home/shackers/Projects/mean/loc8r/node_modules/express/lib/router/index.js:47:12) at Layer.handle [as handle_request] (/home/shackers/Projects/mean/loc8r/node_modules/express/lib/router/layer.js:95:5) at trim_prefix (/home/shackers/Projects/mean/loc8r/node_modules/express/lib/router/index.js:317:13) at /home/shackers/Projects/mean/loc8r/node_modules/express/lib/router/index.js:284:7 at Function.process_params (/home/shackers/Projects/mean/loc8r/node_modules/express/lib/router/index.js:335:12) at next (/home/shackers/Projects/mean/loc8r/node_modules/express/lib/router/index.js:275:10) at /home/shackers/Projects/mean/loc8r/node_modules/express/lib/router/index.js:635:15

This are versions of dependencies i am using:

  • node : 8.9.3 npm : 5.5.1 express : 4.15.5 mongoose : 5.0.0 mongoDb : 3.6.1
Sid B
  • 83
  • 1
  • 7

12 Answers12

9
router.get('/', () => {
    Loc.aggregate([
        {
            $geoNear: {
                near: 'Point',
                distanceField: "dist.calculated",
                maxDistance: 100000,
                spherical: true                
            }
        }
    ]).then(function(err, results, next){
        res.send();
    }).catch(next);
});

Ref:- https://docs.mongodb.com/manual/reference/command/geoNear/

dee
  • 2,244
  • 3
  • 13
  • 33
  • i have the same problem here but, i don't understand the use of aggregate, can explaneid for ? – krekto Apr 11 '18 at 23:40
  • @krekto I'm not a Mongo expert but here is the relevant [documentation](https://docs.mongodb.com/manual/core/aggregation-pipeline/) for Mongo's aggregation pipeline. I believe the aggregation pipeline is just an alternate way of "finding" documents within the database and happens to have geoNear functionality as well. Others are welcome to correct me if I'm wrong. – ptk Apr 12 '18 at 04:12
  • 2
    @krekto-db.students.aggregate([ { $grades: { rank: 'A', group: { _id: "$student_id", total: { $sum: "$score" } }, sort: { total: -1 } } } ]) Code beautify/Indent the this example.The above operation selects student documents with rank equal to "A", groups the matching documents by the student_id field and calculates the total for each student_id field from the sum of the amount field, and sorts the results by the total field in descending order. I hope this helps to understand aggregate. – dee Apr 13 '18 at 11:05
  • @deechris27 get it , tx – krekto Apr 13 '18 at 14:49
8

This error is happening because .geoNear used to be supported, but is no longer supported as of Mongoose 5, which uses the Node MongoDB v3 driver.

The issue is documented in the Migrating to Mongoose 5 document, which in turn links to the MongoDB 3 drive release notes which provides this statement about recommend replacements:

The functionality of the geoNear command is duplicated elsewhere in the language, in the $near/$nearSphere query operators on unsharded collections, and in the $geoNear aggregation stage on all collections.

Effectively, the official docs are endorsing kind of use of $geoNear documented in other answers.

Mark Stosberg
  • 12,961
  • 6
  • 44
  • 49
3

I'm having the exact same problem and I've abandoned the approach I was using before (which looks like you were having too). The following is an alternative that does not throw an error and should give you the same result you were after using Loc.geoNear:

Loc.aggregate(
        [
            {
                '$geoNear': {
                    'near': point,
                    'spherical': true,
                    'distanceField': 'dist',
                    'maxDistance': 1000
                }
            }
        ],
        function(err, results) {
            // do what you want with the results here
        }
    )
ptk
  • 6,835
  • 14
  • 45
  • 91
  • i have the same problem here but, i don't understand the use of aggregate, can explaneid for ? – krekto Apr 11 '18 at 23:40
3

Apparently I'm in the same book (Getting Mean, Manning) and running into roughly the same issues. This seems to work for me:

var mongoose = require('mongoose');
var Loc = mongoose.model('Location');

var sendJSONresponse = function(res, status, content) {
  res.status(status);
  res.json(content);
};

var theEarth = (function() {
  console.log('theEarth');
  var earthRadius = 6371; // km, miles is 3959

  var getDistanceFromRads = function(rads) {
    return parseFloat(rads * earthRadius);
  };

  var getRadsFromDistance = function(distance) {
    return parseFloat(distance / earthRadius);
  };

  return {
    getDistanceFromRads: getDistanceFromRads,
    getRadsFromDistance: getRadsFromDistance
  };
})();

/* GET list of locations */
module.exports.locationsListByDistance = function(req, res) {
  console.log('locationsListByDistance:');
  var lng = parseFloat(req.query.lng);
  var lat = parseFloat(req.query.lat);
  var maxDistance = parseFloat(req.query.maxDistance);
  var point = {
    type: "Point",
    coordinates: [lng, lat]
  };
  console.log('point: ' + point)
  var geoOptions = {
    spherical: true,
    maxDistance: theEarth.getRadsFromDistance(maxDistance),
    num: 10
  };
  console.log('geoOptions: ' + geoOptions);
  if ((!lng && lng!==0) || (!lat && lat!==0) || ! maxDistance) {
    console.log('locationsListByDistance missing params');
    sendJSONresponse(res, 404, {
      "message": "lng, lat and maxDistance query parameters are all required"
    });
    return;
  } else {
    console.log('locationsListByDistance running...');
    Loc.aggregate(
      [{
        '$geoNear': {
          'near': point,
          'spherical': true,
          'distanceField': 'dist.calculated',
          'maxDistance': maxDistance
        }
      }],
      function(err, results) {
        if (err) {
          sendJSONresponse(res, 404, err);
        } else {
          locations = buildLocationList(req, res, results);
          sendJSONresponse(res, 200, locations);
        }
      }
    )
  };
};

var buildLocationList = function(req, res, results) {
  console.log('buildLocationList:');
  var locations = [];
  results.forEach(function(doc) {
      locations.push({
        distance: doc.dist.calculated,
        name: doc.name,
        address: doc.address,
        rating: doc.rating,
        facilities: doc.facilities,
        _id: doc._id
      });
  });
  return locations;
};

returns a result list similar to such:

[
    {
        "distance": 0,
        "name": "Rathaus",
        "address": "Markt",
        "rating": 0,
        "facilities": [
            "museum"
        ],
        "_id": "5a9366517775811a449e503e"
    },
    {
        "distance": 61.77676881925853,
        "name": "Haus Löwenstein",
        "address": "",
        "rating": 0,
        "facilities": [
            "museum"
        ],
        "_id": "5a9366517775811a449e5045"
    },
    {
        "distance": 63.03445976427102,
        "name": "Goldener Schwan",
        "address": "Markt 37",
        "rating": 0,
        "facilities": [
            "restaurant"
        ],
        "_id": "5a9366517775811a449e5052"
    },
    {
        "distance": 66.60375653163021,
        "name": "Klein Printenbäckerei",
        "address": "Krämerstraße 12",
        "rating": 0,
        "facilities": [
            "supermarket"
        ],
        "_id": "5a9366517775811a449e504d"
    },
    {
        "distance": 74.91278395082011,
        "name": "Couven-Museum",
        "address": "Hühnermarkt 17",
        "rating": 0,
        "facilities": [
            "museum"
        ],
        "_id": "5a9366517775811a449e5042"
    },
    {
        "distance": 132.2939512054143,
        "name": "Cathedral Treasury",
        "address": "Johannes-Paul-II.-Straße",
        "rating": 0,
        "facilities": [
            "museum"
        ],
        "_id": "5a9366517775811a449e503d"
    },
    {
        "distance": 152.11867357742042,
        "name": "Aachen Cathedral",
        "address": "Domhof 1",
        "rating": 0,
        "facilities": [
            "museum"
        ],
        "_id": "5a9366517775811a449e503c"
    },
    {
        "distance": 155.92015153163268,
        "name": "International Newspaper Museum",
        "address": "Pontstraße 13",
        "rating": 0,
        "facilities": [
            "museum"
        ],
        "_id": "5a9366517775811a449e5040"
    },
    {
        "distance": 175.0857109968383,
        "name": "Nobis Printen",
        "address": "Münsterplatz 3",
        "rating": 0,
        "facilities": [
            "supermarket"
        ],
        "_id": "5a9366517775811a449e504c"
    },
    {
        "distance": 179.32348875834543,
        "name": "Grashaus",
        "address": "Fischmarkt",
        "rating": 0,
        "facilities": [
            "museum"
        ],
        "_id": "5a9366517775811a449e5044"
    },
    {
        "distance": 189.8675948747873,
        "name": "Maranello",
        "address": "Pontstraße 23",
        "rating": 0,
        "facilities": [
            "restaurant"
        ],
        "_id": "5a9366517775811a449e5057"
    },
    {
        "distance": 198.2239741555585,
        "name": "Carlos I",
        "address": "Rennbahn 1",
        "rating": 0,
        "facilities": [
            "restaurant"
        ],
        "_id": "5a9366517775811a449e5055"
    }
]

Not sure how accurate it is - got a list of addresses loaded and not 100% sure what's close to what in the random mess... but it returns a list and I'll test correctness somehow at some point.

WernerCD
  • 2,137
  • 6
  • 31
  • 51
  • 1
    the object `geoOptions` is no longer required in your code because you are using already the `$geoNear` properties inside the aggregation without passing the object declared right below the console log with your point. – rags2riches-prog May 08 '18 at 19:24
  • 1
    one additional thing, you also need to change the data model & set: data model => `coords: {type: {type: String, default: "Point"}, coordinates: {type: [Number]}}` data set => `"coords" : { "type": "Point", "coordinates": [-0.9690884, 51.455041] }` – bugrasan May 06 '19 at 12:48
2

I found the solution. Just downgrade mongoose and install version 4.9.1. Latest release of mongoose does not support Loc.geoNear

npm remove mongoose
npm install mongoose@4.9.1
2

More straightforward IMO, than the previous two answers in the Grider's course is:

  index(req, res, next) {
    const { lng, lat } = req.query;
    Driver.find({
      'geometry.coordinates': {
        $nearSphere: {
          $geometry: {
            type: 'Point',
            coordinates:[parseFloat(lng), parseFloat(lat)]
          },
          $maxDistance: 200000,
        },
      }
    })
    .then(drivers => res.send(drivers))
    .catch(next);
  }

This is in the spirit of the original definition he gives and uses the new functions which do the same thing as the old geoNear, except they've split out the spherical and non-spherical versions now. You'll need:

    beforeEach((done) => {
  const { drivers } = mongoose.connection.collections;
  drivers.drop()
    .then(() => drivers.createIndex({ 'geometry.coordinates': '2dsphere' }))
    .then(() => done())
    .catch(() => done());
};

In the test helper as mentioned before.

Sam Keays
  • 736
  • 1
  • 5
  • 17
  • The best & easiest way out. I was using `$geoNear` **aggregate** but when I go through this solution, I replaced aggregate completely with this simplest solution – TalESid Aug 18 '20 at 22:46
0

I think you're looking for this, please correct me if there is mistake there.

module.exports.locationsListBydistance = function (req, res) {
    var lng = parseFloat(req.query.lng);
    var lat = parseFloat(req.query.lat);
    Loc.aggregate(
        [{
            $geoNear: {
                'near': {'type':'Point', 'coordinates':[lng, lat]},
                'spherical': true,
                'maxdistance': theEarth.getRadsFromDistance(20),
                'num':10,
                'distanceField': 'dist' 
            }
        }
        ], function(err, results) {
            var locations = [];
            console.log(results);
            results.forEach(function (doc) {
                locations.push({
                    distance: theEarth.getDistanceFromRads(doc.dist),
                    name: doc.name,
                    address: doc.address,
                    facilities: doc.facilities,
                    rating: doc.rating,
                    _id: doc._id
                });
            });
            sendJsonResponse(res, 200, locations);
        });
};
Alex M
  • 2,756
  • 7
  • 29
  • 35
rash
  • 141
  • 1
  • 7
0
router.get('/',function(req,res,next){
    Loc.aggregate([
        {
            $geoNear: {
                near: {type:'Point', coordinates:[parseFloat(req.query.lng), parseFloat(req.query.lat)]},
                distanceField: "dist.calculated",
                maxDistance: 1000,
                spherical: true                
            }
        }
    ]).then(function(Locs){
        res.send(Locs)
    })
})
0

Model.geoNear() has been removed because the MongoDB driver no longer supports it

MELLAL Fethi
  • 137
  • 1
  • 5
0

Model.geoNear() has been removed because the MongoDB driver no longer supports it, so you should use the aggregation method for example : for mongoose v4.2: my code was like this :

Modal.geoNear(
    {type: 'Point', coordinates: [parseFloat(req.query.lng), parseFloat(req.query.lat)]},
    {maxDistance: 100000, spherical: true}
)
.then(results => {
    res.send(results);
})

for the latest mongoose version : the code is :

 Modal.aggregate([{
        $geoNear: {
            near: {
                type: 'Point',
                coordinates: [parseFloat(req.query.lng), parseFloat(req.query.lat)]
            },
            distanceField: 'distance',
            maxDistance: 100000,
            spherical: true
        }
    }])
    .then(results => {
        res.send(results);
    })

Hope i answer or solve your problem, happy coding :D

MELLAL Fethi
  • 137
  • 1
  • 5
-1

The answer given by user : phao5814 is quite right I tried it out and must say It worked out well for me

Naved Ahmad
  • 783
  • 8
  • 7
-1
 index(req, res, next) 
{
   const { lng, lat } = req.query;
   Driver.aggregate([
   {
     '$geoNear': {
                    "near": { 'type': 'Point', 
                    'coordinates': [parseFloat(lng), parseFloat(lat)] },
                    "spherical": true, 
                    "distanceField": 'dist', 
                    "maxDistance": 200000
                }
            }
        ])
            .then(drivers => res.send(drivers))
            .catch(next);
    }
P Rajesh
  • 326
  • 1
  • 2
  • 11
  • If you are working your way through Stephen Grider's course "The Complete Developers Guide to MongoDB" currently being hosted on Udemy, the above code works perfectly. Make sure that you change the beforeEach attribute to say ".then(() => drivers.createIndexes(..." to avoid the warning. – Energetic Pixels Feb 18 '19 at 23:32