I have a collection with many polygon coordinates, which represent the different areas.
What my objective is I will send a lat, long and radius in a mongodb query which should return the polygon coordinates that falls within circle area.
Polygon coordinates:
{
"_id" : "300",
"name" : "MeKesler",
"centroid" : [
0,
0
],
"type" : "cnbd_id",
"coords" : [
[
39.8620784017,
-86.14614844330004
],
[
39.8625395793,
-86.15442037579999
],
[
39.8593030353,
-86.156373024
],
[
39.8586935926,
-86.15669488909998
],
[
39.8534225112,
-86.15854024890001
],
[
39.850391456,
-86.1589050293
],
[
39.8511657057,
-86.1479830742
],
[
39.8511986523,
-86.14598751070002
],
[
39.856881704,
-86.14605188370001
],
[
39.8575241063,
-86.14605188370001
],
[
39.8620784017,
-86.14614844330004
]
]
}
My query:
db.collection.find({
"coords" : {
"$within" : {
"$center" : [
[ 39.863110, -86.168456],
2/69.1
]
}
}
})
Can anyone help on this?
I believe the problem is that your document is not valid geojson. The valid syntax for your polygon would be
{
"_id": "300",
"name": "MeKesler",
"centroid": {
type: "Point",
coordinates: [0, 0]
},
"type": "cnbd_id",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[39.8620784017, -86.14614844330004],
[39.8625395793, -86.15442037579999],
[39.8593030353, -86.156373024],
[39.8586935926, -86.15669488909998],
[39.8534225112, -86.15854024890001],
[39.850391456, -86.1589050293],
[39.8511657057, -86.1479830742],
[39.8511986523, -86.14598751070002],
[39.856881704, -86.14605188370001],
[39.8575241063, -86.14605188370001],
[39.8620784017, -86.14614844330004]
]
]
}
}
Also, you should consider creating a 2dsphere index on the geometry field.
Lastly, the geo query should use the $geoWithin operator, and run against the polygon, not against its coordinates.
db.collection.find({
"geometry" : {
"$geoWithin" : {
"$center" : [
[ 39.863110, -86.168456],
2/69.1
]
}
}
})
Related
I have the following object in my Collection:
{
"_id":"test123",
"footprint":{
"type":"Polygon",
"coordinates":[
[
[10, 30], [20, 45], [38, 38], [43, 38], [45, 30], [10, 30]
]
]
}
}
with index of type "2dsphere" on "footprint" attribute.
Now, I would like to implements the geospatial query "overlaps", as implemented by ST_Overlaps in PostGIS: https://postgis.net/docs/ST_Overlaps.html.
Due to the fact that MongoDB doesn't support "overlap" natively (only within, intersect and near) and according to the above definition, I whould return all overlapping documents not totally within the search area.
Therefore, I'm trying to execute the following filter:
{
"footprint":{
"$geoIntersects":{
"$geometry":{
"type":"Polygon",
"coordinates":[
[
[
41.62109375000001,
38.087716380862716
],
[
41.870727539062514,
37.998201197578084
],
[
41.72393798828124,
38.01268326428104
],
[
41.62109375000001,
38.087716380862716
]
]
]
}
},
"$not":{
"$geoWithin":{
"$geometry":{
"type":"Polygon",
"coordinates":[
[
[
41.62109375000001,
38.087716380862716
],
[
41.870727539062514,
37.998201197578084
],
[
41.72393798828124,
38.01268326428104
],
[
41.62109375000001,
38.087716380862716
]
]
]
}
}
}
}
}
But I get the following error:
can't parse extra field: $not: { $geoWithin: { $geometry: { type: "Polygon", coordinates: [ [ [ 41.62109375000001, 38.08771638086272 ], [ 41.87072753906251, 37.99820119757808 ], [ 41.72393798828124, 38.01268326428104 ], [ 41.62109375000001, 38.08771638086272 ] ] ] } } }
After several tests, it seems I can't execute a second filter on the same attribute.
Am I wrong? Is there any workaround?
Thanks
This is due to the query language and how it parses objects, the object you're trying to use looks like this:
{ key: { query1, query2 }}
Where query1 is $geoIntersects and query2 is $not which is just not a valid structure, what you can do is wrap both of them with an $and query like so:
{
$and: [
{
"footprint": {
"$geoIntersects": {
"$geometry": {
"type": "Polygon",
"coordinates": [
[
[
41.62109375000001,
38.087716380862716
],
[
41.870727539062514,
37.998201197578084
],
[
41.72393798828124,
38.01268326428104
],
[
41.62109375000001,
38.087716380862716
]
]
]
}
}
}
},
{
footprint: {
"$not": {
"$geoWithin": {
"$geometry": {
"type": "Polygon",
"coordinates": [
[
[
41.62109375000001,
38.087716380862716
],
[
41.870727539062514,
37.998201197578084
],
[
41.72393798828124,
38.01268326428104
],
[
41.62109375000001,
38.087716380862716
]
]
]
}
}
}
}
}
]
}
I want to find out which Polygon from a collection, contains the most points from another collection.
I’m using one collection with restaurant data (points) and one with the neighborhood data (polygons)
Both collections are provided by mongodb:
https://raw.githubusercontent.com/mongodb/docs-assets/geospatial/neighborhoods.json
https://raw.githubusercontent.com/mongodb/docs-assets/geospatial/restaurants.json
Neighborhood document:
{
"_id": {
"$oid": "55cb9c666c522cafdb053a1a"
},
"geometry": {
"coordinates": [
[
[
-73.9443878859649,
40.70042452378256
],
[
-73.94424286147482,
40.69969927964773
],
[
-73.94193078816193,
40.70072523469547
],…
]
],
"type": "Polygon"
},
"name": "Bedford"
}
Restaurant document
{
"_id" : { "$oid" : "55cba2476c522cafdb053add" },
"location" : {"coordinates":[-73.856077,40.848447] , "type":"Point" },
"name" : "Morris Park Bake Shop"
}
Here an example to find out all restaurants within a single district:
Select one neighborhood (polygon) by a given point ($geoIntersects)
var neighborhood = db.neighborhoods.findOne(
{
geometry:
{
$geoIntersects:
{
$geometry: { type: "Point", coordinates: [ -73.93414657, 40.82302903 ] }
}
}
}
)
Find out how many restaurants are in this neighborhood
db.restaurants.find( { location: { $geoWithin: { $geometry: neighborhood.geometry } } } ).count()
My question:
Which neighborhood contains the most restaurants?
I have the following aggregate:
db.locations.aggregate(
// Pipeline
[
// Stage 1
{
$geoNear: {
near: { type: "Point", coordinates: [-47.121314, -18.151515 ] },
distanceField: "dist.calculated",
maxDistance: 500,
includeLocs: "dist.location",
num: 50000,
spherical: true
}
},
// Stage 2
{
$group: {
"_id" : {
'loc' : '$loc'
},
qtd: { $sum:1 }
}
},
], );
And the following collection:
{
"_id" : ObjectId(),
"loc" : {
"type" : "Point",
"coordinates" : [
-47.121311,
-18.151512
]
}
},
{
"_id" : ObjectId(),
"loc" : {
"type" : "Point",
"coordinates" : [
-47.121311,
-18.151512
]
}
},
{
"_id" : ObjectId(),
"loc" : {
"type" : "Point",
"coordinates" : [
-47.121312,
-18.151523
]
}
},
{
"_id" : ObjectId(),
"loc" : {
"type" : "Point",
"coordinates" : [
-47.121322,
-18.151533
]
}
}
When I run the aggregate, I have the following result:
{
"_id" : {
"loc" : {
"type" : "Point",
"coordinates" : [
-47.121311,
-18.151512
]
}
},
"qtd" : 2.0
},
{
"_id" : {
"loc" : {
"type" : "Point",
"coordinates" : [
-47.121312,
-18.151523
]
}
},
"qtd" : 1.0
},
{
"_id" : {
"loc" : {
"type" : "Point",
"coordinates" : [
-47.121322,
-18.151533
]
}
},
"qtd" : 1.0
}
I would like to group these locations in a single document, since they are very close ..
I thought of reducing the size of each point, -47.121314 being something like -47.1213
Something like this
{
"_id" : {
"loc" : {
"type" : "Point",
"coordinates" : [
-47.1213,
-18.1515
]
}
},
"qtd" : 4.0
}
But I have no idea how to group these documents.
Is it possible?
The way to reduce the floating point precision is to $multiply out the number by the required precision adjustment, "truncate it" to an integer and then $divide back to the desired precision.
For latest MongoDB releases ( since MongoDB 3.2 ) you can use $trunc:
db.locations.aggregate([
{ "$geoNear": {
"near": {
"type": "Point",
"coordinates": [ -47.121314, -18.151515 ]
},
"distanceField": "qtd",
"maxDistance": 500,
"num": 50000,
"spherical": true
}},
{ "$group": {
"_id": {
"type": '$loc.type',
"coordinates": {
"$map": {
"input": '$loc.coordinates',
"in": {
"$divide": [
{ "$trunc": { "$multiply": [ '$$this', 10000 ] } },
10000
]
}
}
}
},
"qtd": { "$sum": '$qtd' }
}}
]);
For releases prior to that, you can use $mod and $subtract to remove the "remainder" instead:
db.locations.aggregate([
{ "$geoNear": {
"near": {
"type": "Point",
"coordinates": [ -47.121314, -18.151515 ]
},
"distanceField": "qtd",
"maxDistance": 500,
"num": 50000,
"spherical": true
}},
{ "$group": {
"_id": {
"type": '$loc.type',
"coordinates": {
"$map": {
"input": '$loc.coordinates',
"as": "coord",
"in": {
"$divide": [
{ "$subtract": [
{ "$multiply": [ '$$coord', 10000 ] },
{ "$mod": [
{ "$multiply": [ '$$coord', 10000 ] },
1
]}
]},
10000
]
}
}
}
},
"qtd": { "$sum": '$qtd' }
}}
]);
Both return the same result:
/* 1 */
{
"_id" : {
"type" : "Point",
"coordinates" : [
-47.1213,
-18.1515
]
},
"qtd" : 4.01180839007879
}
We use $map here to "reshape" the array contents of "coordinates" applying the "rounding" to each value in the array. You might note the two slightly different usages with "as' in the second example, since the ability to use $$this as a default reference was only applied in MongoDB 3.2, for which the listing presumes you would not have or otherwise you would use $trunc instead of the alternate method usage.
You should note that $geoNear which is essentially a "nearest" search is only returning 100 documents by default or alternately up to the number specified in "num" or "limit" options. So that is always a governing factor in the number of results returned if those would exceed the other constraints such as "maxDistance".
There is also no need to follow the documentation so literally, as "distanceField" is the only other mandatory parameter aside from "spherical" which is required when a "2dsphere" index is used. The value to "distanceField" can be whatever you actually want it to be, and in this case we simply supply it directly with the name of the property you want to output.
I have a question to the $geoIntersects-Operator.
I have the following searchBox and collection-content:
> BOX
{
"type" : "Polygon",
"coordinates" : [
[
[
0,
0
],
[
3,
0
],
[
3,
3
],
[
0,
3
],
[
0,
0
]
]
]
}
> db.polygon.find()
{ "_id" : "Poly1", "shape" : { "type" : "Polygon", "coordinates" : [ [ [ 0, 0 ], [ 3, 0 ], [ 3, 3 ], [ 0, 3 ], [ 0, 0 ] ] ] } }
{ "_id" : "Poly2", "shape" : { "type" : "Polygon", "coordinates" : [ [ [ 3, 0 ], [ 6, 0 ], [ 6, 3 ], [ 3, 3 ], [ 3, 0 ] ] ] } }
> db.polygon.find( {shape: {$geoIntersects: {$geometry: BOX}}}, {_id:1})
{ "_id" : "Poly1" }
Like you can see, the BOX and Poly1 are identical.
Poly2 shared an edge with BOX.
So when I was executing the $geoIntersects-Query I was expecting that both polygon’s where returned because of the shared edge, but only Poly1 was found.
Can somebody explain that to me? Or did I made a stupid mistake I don’t see :(
Auf Wiedersehen, Andre
Good question. It looks like it's a bug or the documentation is not accurate. Just want to share the results of my small research on the issue
Point:
.find( {shape: {$geoIntersects: {$geometry: {type: "Point", coordinates : [3,0] }}}}, {_id:1})
No surprise it returns both Poly1 and Poly2.
LineString:
.find( {shape: {$geoIntersects: {$geometry: {type: "LineString", coordinates : [[3,0], [3, 3]] }}}}, {_id:1})
Returns Poly1 only, what if we revert the order of line points?
.find( {shape: {$geoIntersects: {$geometry: {type: "LineString", coordinates : [[3,3], [3, 0]] }}}}, {_id:1})
Returns Poly2 only now. So the order of the points is important for LineString which is really weird to me.
Polygon:
Let's also try to change the order of points for polygon query.
.find( {shape: {$geoIntersects: {$geometry: {type: "Polygon", coordinates : [ [ [ 3, 0 ], [ 3, 3 ], [ 6, 3 ], [ 6, 0 ], [ 3, 0 ] ] ]}}
Now even the order of points for line [ 3, 0 ], [ 3, 3 ] matches Poly1 definition but it still returns Poly2 only.
Summary:
So when the documentation says that
This includes documents that have a shared edge
It's no surprise true for Point, it is partially true for LineString because the order of points is important! Finnaly it's not true at all for Polygon.
This is sad in fact but really good to know. I wish I were doing something wrong during my research and would be glad if someone drop in with a good explanation.
just use smaller numbers. for triangles like these:
[{
_id:54cfbc19d9e1f418373ee427,
geo:{type:Polygon,
coordinates:[[[0.3,0.3],[0,0.3],[0,0],[0.3,0.3]]]}
},
{_id:54cfbc19d9e1f418373ee428,
geo:{type:Polygon,
coordinates:[[[0,0],[0.3,0],[0.3,0.3],[0,0]]]}
}]
.find({
geo: {
$geoIntersects: {
$geometry: {
type: "Point" ,
coordinates: [0.005,0.005]
}
}
}
})
will give you right result.
I guess that $geoIntersects count that Earth is sphere.
I have user collection:
{
"_id": { "$oid" : "514C438232F5699004000014" },
"gender": 1,
"loc": {
"coordinates": [
0.777084,
0.701690
],
"type": "Point"
},
"name": "H1",
"radius": 1
},
{
"_id": { "$oid" : "514C438232F5699004000014" },
"gender": 1,
"loc": {
"coordinates": [
0.677084,
0.701690
],
"type": "Point"
},
"name": "H2",
"radius": 0.4
}
db.user.ensureIndex( { loc : "2dsphere" } )
I need to write query and use radius property from collection's row ( "radius": 1 ) in find query like this:
db.user.find( { loc: { $geoWithin :{ $centerSphere : [ [0.7, 0.7 ] , radius ]} } } )
But mongo returns:
JavaScript execution failed: ReferenceError: radius is not defined
I have tried db.user.find( { loc: { $geoWithin :{ $centerSphere : [ [0.7, 0.7 ] , this.radius ]} } } )
I think you have to do a two way query. First fetch the radius of a given user, then search for all location within this radius.