Can't canonicalize query :: caused by :: invalid argument in geo near query - mongodb

Hoping someone can help with this because I've been working on it all morning
I'm trying to use MongoDB Near to find locations within a radius of a given point on a map. I get the following error:
{"name":"MongoError","message":"Can't canonicalize query :: caused by
:: invalid argument in geo near query: spherical","$err":"Can't
canonicalize query :: caused by :: invalid argument in geo near query:
spherical","code":2,"ok":0}
This is the mongoose schema for locations:
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var LocationSchema = new Schema({
title: String,
coordinates: {
type: [Number],
index: '2dsphere'
},
created: {
type: Date,
default: Date.now
}
});
mongoose.model('Location', LocationSchema);
and this is the route where I try to use "Near" - basically I enter a set of coordinates in a form where the target is the following
router.post('/nearme', function (req, res, next) {
// Setup limit
var limit = req.body.limit || 10;
// Default max distance to 10 kilometers
var maxDistance = req.body.distance || 10;
// Setup coords Object = [ <longitude> , <latitude> ]
var coords = [];
// Create the array
coords[0] = req.body.longitude;
coords[1] = req.body.latitude;
// find a location
Location.find({
'coordinates': {
$near: {
$geometry: {
type: "Point",
coordinates: coords
},
// distance to radians
$maxDistance: maxDistance * 1609.34, spherical: true
}
}
}).limit(limit).exec(function (err, stores) {
if (err) {
return res.status(500).json(err);
}
//res.status(200).json(stores);
res.render('locations', {
title: 'Locations',
location: stores,
lat: -23.54312,
long: -46.642748
});
});
Also - I indexed the location collection as follows:
db.locations.ensureIndex({ 'coordinates' : '2dsphere'})
and as far as I can tell, it looks right:
Things I've tried:
using "geoNear" instead of geometry.
removing spherical:true
searching stackoverflow for similar errors - none seem to have the same issue
Any help you could provide would be amazing. Thank you!

Thanks to Joe I reviewed the MongoDB docs and updated the /nearme route as follows (changes made in $near):
router.post('/nearme', function (req, res, next) {
// Setup limit
var limit = req.body.limit || 10;
// Default max distance to 10 kilometers
var maxDistance = req.body.distance || 10;
// Setup coords Object = [ <longitude> , <latitude> ]
var coords = [];
// Create the array
coords[0] = req.body.longitude;
coords[1] = req.body.latitude;
// find a location
Location.find({
'coordinates': {
$near: {
$geometry: {
type: "Point" ,
coordinates: coords
},
$maxDistance: maxDistance * 1609.34,
$minDistance: 0
}
}
}).limit(limit).exec(function (err, stores) {
if (err) {
return res.status(500).json(err);
}
//res.status(200).json(stores);
res.render('locations', {
title: 'Locations',
location: stores,
lat: -23.54312,
long: -46.642748
});
});
});

Related

Mongo geoWithin error: Polygon coordinates must be an array

I have a data set with geo points.
{ _id ...other fields... location: { type: "Point", coordinates: [0,0]}}
What I have been attempting to do is filter out and delete any documents that have points that are in water bodies. I downloaded and converted a shape file of the earth's water bodies and stored that in a separate collection in the same database.
I have been trying to use Mongo's geoWithin function, but am not able to get it to work for me when I specify the water body's polygon document. If I hard code a polygon it works, but I don't really want to type in all the earth's water polygons into my code...
This doesn't work:
var geo = {type: "Point", coordinates: [0,0]}
db.waterBodies.find({
geo: {
$geoWithin: {
$geometry: {
type: 'Polygon',
coordinates: "$geometry.coordinates"
}
}
}
}).count()
or this
var geo = {type: "Point", coordinates: [0,0]}
var poly = [[[0,0],[0,3],[3,0],[0,0]]]
db.data.find({
geo: {
$geoWithin: {
$geometry: {
type: 'Polygon',
coordinates: "$poly"
}
}
}
}).count()
It generates this error:
E QUERY [js] uncaught exception: Error: count failed: { "ok" : 0, "errmsg" : "Polygon coordinates must be an array", "code" : 2,
"codeName" : "BadValue" } :
_getErrorWithCode#src/mongo/shell/utils.js:25:13 DBQuery.prototype.count#src/mongo/shell/query.js:376:11 #(shell):1:1
This works and doesn't throw an error but requires hard coded values:
var geo = {type: "Point", coordinates: [0,0]}
db.data.find({
geo: {
$geoWithin: {
$geometry: {
type: 'Polygon',
coordinates: [[[0,0],[0,3],[3,0],[0,0]]]
}
}
}
}).count()
and this
db.data.find({
'location.coordinates': {
$geoWithin: {
$geometry: {
type: 'Polygon',
coordinates: [[[0,0],[0,3],[3,0],[0,0]]]
}
}
}
}).count()
I of coarse don't want the count but used that for testing purposes.
The simple answer for me would look something like this:
const polygons = await getPolygons(); //get all the polygons from the water body's collection
const filter = {
'location.coordinates': { //the points I want to filter
$geoWithin: { //could also be geoIntersects
$geometry: {
type: 'MultiPolygon',
coordinates: polygons.geometry.coordinates //the water body polygons
}
}
}
};
try {
await db.collection(collection).deleteMany(filter);
} catch (e) {
if (e instanceof MongoError) {
Logger.info('Error deleting log');
throw e;
}
}
I would want to use multipolygon since there are many water bodies.
So far I have read everything I can find on google but nothing has worked for me. All the examples I have found hard code an array for the coordinates, but I don't want to do this.
Please help and let me know if there is a way to do this or to remove all points that are not found on land.
Thanks in advance.
Does this work for you?
db.data.insert({
geo: {
type: "Point",
coordinates: [0,0]
}
})
db.polygons.insert({
geo: {
type: "Polygon",
coordinates: [[[0,0],[0,3],[3,0],[0,0]]]
}
})
var polygon = db.polygons.findOne()
db.data.find({
geo: {
$geoWithin: {
$geometry: {
type: 'Polygon',
coordinates: polygon.geo.coordinates
}
}
}
}).count()

Empty array response to Get request

I am following a full-stack web development guide. The git repository can be found here:
https://github.com/cliveharber/gettingMean-2/tree/chapter-06
In chapter 6, I've created an API that is supposed to display a list of locations based on the GPS coordinates. When I test the get request I get an empty array. The get request URL is:
http://localhost:3000/api/locations?lat=51.455041&lng=-0.9690884
This is the controller code for locations:
Note that I use a Mongoose aggregate called $geonear to find a list of locations close to a specified point
const locationsListByDistance = async (req, res) => {
const lng = parseFloat(req.query.lng);
const lat = parseFloat(req.query.lat);
const near = {
type: "Point",
coordinates: [lng, lat]
};
const geoOptions = {
distanceField: "distance.calculated",
key: 'coords',
spherical: true,
maxDistance: 20000
};
if (!lng || !lat) {
return res
.status(404)
.json({ "message": "lng and lat query parameters are required" });
}
try {
const results = await Loc.aggregate([
{
$geoNear: {
near,
...geoOptions
}
}
]);
const locations = results.map(result => {
return {
id: result._id,
name: result.name,
address: result.address,
rating: result.rating,
facilities: result.facilities,
distance: `${result.distance.calculated.toFixed()}m`
}
});
res
.status(200)
.json(locations);
} catch (err) {
res
.status(404)
.json(err);
}
};
This is my location model:
const locationSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
address: String,
rating: {
type: Number,
'default': 0,
min: 0,
max: 5
},
facilities: [String],
coords: {
type: {type: String},
coordinates: [Number]
},
openingTimes: [openingTimesSchema],
reviews: [reviewSchema]
});
I am sure I use the right GPS coordinates in the get request URL. This the document I am trying to retrieve from the database:
I am not sure why I'm getting an empty array when testing this API. Are there any issues in my code?
If I'm reading your code right, the aggregation that you are sending is:
Loc.aggregate([
{
$geoNear: {
{
type: "Point",
coordinates: [lng, lat]
},
distanceField: "distance.calculated",
key: 'coords',
spherical: true,
maxDistance: 20000
}
}
]);
You haven't mentioned creating a 2dsphere index on the coords field of the collection, I'll assume you have done that.
That aggregation seems to be missing the near field name, like:
Loc.aggregate([
{
$geoNear: {
near: {
type: "Point",
coordinates: [lng, lat]
},
distanceField: "distance.calculated",
key: 'coords',
spherical: true,
maxDistance: 20000
}
}
]);

Get lat and lng using $near and $geometry not working on Mongo + meteor

I am trying to get the all docs that is within 200m from a center point but i am having this error
Exception from sub getLocNearMe id hg3jRDv8onsZEGvBM Error: Exception
while polling query
{"collectionName":"Col_Location","selector":{"loc":{"$near":{"$geometry":{"type":"Point","coordinates":[1.3852457,103.88112509999999]},"$maxDistance":200}}},"options":{"transform":null}}:
$near requires a point, given { type: "Point", coordinates: [
1.3852457, 103.8811251 ] }
client/component/map.jsx
navigator.geolocation.getCurrentPosition(function (pos) {
const sub = Meteor.subscribe("getLocNearMe", pos.coords.latitude, pos.coords.longitude)
Tracker.autorun(function () {
if (sub.ready()) {
Data.MarkersRawData = Col_Location.find().fetch()
}
})
})
lib/collections.jsx
Meteor.publish("getLocNearMe", function(lng, lat) {
check(lat, Number)
check(lng, Number)
const data = Col_Location.find({
loc: {
$near: {
$geometry: {
type: "Point" ,
coordinates: [lng, lat]
},
$maxDistance: 200
}
}
})
console.log(data)
if (data) {
return data
}
return this.ready()
})
server/server.jsx
Col_AllQuestion._ensureIndex({"loc": "2dsphere"})
Col_Location.insert({
loc: {
type: "Point",
coordinates: [103.8, 1.31]
}
})
Testing you "Point" with http://geojsonlint.com/ gives several problems. It seems the (somewhat nitpicky) spec demands your keys to be quoted strings (which i think meteor handles for you) but also the fact that your coordinates are flipped (lat/long).
Putting you sample in like this gives a valid GeoJSON result:
{
"type": "Point",
"coordinates": [ 103.8811251, 1.3852457 ]
}

Sails.js $near query does not work and asks for indices

I'm trying to query the nearest points to some coordinate in Sails.js using MongoDB, but I'm getting the following error:
{ [MongoError: can't find any special indices: 2d (needs index), 2dsphere (needs index), for: { $near: { $geometry: { type: "Point", coordinates: [ "4.3795912", "51.9985675" ] }, $maxDistance: 1000 } }] name: 'MongoError' }
I made sure I had this in bootstrap.js:
sails.models.location.native(function (err, collection) {
collection.ensureIndex({ coordinates: '2dsphere' }, function () {
cb();
});
});
And my Location model looks like this:
attributes: {
coordinates: {
type: 'json'
},
user: {
model: 'user'
}
}
My controller code is the following:
Location.native(function(err, collection) {
var query = {};
collection.find(
query.coordinates = {
$near: {
$geometry: {
type: "Point",
coordinates: [
user.locations[0].lng,
user.locations[0].lat
]
},
$maxDistance : 1000
}
}
).toArray(function(err, result){
if(err) {
console.log(err);
}
else
return res.json(result);
});
});
I'm pretty sure I did just what I was supposed to do, but apparently I have done something wrong or I forgot something. Anyone any idea how to get this to work? Thanks.
I have solved the problem. There were two things I did wrong. First of all, every time I called sails lift the index I created would get removed and I did not know that. Second problem was that my location point was not in proper GeoJSON format:
{
"type": "Point",
"coordinates": [
4.373654,
51.998715
]
}

How to define a circle using GeoJson?

I want to use geometry in Mongodb.
But circle is not supported in geojson according to the geojson.org
I had exactly the same problem, the solution is to create a polygon that roughly approximates a circle (imagine a polygon with 32+ edges).
I wrote a module that does this. You can use it like this:
const circleToPolygon = require('circle-to-polygon');
const coordinates = [-27.4575887, -58.99029]; //[lon, lat]
const radius = 100; // in meters
const numberOfEdges = 32; //optional that defaults to 32
let polygon = circleToPolygon(coordinates, radius, numberOfEdges);
You will need to model it as a point and then store the radius in another field. If you want to test whether or not something is inside of that circle, you will need to use the proximity spatial index as discussed here
{
<location field>: {
$geoWithin: { $centerSphere: [ [ <x>, <y> ], <radius> ] }
}
}
https://docs.mongodb.com/manual/reference/operator/query/centerSphere/
Since v1.8
Another approach to it. In this case, I have used mongoose one of the most popular distribution of MongoDB to add a circle to a map with a radius and then query using an external parameter and assessing if it's inside a circle or outside the circle.
This example also has commented section for polygon, where if you have saved a polygon and you want to search if the point exists inside a polygon, you can do that too. Also, there is an upcoming section for a full integration of front end and backend for a complete geofence experience.
The code
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var assert = require('assert');
console.log('\n===========');
console.log(' mongoose version: %s', mongoose.version);
console.log('========\n\n');
var dbname = 'testing_geojsonPoint';
mongoose.connect('localhost', dbname);
mongoose.connection.on('error', function() {
console.error('connection error', arguments);
});
// schema
var schema = new Schema({
loc: {
type: {
type: String
},
coordinates: []
},
radius : {
type : 'Number'
}
});
schema.index({
loc: '2dsphere'
});
var A = mongoose.model('A', schema);
// mongoose.connection.on('open', function() {
// A.on('index', function(err) {
// if (err) return done(err);
// A.create({
// loc: {
// type: 'Polygon',
// coordinates: [
// [
// [77.69866, 13.025621],
// [77.69822, 13.024999, ],
// [77.699314, 13.025025, ],
// [77.69866, 13.025621]
// ]
// ]
// }
// }, function(err) {
// if (err) return done(err);
// A.find({
// loc: {
// $geoIntersects: {
// $geometry: {
// type: 'Point',
// coordinates: [77.69979,13.02593]
// }
// }
// }
// }, function(err, docs) {
// if (err) return done(err);
// console.log(docs);
// done();
// });
// });
// });
// });
mongoose.connection.on('open', function() {
A.on('index', function(err) {
if (err) return done(err);
A.create({
loc: {
type: 'Point',
coordinates: [77.698027,13.025292],
},
radius : 115.1735664276843
}, function(err, docs) {
if (err) return done(err);
A.find({
loc: {
$geoNear: {
$geometry: {
type: 'Point',
coordinates: [77.69735,13.02489]
},
$maxDistance :docs.radius
}
}
}, function(err, docs) {
if (err) return done(err);
console.log(docs);
done();
});
});
});
});
function done(err) {
if (err) console.error(err.stack);
mongoose.connection.db.dropDatabase(function() {
mongoose.connection.close();
});
}
See full example in action