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 PointsP0
givenP0's latitude and longitude
and given a maximum radiusdistance
used to find the interested points.
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).
undefined
?If you would like to receive some clarifications just post a comment below.
Thanks in Advance.
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
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]
]
]
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With