Skip to content

Instantly share code, notes, and snippets.

@PascalAnimateur
Last active February 22, 2021 20:19
Show Gist options
  • Star 21 You must be signed in to star a gist
  • Fork 8 You must be signed in to fork a gist
  • Save PascalAnimateur/b73617f0a27475fd4ccb to your computer and use it in GitHub Desktop.
Save PascalAnimateur/b73617f0a27475fd4ccb to your computer and use it in GitHub Desktop.
Geospatial example in Sails.js using native MongoDB query
module.exports.bootstrap = function(cb) {
// Ensure we have 2dsphere index on coordinates attribute of Place.
sails.models.place.native(function (err, collection) {
collection.ensureIndex({ coordinates: '2dsphere' }, function () {
// It's very important to trigger this callback method when you are finished
// with the bootstrap! (otherwise your server will never lift, since it's waiting on the bootstrap)
cb();
});
});
};
module.exports.models = {
// Geospatial queries require MongoDB connection (sails-mongo)
connection: 'someMongodbServer'
};
/* Place model */
module.exports = {
attributes: {
name: {
type: 'string',
required: true
},
// This is the attribute used for the place's geolocation.
// Be careful to store it as [ lng, lat ] or else geoNear queries will give imprecise results.
coordinates: {
type: 'json',
required: true
}
},
/**
* Find places closer than a certain distance (in km) from a specified location [ lng, lat ].
* @param conditions
* JSON object should look like this:
* {
* lng: -72.213,
* lat: 45.012,
* maxDistance: 100,
* limit: 20,
* }
*
* @param callback (err, results)
* Returns an array of results (ordered by increasing distance), it looks like this:
* [
* {
* dis: 10.321,
* obj: { JSON object of Place }
* },
* {
* dis: 20.123,
* obj: { JSON object of Place }
* }
* ]
*/
findNear: function (conditions, callback) {
Place.native(function (err, collection) {
if (err) return callback(err);
collection.geoNear({
type: "Point" ,
coordinates: [ conditions.lng, conditions.lat ]
}, {
limit: conditions.limit || 30,
maxDistance: conditions.maxDistance * 1000.0,
distanceMultiplier: 0.001,
spherical : true
}, function (err, places) {
if (err) return callback(err);
return callback(null, places.results);
});
});
}
};
/* Place controller */
module.exports = {
/**
* Create two dummy places.
*/
dummy: function (req, res) {
Place.create({
name: 'New Richmond',
coordinates: [ -65.8716805, 48.1804069 ]
}, function (err, place) {
if (err) return res.negotiate(err);
console.log('Place created: ', place);
res.ok();
});
Place.create({
name: 'Montreal',
coordinates: [ -73.5647636, 45.5158157 ]
}, function (err, place) {
if (err) return res.negotiate(err);
console.log('Place created: ', place);
res.ok();
});
},
/**
* Find places closer than a certain distance (in km) from a specified location [ lng, lat ].
*/
search: function (req, res) {
// TODO: Default values and conditions validation should go in Place.findNear
var conditions = {
lng: parseFloat(req.param('lng')) || 0,
lat: parseFloat(req.param('lat')) || 0,
maxDistance: parseFloat(req.param('maxDistance')) || 1000,
limit: req.param('limit') || 30,
};
Place.findNear(conditions, function (err, results) {
if (err) return res.negotiate(err);
return res.json(results);
});
}
};
@Jamie452
Copy link

Jamie452 commented Feb 2, 2016

+1 thanks for this, been looking for something like this for a while

@PascalAnimateur
Copy link
Author

I've updated this to fix the order of coordinates in the geoLocation array... these should be [ lng, lat ] in your records as required by the WGS84 standard used by mongodb. If you experience wrong / funky results from geoNear / findNear (i.e. maxDistance behaving like an ellipse instead of a circle), this is your problem right there. It would still work for plotting points on a map, but the geospatial queries wouldn't have any kind of precision.

@Jamie452
Copy link

Implemented your update and everything is working perfectly now, thanks!

P.S GitHub didn't notify me of your comment, so thanks again for reaching out directly.

@theCrab
Copy link

theCrab commented Feb 15, 2016

@Jamie452 @PascalAnimateur I am trying to do an app that tracks the user position and shows delivery vans that are nearest to them. I don't need to store the van location as its constantly changing. I am using Sails as a backend. The front end is 2 apps. 1 for the van driver which updates Sails every time its location changes significantly (0.5 kilometres). The other is a would be customer app and that just displays map pins of the closest van to them.

I am not very sure what this demands as its my first time doing such an app. After integrating your example above, my app looks like this:

// Location.js
module.exports = {

  attributes: {
    van_id: {
      type: 'string',
      required: true,
      index: true
    },
    coordinates: {
      type: 'json',
      required: true
    }
  }
}

I would love to have some thoughts on what you think the best way to handle this would be? Thanks

