8
votes

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
10
I'm having the exact same problem - did you manage to find a solution?? - p4t

10 Answers

10
votes
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/

5
votes

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.

3
votes

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
        }
    )
3
votes

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.

2
votes

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 [email protected]
2
votes

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.

0
votes

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);
        });
};
0
votes
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)
    })
})
-1
votes
 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);
    }
-2
votes

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