MongoDB Geospacial Query Spheres Overlapping Single Point - mongodb

I am trying to create a geospacial query in MongoDB that finds all circles (with varying radius) that overlap a single point.
My data looks something like this:
{
name: "Pizza Hut",
lat: <latitude>
lon: <longitude>
radius: 20
...
}
Basically, I am trying to do exactly what is described in this SO post but with MongoDB - Get all points(circles with radius), that overlap given point
geoIntersects (http://docs.mongodb.org/manual/reference/operator/query/geoIntersects/) looks like what I need. But in my case, the lat, lon, and radius is stored with each mongodb document and is not a fixed radius that is part of the query. Can this be done?
A different approach would be to find all documents whose distance from my query point is less than the value of their radius field (ie - 20km in the example above). How do you structure a MongoDB query where the calculated distance is part of the query filter criteria?
Thanks!

Well it would be nicer if you could use a GeoJSON object to represent the location but as of present the supported types are actually limited so a "Circle" type which would be ideal is not supported.
The closest you could do is a "Polygon" approximating a circle, but this is probably a little too much work to construct just for this query purpose. The other gotcha with doing this and then applying $geoIntersects is that the results will not be "sorted" by the distance from the query point. That seems to be the opposite of the purpose of finding the "nearest pizza" to the point of origin.
Fortunately there is a $geoNear operation added to the aggregation framework as of MongoDB 2.4 and greater. The good thing here is it allows the "projection" of a distance field in the results. This then allows you to do the logical filtering on the server to those points that are "within the radius" constraint to the distance from the point of origin. It also allows sorting on the server as well.
But you are still going to need to change your schema to support the index
db.places.insert({
"name": "Pizza Hut",
"location": {
"type": "Point",
"coordinates": [
151.00211262702942,
-33.81696995135973
]
},
"radius": 20
})
db.places.ensureIndex({ "location": "2dsphere" })
And for the aggregation query:
db.places.aggregate([
// Query and project distance
{ "$geoNear": {
"near": {
"type": "Point",
"coordinates": [
150.92094898223877,
-33.77654333272719
]
},
"distanceField": "distance",
"distanceMultiplier": 0.001,
"maxDistance": 100000,
"spherical": true
}},
// Calculate if distance is within delivery sphere
{ "$project": {
"name": 1,
"location": 1,
"radius": 1,
"distance": 1,
"within": { "$gt": [ "$radius", "$distance" ] }
}},
// Filter any false results
{ "$match": { "within": true } },
// Sort by shortest distance from origin
{ "$sort": { "distance": -1 } }
])
Basically this says,
*"out to 100 kilometers from a given location, find the places with their distance from that point. If the distance is within their "delivery radius" then return them, sorted by the closest"
There are other options you can pass to $geoNear in order to refine the result, as well as return more than the default 100 results if required and basically pass other options to query such as a "type" or "name" or whatever other information you have on the document.

Related

How to store a circle and see if it overlaps with a box/ Geojson point in mongodb

I am not sure if there is a way to do this. I need to have a circle in mongodb and run a query against that with a box using $box to see if these two shapes overlap or not. However, Geojson does not support circles. What would be the best way to get this done?
The circle is stored like this:
places = {
...
"location": {
"type": "Point",
"coordinates": [
-79.390756,
43.706685
]
},
"radius": 100
}
I have two specific problems:
The first issue is that maxDistance is stored in the same object as the Geojson object and cannot be used in a $near query with $maxDistance; it only takes a number.
I do a partial postal code/ zip code search on Google Geocoding Api which returns a box with two corner coordinates like this:
"geometry": {
"bounds": {
"northeast": {
"lat": 43.710565,
"lng": -79.37363479999999
},
"southwest": {
"lat": 43.690848,
"lng": -79.40025399999999
}
}
As far as I know,I cannot use $box as it only works with $geoWithin.
Edit 1:
My initial plan with the circle and the box changed mainly because I did not find a suitable and efficient solution to this problem. Instead of checking if the circle overlaps with the box, now I check if a Geojson point is inside the circle as follows:
db.places.aggregate([
{"$geoNear": {near: { type: "Point", coordinates: [ -80.459293, 40.713640] },
distanceField: "dist.calculated", maxDistance: 100000,
key: 'myLocation', query: { 'SomeField': "..." }, spherical: true}},
{ "$match" : {$expr:{ $lte:['$dist.calculated', 'radius']}}}])
The problem here is that I d have to run a query within 100 KM first and then in another stage of the aggregation check the distance.
Is there a more efficient way to implement this? Thanks.
You can store a circle as point and radius. And you can use a $near query with a point and $maxDistance in meters which is the radius of the circle. See MongoDB Documentation.
Query to find all location, geometry field of the collection, at a certain distance from a point.
db.places.find(
{
location:
{ $near :
{
$geometry: { type: "Point", coordinates: [ -73.9667, 40.78 ] },
$maxDistance: 5000
}
}
}
)
Query to find if a given geometry (point, polygon(rect too)) in a query intersects with a geometry of a field in the collection.
//find quests bots that matches the users location
await Collection.find({ geometry:
{ $geoIntersects:
{
{
type: "Point",
coordinates: [
-73.99460599999999,
40.7347229
]
}
}
}
});

Creating a geospatial query to find two users within each other's radius

I am creating a matching system based on user proximity. The basic idea is I have user A at point A and user B at another point B. Both users have search filters that determine how far a user can be to make a match. This distance is in Kilometers.
Anyways would it be possible to do this in a single vanilla query (without using an aggregate query)? Here is the query I have so far
var loc = userA.location;
var rad = userA.radius;
{$near : {
$geometry : {
type : "Point",
coordinates : loc
},
$maxDistance : rad
}
}
How do I account for user's B preference though?
I was planning to store a geometry that was basically their circle of preference and then see if userA was within that circle, but this just doesn't seem right.
You already have the radius property stored on each user, so that is a good thing, and not only for a $maxDistance option, but also as a check to see if the distance between users is "less than" the radius attribute.
What you now basically need is a way to "see" that distance and "compare". For this you use the aggregation framework and $geoNear:
db.users.aggregate([
{ "$geoNear": {
"near": {
"type": "Point",
"coordinates": userA.location
},
"spherical": true,
"maxDistance": userA.radius,
"distanceField": "distance"
}},
{ "$redact": {
"$cond": {
"if": { "$lt": [ "$distance", "$radius" ] },
"then": "$$KEEP",
"else": "$$PRUNE"
}
}}
])
So what this does it executes the same sort of $near result along with the "maxDistance" constraint, but actually returns an additional property in the named "distanceField" representing the actual distance between found 'users'. The "spherical" option is required when the index is "2dsphere".
As an aggregation "pipeline", this allows another operation to act on the output. The $redact stage makes a "logical comparison" of the existing "radius" data and the generated "distance" field, to see if that distance is "less than" the stored radius data.
Only when that condition is true is the document returned via $$KEEP, otherwise the distance is larger than the users radius as the document is removed from results via $$PRUNE.
That's how you make a comparison that respects the radius on the user data for returning results.

MongoDB: Select all polygons within the radius of a point

I have two collections in my database: Post and Gallery. A Post has a single GeoJSON Point location, and a gallery is a collection of posts. The gallery's location is a GeoJSON Polygon bounding the gallery's posts (using quickhull algorithm). I now need to query for all galleries within x miles of a certain point, however I'm not getting any results even if I query from right next to my polygon.
I would like the behavior to be exactly the same as the following:
db.posts.find({
'location': {
$geoWithin: {
$centerSphere: [[-70, 30], 1000/3959]
}
}
});
Here, all posts within a the radius are returned. However, when I run the same type of function in this way, I am returned nothing, which is not correct:
db.galleries.find({
'location': {
$geoWithin: {
$centerSphere: [[-70, 30], 1000/3959]
}
}
});
One of my galleries has the following location (is 2dsphere index with 2dsphereIndexVersion = 2):
"location": {
"type": "Polygon",
"coordinates": [
[
[
-73.986882,
40.682829
],
[
-73.971089,
40.6672045
],
[
-73.955296,
40.65158
],
[
-73.986882,
40.682829
]
]
]
}
How do I query for location polygons that at least intersect with my radius?
I had the same problem and tried the exact same query that you initially tried. I'm not sure why it didn't work, but I was eventually able to get $near to do the job. Keep in mind that $near uses meters when calculating $maxDistance, so I had to convert my 10 mile desired distance to meters by multiplying by 1609.34. Here's the query that I ended up using:
db.maTowns.find(
{
'geometry': {
$near: {
$geometry: {
'type': "Point",
'coordinates': [ -71, 42 ]
},
$maxDistance: 10 * 1609.34
}
}
}
)
Select all polygons within the radius of a point
With the recent release of MongoDB version 3.6.0-rc0, you can now query GeoJSON LineStrings and Polygons with $geoWithin geospatial operator $centerSphere.
See also SERVER-27968 for more information about the change. Note that this change is pending to be backported.
Also maybe related for $geoIntersects and $centerSphere is ticket SERVER-30390

Mongo ordering a $near with another secondary sort

I have a list of shops they have a useCount and a geolocation.
How would I search and order by useCount but also have a property on each object returned signifying how close they are to me.
schema:
{
name: String,
useCount: { type: Number, index: true },
location: { 'type': {type: String, enum: "Point", default: "Point"}, coordinates: { type: [Number], default: [0,0]} }
}
e.g results
shop1 usecount-12 closest-3 geo-1333.222,222.222
shop2 usecount-3 closest-1 geo-1333.222,222.222
shop3 usecount-1 closest-2 geo-1333.222,222.222
Presuming your data is actually properly arranged for MongoDB and looks something like this:
{
"shop": 1,
"usecount": 12,
"closest": 3,
"geo": {
"type": "Point",
"coordinates": [1333.222,222.222]
}
}
And your coordinates are in fact in "longitude/latitude" order as is requireed from GeoJSON and MongoDB and that you have a geospatial index that is "2dsphere", then your best option for "composite sort" is using the $geoNear aggregate command pipeline, along with aggregation $sort:
Model.aggregate(
[
{ "$geoNear": {
"near": {
"type": "Point",
"coordinates": [1333.222,222.222]
},
"distanceField": "dist",
"spherical": true
}},
{ "$sort": { "dist": 1, "usecount": -1 } }
],
function(err,results) {
}
)
Where the $geoNear projects the "distance" as the nominated field here in "dist", and then you use that in the $sort along with the other field "usecount" as shown here in descending order for the "largest" value if "usecount" first, and within each "dist" already sorted.
The aggregation framework though .aggregate() does more than just "aggregate" documents. It is your "main tool" for projecting new values into a document, useful for such things as sorting results by values that "calculate" by one means or the other.
Unlike $near ( or $nearSphere ) the distance is returned as a true field in the document rather than just a "default" sort order. This allows that key to be used in the sorted results, along with any other field value present or projected into the document at the $sort stage.
Also noting that your data here does not appear to be valid spherical coordinates, which is going to cause problems with GeoJSON storage and also a "2dsphere" index. If not real global coordinates but coordinates on a "plane, then just use a plane legacy array for "geo" as [1333.222,222.222] and a "2d" index only. As well the argument to "near" within $geoNear is simply an array as well, and the "spherical" option would then not be required.
But possibly a problem with typing in your question as well.

MongoDB performance with geo data

I am using a single MongoDB 3.0.1 instance (without sharded cluster, replicas, etc) with one database containing a collection with 15324247 points. Of course, points are indexed with a 2dsphere index. Queries are done through a Node.js app.
Looking for points near a concrete lon&lat, it requires 11710ms to return 59925 points.
The same query including a restricted project (only geometry), it still requires 4351ms to return 59925 points.
find({
"geometry": {
"$near": {
"$geometry": {
"type": "Point",
"coordinates": [110.30838012695312,
-20.86522808076763]
},
"$maxDistance": 1000
}
}
},
{
geometry: 1
})
Changing the query and using and aggregation instead of find it requires: 5799ms but returns 8882 points, and projecting only the geometry 4606ms returns 8882 points.
aggregate([{
"$geoNear": {
"near": {
"type": "Point",
"coordinates": [110.30838012695312,
-20.86522808076763]
},
"distanceField": "dist.calculated",
"maxDistance": 964,
"spherical": true,
"num": 70000
}
}])
Although all the elements are indexed, is this a normal performance? How could be improved? I have tried $geoWithin instead of $geoNear, or adding more keys to geoindex, using cursor.get instead of cursor.on in Node.js side, increasing/decreasing batchSize for aggregation... but performances are quite similar in all cases.
And the second question is why the aggregation is returning less results than the find?