AndreaM16 AndreaM16 - 5 months ago 171
Node.js Question

Finding Closest Points to a certain Point Given its Coordinates and Maximum Distance - Query Result Undefined using Mongoose with MEAN Stack

I've an issue that I wasn't able to solve in some days, even looking at related Stack Overflow Q/A.

I'm developing an application reusing Scotch's Create a MEAN Stack Google Map App Tutorial by Ahmed Haque approach.

I'm trying to implement an application that uses Google Maps API to draw

Points
,
LineStrings
and
Polygons
which coordinates are contained in GeoJson files that are stored in a MongoDB instance.

I'm using
Mongoose
to build the Schema for my data and query my
MongoDB
database.


I would like to find the closest Points
CP
to a certain Points
P0

given
P0's latitude and longitude
and given a maximum radius
distance
used to find the interested points.


enter image description here

Given the image over, I would like that, for example, if I insert 2000 (kilometers), my query will find all the points maximum 2000 kilometers away from P0. In this example, it should probably give me P1 and P2.

I was able to do it when I only had Points in my
Mongoose Schema
.

I had this
Schema
with only markers (Points)
:

// Pulls Mongoose dependency for creating schemas
var mongoose = require('mongoose');
var Schema = mongoose.Schema;

// Creates a User Schema.
var MarkerSchema = new Schema({
username: {type: String, required: true},
location: {type: [Number], required: true}, // [Long, Lat]
created_at: {type: Date, default: Date.now},
updated_at: {type: Date, default: Date.now}
});

// Indexes this schema in 2dsphere format
MarkerSchema.index({location: '2dsphere'});

module.exports = mongoose.model('mean-markers', MarkerSchema);


And this was my
Old Query for only Markers
:


var User = require('./model.js');

app.post('/query/', function(req, res) {

// Grab all of the query parameters from the body.
var lat = req.body.latitude;
var long = req.body.longitude;
var distance = req.body.distance;
var reqVerified = req.body.reqVerified;

// Opens a generic Mongoose Query
var query = User.find({});

// ...include filter by Max Distance (converting miles to meters)
if (distance) {

// Using MongoDB's geospatial querying features
query = query.where('location').near({
center: {
type: 'Point',
coordinates: [long, lat]
},

// Converting meters to miles
maxDistance: distance * 1609.34,
spherical: true
});
}
});


It worked really well, and I was able to get close points.

Then, I changed my
Schema
to be more dynamic and also support
Polylines and Polygons
.

I'm able to insert and draw new Points, Polylines and Polygons with the following
Schema
:


var mongoose = require('mongoose');
var GeoJSON = require('geojson');
var Schema = mongoose.Schema;

// Creates a Location Schema.
var LocationSchema = new Schema({
name: {type: String, required: true},
location: {
type: {type : String, required: true},
coordinates : [Schema.Types.Mixed]
},
created_at: {type: Date, default: Date.now},
updated_at: {type: Date, default: Date.now}
});

LocationSchema.index({location: '2dsphere'});
module.exports = mongoose.model('mean-locations', LocationSchema);


And this is my
Mongoose Query
:


var GeoObjects = require('./model.js');

app.post('/query/', function(req, res) {

// Grab all of the query parameters from the body.
var lat = req.body.latitude;
var long = req.body.longitude;
var distance = req.body.distance;

var query;

if (distance) {
query = GeoObjects.find({'location.type':'Point'})
.where('location.coordinates').near({
center: {
type: 'Point',
coordinates: [lat, long]
},
// Converting meters to miles
maxDistance: distance * 1609.34,
spherical: true
});
}

// Execute Query and Return the Query Results
query.exec(function(err, users) {
if (err)
res.send(err);
console.log(users);
// If no errors, respond with a JSON of all users that meet the criteria
res.json(users);
});
});


console.log(users);
gives me
undefined.


Logging query results in my queryCtrl.js gives me the following error message:

name: "MongoError", message: "error processing query: ns=MeanMapApp.mean-locatio…ed error: unable to find index for $geoNear query", waitedMS: 0, ok: 0, errmsg: "error processing query: ns=MeanMapApp.mean-locatio…ed error: unable to find index for $geoNear query"


Same with a little variation:

app.post('/query/', function(req, res) {

// Grab all of the query parameters from the body.
var lat = req.body.latitude;
var long = req.body.longitude;
var distance = req.body.distance;

console.log(lat,long,distance);

var points = GeoObjects.find({'location.type':'Point'});

var loc = parseFloat(points.location.coordinates);
console.log(JSON.stringify(loc));

if (distance) {
var query = points.near(loc, {
center: {
type: 'Point',
coordinates: [parseFloat(lat), parseFloat(long)]
},
// Converting meters to miles
maxDistance: distance * 1609.34,
spherical: true
});
}
});


This is an example of a marker:

{
"name": "user01",
"location": {
"type":"Point",
"coordinates": [102.0, 0.0]

}
}


How $near operator works with distance and maxDistance:

From Scotch's Making MEAN Apps with Google Maps (Part II) by Ahmed Haque


MongoDB search parameter $near and its associated properties
maxDistance and spherical to specify the range we’re looking to cover.
We’re multiplying the distance of our query body by 1609.34, because
we want to take our users’ input (in miles) and convert it into the
units MongoDB expects (in meters).



  1. Why Am I getting
    undefined
    ?

  2. Is it possible that this problem is caused by my Schema?

  3. How can I fix this?



If you would like to receive some clarifications just post a comment below.

Thanks in Advance.

Answer

I finally managed to solve this issue.

Essentially, the issue was caused by the schema, since 2dIndex was referred to a wrong field (type and coordinates).

I solved using the following Schema:

var mongoose = require('mongoose');
var GeoJSON  = require('geojson');
var Schema   = mongoose.Schema;

var geoObjects = new Schema({
                               name : {type: String},
                               type: {
                                       type: String,
                                       enum: [
                                               "Point",
                                               "LineString",
                                               "Polygon"
                                             ]
                                      },
                                coordinates: [],
                                created_at: {type: Date, default: Date.now},
                                updated_at: {type: Date, default: Date.now}
});

// Sets the created_at parameter equal to the current time
geoObjects.pre('save', function(next){
   now = new Date();
   this.updated_at = now;
   if(!this.created_at) {
      this.created_at = now
   }
   next();
});

geoObjects.index({coordinates: '2dsphere'});

module.exports = mongoose.model('geoObjects', geoObjects);

And the following Query:

app.post('/query/', function(req, res) {

        // Grab all of the query parameters from the body.
        var lat = req.body.latitude;
        var long = req.body.longitude;
        var distance = req.body.distance;

        var query = GeoObjects.find({'type':'Point'});

        // ...include filter by Max Distance 
        if (distance) {

            // Using MongoDB's geospatial querying features. 
            query = query.where('coordinates').near({
                center: {
                    type: 'Point',
                    coordinates: [long, lat]
                },

                // Converting meters to miles
                maxDistance: distance * 1609.34,
                spherical: true
            });
        }

        // Execute Query and Return the Query Results
        query.exec(function(err, geoObjects) {
            if (err)
                res.send(err);

            // If no errors, respond with a JSON 
            res.json(geoObjects);
        });
    });

I hope it will help someone!

Comments