@PascalAnimateur
Copy link
Author

@theCrab: For the customer app you need some way of getting a location from IP, you could use something like node-freegeoip.

Seems like an ambitious project for a newcomer to Sails.. good luck!

@nanyaks
Copy link

nanyaks commented May 3, 2016

Thanks @PascalAnimateur. Works perfectly!

@shantanu2
Copy link

@PascalAnimateur

 module.exports.bootstrap = function(cb) {

  // Ensure we have 2dsphere index on coordinates attribute of Place.
  sails.models.modelName.native(function (err, collection) {
    collection.ensureIndex({ coordinates: '2dsphere' }, function () {

      // It's very important to trigger this callback method when you are finished
      // with the bootstrap!  (otherwise your server will never lift, since it's waiting on the bootstrap)
      cb();

    });
  });

};

its giving me error

error: Bootstrap encountered an error: (see below)
error: TypeError: Cannot read property 'native' of undefined
    at Object.module.exports.bootstrap (E:\codes\backend\Masterbackend\config\bootstrap.js:17:32)
    at Sails.runBootstrap (C:\Users\hp\AppData\Roaming\npm\node_modules\sails\lib\app\private\bootstrap.js:44:25)
    at Sails.wrapper [as runBootstrap] (C:\Users\hp\AppData\Roaming\npm\node_modules\sails\node_modules\@sailshq\lodash\lib\index.js:3250:19)
    at Sails.initialize (C:\Users\hp\AppData\Roaming\npm\node_modules\sails\lib\app\private\initialize.js:68:9)
    at wrapper (C:\Users\hp\AppData\Roaming\npm\node_modules\sails\node_modules\@sailshq\lodash\lib\index.js:3250:19)
    at C:\Users\hp\AppData\Roaming\npm\node_modules\sails\node_modules\async\lib\async.js:713:13
    at iterate (C:\Users\hp\AppData\Roaming\npm\node_modules\sails\node_modules\async\lib\async.js:262:13)
    at C:\Users\hp\AppData\Roaming\npm\node_modules\sails\node_modules\async\lib\async.js:274:29
    at C:\Users\hp\AppData\Roaming\npm\node_modules\sails\node_modules\async\lib\async.js:44:16
    at C:\Users\hp\AppData\Roaming\npm\node_modules\sails\node_modules\async\lib\async.js:718:17
    at C:\Users\hp\AppData\Roaming\npm\node_modules\sails\node_modules\async\lib\async.js:167:37
    at C:\Users\hp\AppData\Roaming\npm\node_modules\sails\lib\app\load.js:184:13
    at C:\Users\hp\AppData\Roaming\npm\node_modules\sails\node_modules\async\lib\async.js:52:16
    at C:\Users\hp\AppData\Roaming\npm\node_modules\sails\node_modules\async\lib\async.js:548:17
    at C:\Users\hp\AppData\Roaming\npm\node_modules\sails\node_modules\async\lib\async.js:542:17
    at _arrayEach (C:\Users\hp\AppData\Roaming\npm\node_modules\sails\node_modules\async\lib\async.js:85:13)
    at Immediate.taskComplete [as _onImmediate] (C:\Users\hp\AppData\Roaming\npm\node_modules\sails\node_modules\async\lib\async.js:541:13)
    at runCallback (timers.js:800:20)
    at tryOnImmediate (timers.js:762:5)
    at processImmediate [as _immediateCallback] (timers.js:733:5)

any help ????

@shantanu2
Copy link

Found Out Solution

module.exports.bootstrap = function(cb) {

  // Ensure we have 2dsphere index on coordinates attribute of Place.
  modelName.native(function (err, collection) {
    collection.ensureIndex({ coordinates: '2dsphere' }, function () {

      // It's very important to trigger this callback method when you are finished
      // with the bootstrap!  (otherwise your server will never lift, since it's waiting on the bootstrap)
      cb();

    });
  });

};

using native via modelName solve the problem

@tiham07
Copy link

tiham07 commented Dec 1, 2017

There are three main things to be considered:

  1. The 2dsphere index needs to be created properly (As described in the bootstrap.js.
  2. The name of the coordinates fields must be exactly coordinates (not coord, coordinate, position, ...)
  3. The positions need to be inserted with [longitude, latitude] not with [latitude, longitude].

@pb-z
Copy link

pb-z commented Aug 2, 2019

Thanks, this gist is really helpful!
Since .native() has been deprecated, the new bootstrap.js like below:

// Ensure we have 2dsphere index on coordinates attribute of Place.
  var db = Place.getDatastore().manager;
  var collection = db.collection('Place');
  collection.ensureIndex({ coordinates: '2dsphere' }, () => {

    // It's very important to trigger this callback method when you are finished
    // with the bootstrap!  (otherwise your server will never lift, since it's waiting on the bootstrap)
    cb();
  });

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment