Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 708
AndreaM16 Avatar asked Jun 09 '16 17:06

AndreaM16


2 Answers

I don't understand what's beneath all your code, but I know a thing:
If you are using Google's Radar Search, you must take into consideration that

The maximum allowed radius is 50 000 meters.

Just take a look at their Documentation

Meaning that if you try with higher radius, results could be Zero

like image 66
Soldeplata Saketos Avatar answered Oct 26 '22 22:10

Soldeplata Saketos


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: [Number],
                                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: [lat, long]
                },

                // 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!

EDIT

The schema I put over causes a bit of problems with LineStrings and Polygons.

Here are correct schemas which allow using geoQueries

linestring-model.js:

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

// Creates a LineString Schema.
var linestrings = new Schema({
    name: {type: String, required : true},
    geo : {
        type : {type: String,
            default: "LineString"},
        coordinates : Array
    },
    created_at: {type: Date, default: Date.now},
    updated_at: {type: Date, default: Date.now}
});

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

linestrings.index({geo : '2dsphere'});
module.exports = mongoose.model('linestrings', linestrings);

polygon-model.js

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

// Creates a Polygon Schema.
var polygons = new Schema({
    name: {type: String, required : true},
    geo : {
        type : {type: String,
            default: "Polygon"},
        coordinates : Array
    },
    created_at: {type: Date, default: Date.now},
    updated_at: {type: Date, default: Date.now}
});

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

polygons.index({geo : '2dsphere'});
module.exports = mongoose.model('polygons', polygons);

LineString Insert:

{  
    "name" : "myLinestring", 
    "geo" : {
        "type" : "LineString", 
        "coordinates" : [
            [
                17.811, 
                12.634
            ], 
            [
                12.039, 
                18.962
            ], 
            [
                15.039, 
                18.962
            ], 
            [
                29.039, 
                18.962
            ]
        ]
    }
}

Polygon Insert:

{  
    "name" : "Poly", 
    "geo" : {
        "type" : "Polygon", 
        "coordinates" :  [
                           [ 
                             [25.774, -80.190], [18.466, -66.118], 
                             [32.321, -64.757], [25.774, -80.190] 
                           ]
                         ]
    }
}
like image 39
AndreaM16 Avatar answered Oct 26 '22 23:10

AndreaM16