Reputation: 113
As soon as a User logs in his/or her location is reported. And continues to report the current users location every 15 minutes there on. Or atleast until their session is over.A users location is read into the Database with the Locate method. It is stored in the location array in the database associated with that specific user. And this is all working fine.
A users location will be read out of the database with the scan method.
I was wondering how to create a 2dsphere around location.coordinates. I know I have to target location.coordinates so mongo knows to treat it as a 2dsphere. So I know I have to use User.ensureIndex({location: "2dsphere"}). My issues/ questions are:
1st) Should it be User.ensureIndex("location.coordinate": 2dsphere) or ("location": 2dsphere). And would this go at the top of the app.js.
2nd) In my scan method I'm trying to create logic and query for documents with a username that matches in addition to being within 300 meters. So lastly my question is my last Mongo query structured correctly
Here's what my mongo Schema looks like:
var userSchema = mongoose.Schema({
username: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
},
email: {
type: String
},
location:
{
type: "Point",
coordinates: [Number, Number],
datetime: Date
},
points: {type:Number, default:10000},
imageUrl: {type:String, default:'/img/gravatar.jpg'}
});
This is how my app.js is set up:
var User = require('../models/users.js');
var userController = {
locate: function (req, res) {
var data = req.body;
var date = new Date();
var username = req.user.username;
User.findOne({username:username}, function(err, user) {
if (err) return handleErr(err);
find = {
coordinates: [data.longitude, data.latitude],
datetime: date
};
user.location.push(find);
user.save();
});
},
scan: function (req, res) {
var date = new Date();
var twentyb4 = date.setMinutes(date.getMinutes - 15);
User.find({"datetime": {$gte: twentyb4}}, function (err, user) {
if (err) return handleErr(err);
for (var i = 0; i < user.length; i++) {
//return users other than current user
if(req.user.username !== user[i].username){
User.find({$and: [{username: user[i].username}, {$near: {$geometry: {type: "Point", coordinates: [ req.user.longitude, req.user.latiude ]}, $maxDistance: 300, $minDistance: 0}}]}, function(err, data){
if (err) return handleErr(err);
console.log(data.username);
});
}
}
});
};
So ultimately what the scan function is to do is find all users in the database not including the current user. And then it filters to get just those users that have logged in within the last 20 minutes. And then I filter it down one more time comparing the last reported location of the other users compared to that of the current user.
Upvotes: 1
Views: 294
Reputation: 113
Heres the working solution:
I needed to have:
var userSchema = mongoose.Schema({
username: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
},
location:
{
type: { type: String, default: "Point" },
coordinates: [Number, Number],
datetime: Date
},
email: {
type: String
}
})
userSchema.index({ "location.coordinates": "2dsphere"}); //In my model
This is my controller in Node (server):
scan: function (req, res) {
var date = new Date();
var minusmin = date.setMinutes(date.getMinutes() - 20);
User.find({ "location.datetime": {$gte: minusmin}}, function (err, user) {
if (err) return handleErr(err);
for (var i = 0; i < user.length; i++) {
//return users other than current user
if(req.user.username !== user[i].username){
User.find({ username: user[i].username, "location.coordinates": { $nearSphere: { $geometry: { type: "Point", coordinates: [ req.user.location.coordinates[0], req.user.location.coordinates[1] ]}, $maxDistance: 300 } } }, function(err, data){
if (err) return handleErr(err);
console.log(data);
});
}
}
res.send(user);
});
}
Upvotes: 0
Reputation: 50406
Your schema is wrong, you need to fix this:
location:
{
type: "Point",
coordinates: [Number, Number],
datetime: Date
},
To this:
location: {
"type": { "type": String, "default": "Point" },
"coordinates": [Number, Number],
"datetime": Date
},
As the "type" keywork is being confused, and "Point" is not a mongoose datatype.
You also need an index defined for geoSpatial queries:
userSchema.index({ "location": "2dsphere" });
Then your query is quite simple ( you don't need $and
here as all query arguments are an "and" condition already ), and you really want $nearSphere
here with real world coordinates:
User.find({
"username": user[i].username,
"$nearSphere": {
"$geometry": {
"type": "Point",
"coordinates": [ parseFloat(req.user.longitude), parseFloat(req.user.latiude) ]
},
"$maxDistance": 300,
"$minDistance": 0
}
}, function(err, data) {
});
Being careful to watch that parseFloat
in there as well, as I cannot recall offhand if Mongoose actually has the smarts to "type cast" with a $nearSphere
query like this.
I would also suggest "don't loop" here, and just get all users other than the current usename all at once. Simple with some pre-filtering and the $in
operator:
// Filter out username values only except for the current user
var otherUsers = user.map(function(u) {
return u.username
}).filter(function(u) { return u != req.user.username });
if ( otherUsers.length > 0 ) {
User.find({
"username": { "$in": otherUsers },
"$nearSphere": {
"$geometry": {
"type": "Point",
"coordinates": [ parseFloat(req.user.longitude), parseFloat(req.user.latiude) ]
},
"$maxDistance": 300,
"$minDistance": 0
}
}, function(err, data) {
});
} else {
// no-one else is within the time :(
}
Now you tested all the other users in a single query, so you don't need to bother with looping results and the related asynchronous control that you really would require otherwise as well.
Upvotes: 1
Reputation: 8523
You only need to create an index once (ensureIndex is deprecated, the same functionality is present in createIndex. Since you're using mongoose, just define the index on your schema, e.g.
userSchema.index({location: '2dsphere'});
Your query is almost there, here's the correct version.
var geoJSONpoint = {
type: 'Point',
coordinates: [
req.user.longitude,
req.user.latitude
]
}
User.find({ username: user[i].username, location: { $near: { $geometry: geoJSONpoint, $maxDistance: 300 } } })
...
Upvotes: 0