Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MongoDB Geospacial Query Spheres Overlapping Single Point

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!

like image 364
Jeff Avatar asked Sep 03 '14 02:09

Jeff


1 Answers

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.

like image 121
Neil Lunn Avatar answered Oct 25 '22 16:10

Neil Lunn