I have a very strange behavior when aggregating results in MongoDB. For example, if I query the following :
db.getCollection('locations').aggregate([
{"$geoNear": {
"spherical": true,
"maxDistance": 14239,
"near": {
"type": "Point",
"coordinates": [45.180584858570136,5.760955810546876]
},
"distanceField": "distance"
}},
{"$match": {
"datetime": {
"$gte": ISODate("2019-03-01T00:00:00Z"),
"$lt": ISODate("2019-03-02T00:00:00Z")
}
}}
])
I get no results. But if I change the coordinates to [45.180584858570136,5.7602691650390625] which is 53.82 meters away from the first point, I get 16 results like this one :
{
"_id" : ObjectId("5c791c276bc27675f3bd2d7f"),
"user" : ObjectId("5c4620c96bc27618b1a39cfe"),
"coordinates" : {
"type" : "Point",
"coordinates" : [
45.1837952,
5.7204736
]
},
"datetime" : ISODate("2019-03-01T11:37:04.000Z"),
"_updated" : ISODate("2019-03-01T11:48:55.000Z"),
"_created" : ISODate("2019-03-01T11:48:55.000Z"),
"_etag" : "535e09d3d25f0b970fef8e45f220e41a99fd11f6",
"distance" : 4444.24394629098
}
Well, I can't understand why I get no results in the first case, because all the 16 results must match the given range...
Here's a more complete sample of data : https://gist.github.com/sylvainbx/502515b809341173e0ee36d4579ba31b
Does anyone has already saw this before or has any idea on how to solve this?
Ok, I've found the solution by myself. Changing the query as the following fixes the issue:
db.getCollection('locations').aggregate([
{
$geoNear: {
near: { type: "Point", coordinates: [45.180584858570136,5.760955810546876] },
key: "coordinates",
spherical: true,
distanceField: "distance",
maxDistance: 14239,
query: {
"datetime": {
"$gte": ISODate("2019-03-01T00:00:00Z"),
"$lt": ISODate("2019-03-02T00:00:00Z")
}
}
}
}
])
Related
I'm trying to write a MongoDB query that searches for documents within a radius centered on a specified location.
The query below works. It finds all documents that are within searching.radius radians of searching.coordinates.
However what I would like to do is add the current documents allowed_radius value to the searching.radius value, so that the allowed sphere is actually larger.
How can I phrase this query to make this possible?
Present Query:
collection.aggregate([
{
$project:{
location: "$location",
allowed_radius: "$allowed_radius"
}
},
{
$match: {
$and:
[
{ location: { $geoWithin: { $centerSphere: [ searching.coordinates, searching.radius ] }}},
{...},
...]
...}
]);
What I am trying to do (pseudo-query):
collection.aggregate([
{
$project:{
location: "$location",
allowed_radius: "$allowed_radius"
}
},
{
$match: {
$and:
[
{ location: { $geoWithin: { $centerSphere: [ searching.coordinates, { $add: [searching.radius, $allowed_radius]} ] }}},
{...},
...]
...}
]);
I tried using $geoWithin / $centerSphere, but couldn't make it work this way.
Here is another way of doing so, using the $geoNear operator:
Given this input:
db.collection.insert({
"airport": "LGW",
"id": 1,
"location": { type: "Point", coordinates: [-0.17818, 51.15609] },
"allowed_radius": 100
})
db.collection.insert({
"airport": "LGW",
"id": 2,
"location": { type: "Point", coordinates: [-0.17818, 51.15609] },
"allowed_radius": 0
})
db.collection.insert({
"airport": "ORY",
"id": 3,
"location": { type: "Point", coordinates: [2.35944, 48.72528] },
"allowed_radius": 10
})
And this index (which is required for $geoNear):
db.collection.createIndex( { location : "2dsphere" } )
With searching.radius = 1000:
db.collection.aggregate([
{ $geoNear: {
near: { "type" : "Point", "coordinates": [7.215872, 43.658411] },
distanceField: "distance",
spherical: true,
distanceMultiplier: 0.001
}},
{ $addFields: { radius: { "$add": ["$allowed_radius", 1000] } } },
{ $addFields: { isIn: { "$subtract": ["$distance", "$radius" ] } } },
{ $match: { isIn: { "$lte": 0 } } }
])
would return documents with id 1 (distance=1002 <= radius=1000+100) and 3 (distance=676 <= radius=1000+10) and discard id 2 (distance=1002 > 1000+0).
The distanceMultiplier parameter is used to bring back units to km.
$geoNear must be the first stage of an aggregation (due to the usage of the index I think), but one of the parameters of $geoNear is a match query on other fields.
Even if it requires the geospacial index, you can add additional dimensions to the index.
$geoNear doesn't take the location field as an argument, because it requires the collection to have a geospacial index. Thus $geoNear implicitly uses as location field (whatever the name of the field) the one indexed.
Finally, I'm pretty sure the last stages can be simplified.
The $geoNear stage is only used to project the distance on each record:
{ "airport" : "ORY", "distance" : 676.5790971238937, "location" : { "type" : "Point", "coordinates" : [ 2.35944, 48.72528 ] }, "allowed_radius" : 10, "id" : 3 }
{ "airport" : "LGW", "distance" : 1002.3351814526812, "location" : { "type" : "Point", "coordinates" : [ -0.17818, 51.15609 ] }, "allowed_radius" : 100, "id" : 1 }
{ "airport" : "LGW", "distance" : 1002.3351814526812, "location" : { "type" : "Point", "coordinates" : [ -0.17818, 51.15609 ] }, "allowed_radius" : 0, "id" : 2 }
In fact, the geoNear operator requires the use of the distanceField argument, which is used to project the computed distance on each record for the next stages of the query. At the end of the aggregation, returned records look like:
{
"airport" : "ORY",
"location" : { "type" : "Point", "coordinates" : [ 2.35944, 48.72528 ] },
"allowed_radius" : 10,
"id" : 3,
"distance" : 676.5790971238937,
"radius" : 1010,
"isIn" : -333.4209028761063
}
If necessary, you can remove fields produced by the query for the query (distance, radius, isIn) with a final $project stage. For instance: {"$project":{"distance":0}}
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 am having a problem with MongoDB query. My query to MongoDB database is:
Places.aggregate([
Stage-A
{ "$geoNear": {
"near": {
"type": "Point",
"coordinates": coord
},
"spherical": true,
"maxDistance" : maxDistance,
"query": {tags:{$all:tags}},
"limit": resultLimit,
"distanceField": "distance"
}
},
{Stage-B = check is stage-A have found any documents},
{Stage-C},
{Stage-D}
])
I want to check If the stage-A has found any results, if not, I want to move to stage-C with a different query. If Stage-A finds results, then I will directly move to final stage-D. My hypothetical aggregate query looks like this :
Places.aggregate([
Stage-A
{
"$geoNear": {
"near": {
"type": "Point",
"coordinates": coord
},
"spherical": true,
"maxDistance" : maxDistance,
"query": {tags:{$all:tags}},
"limit": resultLimit,
"distanceField": "distance"
}
},
Stage-B
{
In this stage-B, I want to check if the stage-A has found any results,
if results, I want to move to the last stage-D, otherwise stage-C then finally Stage-D
},
Stage-C
{
"$geoNear": {
"near": {
"type": "Point",
"coordinates": coord
},
"spherical": true,
"maxDistance" : maxDistance,
"query": {tags:{$in:tags}}, >> different query
"limit": resultLimit,
"distanceField": "distance"
}
},
Stage-D
{
$project:{
}
}
])
I was wondering if this logical flow is possible in MongoDB aggregate or something else. If so, what would be the stage-B? If not, what is the best way to solve this problem? I wanted to do all of these in a single request, so I can save some CPU cycles.
I am not getting the correct results returned when using $geoNear in the aggregate pipeline. The same query using a typical find() query (using $near) does in fact return the right results.
BUT, when removing the equality condition (on schedule.key), both queries return the correct data.
$geoNear using aggregate pipeline:
db.place.aggregate(
[
{
$geoNear: {
spherical: true,
near: { type: "Point", coordinates: [ 18.416145, -33.911973 ] },
distanceField: "dist"
}
},
{
$match: {
"schedule.key": { "$eq": "vo4lRN_Az0uwOkgBzOERyw" }
}
}
])
$near find query:
db.place.find(
{
"point" : {
$near: {
type: "Point",
coordinates: [ 18.416145,-33.911973 ]
}
},
"schedule.key" : {
$eq : "vo4lRN_Az0uwOkgBzOERyw"
}
})
A document in this collection looks something like this:
{
"_id" : UUID("da6ccbb1-3c7a-45d7-bc36-a5e6007cd919"),
"schedule" : {
"_id" : UUID("587de5b7-a744-4b28-baa8-e6efb5f7f921"),
"key" : "vo4lRN_Az0uwOkgBzOERyw"
},
"point" : {
"type" : "Point",
"coordinates" : [
18.425102,
-33.922153
]
},
"name" : "Cape Town"
}
I have created the appropriate index on the point field:
db.place.ensureIndex( { "point" : "2dsphere" } );
It's not the "same" query at all. There is a distinct difference in using a separate $match stage, since the "filtering" is only done "after" the "nearest resuts" are found. This means that you potentially return "less" results since the criteria is not issued in combination.
That's why there is a "query" option in $geoNear:
db.place.aggregate(
[
{
$geoNear: {
spherical: true,
near: { type: "Point", coordinates: [ 18.416145, -33.911973 ] },
distanceField: "dist",
query: {
"schedule.key": { "$eq": "vo4lRN_Az0uwOkgBzOERyw" }
}
}
}
])
Now that's the same query. Or it would be exactly the same if you used $nearSphere. Since $near does not account for the curvature of the earth in distance calcuations. $nearSphere and $geoNear does.
But the main point is combining with the "query" option, since that's the only way you truly get both criteria considered in the initial search.
db.places.aggregate([
{
$geoNear: {
near: { type: "Point", coordinates: [ -73.99279 , 40.719296 ] },
distanceField: "dist.calculated",
maxDistance: 2,
query: { type: "public" },
includeLocs: "dist.location",
num: 5,
spherical: true
}
}
])
I am Using aggregate method in mongodb.and this is working for me but I need projection on the document for only return some field to show. So how can I achieve projection in this method
Even OP's problem is solved in the comments I decided to add an detailed answer as it will help some one else.
As #hecnabae said you have to append $project stage to your query to retrieve data.
Here is an example:
db.getCollection("places").aggregate(
[
{
"$geoNear": {
"near": { "type": "Point", "coordinates": [ -73.99279 , 40.719296 ] },
"distanceField: "dist.calculated",
"maxDistance": 2,
"query": { "type": "public" },
"includeLocs": "dist.location",
"num": 5,
"spherical": true
}
},
{
"$project" : {
"Name" : 1.0,
"Distance" : "$dist.calculated",
"Location": "$dist.location"
}
}
],
{
"allowDiskUse" : false
}
);
This will return you an list of data as follows:
{
"_id" : ObjectId("5e3207128cbfe302dcbb1234"),
"Name" : "Your Place Name",
"Distance" : 17.98627020911569,
"Location" : [
58.1230154,
23.1232555
]
}