How to projection in $geoNear aggregate method - mongodb

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
]
}

Related

Need help on mongo aggregation query

Below is the sample data:
[{
"outdoor": {
"location": {
"type": "Point",
"coordinates": [
-92.41151,
35.11683
]
}
},
"geoHash": "dr72jwgnbbst", "status":"Active","customerId":"8047380094","locationId":"A0"
},
{
"outdoor": {
"location": {
"type": "Point",
"coordinates": [
-89.58342,
36.859161
]
}
},
"geoHash": "dn6qtkr5xk8m", "status":"Pending","customerId":"8047380094","locationId":"A1"
},
{
"outdoor": {
"location": {
"type": "Point",
"coordinates": [
-86.038762,
36.519016
]
}
},
"geoHash": "dn6zf0h6xtcp", "status":"Active","customerId":"8047380094","locationId":"A2"
},
{
"outdoor": {
"location": {
"type": "Point",
"coordinates": [
-98.3081936,
26.2143207
]
}
},
"geoHash": "9udj4unjmp9f", "status":"Pending","customerId":"8047380094","locationId":"A3"
},
{
"outdoor": {
"location": {
"type": "Point",
"coordinates": [
-98.5377275,
29.4878928
]
}
},
"geoHash": "9v1zv8p52t8u", "status":"Pending","customerId":"8047380094","locationId":"A4"
},
{
"outdoor": {
"location": {
"type": "Point",
"coordinates": [
-73.7018126,
42.641387
]
}
},
"geoHash": "dreddfeup69m", "status":"Pending","customerId":"8047380094","locationId":"A5"
},
{
"outdoor": {
"location": {
"type": "Point",
"coordinates": [
-111.865295,
33.431942
]
}
},
"geoHash": "9tbqnqn5jtwq", "status":"Active","customerId":"8047380094","locationId":"A6"
},
{
"outdoor": {
"location": {
"type": "Point",
"coordinates": [
-79.810763,
34.174603
]
}
},
"geoHash": "dnp4rv796rtz", "status":"Active","customerId":"8047380094","locationId":"A7"
}
]
Currently we are running 2 queries:
Query 1 - This query will give the counts by status grouped by geoHash substring.
db.locations.aggregate([{"$match": {"customerId": "8047380094"}}, {"$project": {"status": 1, "geoHash": {"$substr": ["$geoHash", 0, 2]}}}, {"$group": {"_id": {"geoHash": "$geoHash", "status": "$status"}, "statusCount": {"$sum": 1}}}],
{
"allowDiskUse": true
});
Output 1:
{ "_id" : { "geoHash" : "dr", "status" : "Active" }, "statusCount" : 1 }
{ "_id" : { "geoHash" : "dr", "status" : "Pending" }, "statusCount" : 1 }
{ "_id" : { "geoHash" : "dn", "status" : "Active" }, "statusCount" : 2 }
{ "_id" : { "geoHash" : "dn", "status" : "Pending" }, "statusCount" : 1 }
{ "_id" : { "geoHash" : "9u", "status" : "Pending" }, "statusCount" : 1 }
{ "_id" : { "geoHash" : "9v", "status" : "Pending" }, "statusCount" : 1 }
{ "_id" : { "geoHash" : "9t", "status" : "Active" }, "statusCount" : 1 }
Query 2 - We have a query to get the first location coordinates for the geohash group.
db.locations.aggregate([{"$match": {"customerId": "8047380094"}}, {"$project": {"geoHash": {"$substr": ["$geoHash", 0, 2]}, "locations": "$outdoor.location.coordinates"}}, {"$group": {"_id": "$geoHash", "locations": {"$push": "$locations"}}}, {"$project": {"_id": 1, "locations": {"$arrayElemAt": ["$locations", 0]}}}],
{
"allowDiskUse": true
});
Output 2:
{ "_id" : "dr", "locations" : [ -92.41151, 35.11683 ] }
{ "_id" : "dn", "locations" : [ -89.58342, 36.859161 ] }
{ "_id" : "9u", "locations" : [ -98.3081936, 26.2143207 ] }
{ "_id" : "9v", "locations" : [ -98.5377275, 29.4878928 ] }
{ "_id" : "9t", "locations" : [ -111.865295, 33.431942 ] }
Question 1:
Is there any we can combine both queries into 1 and get both the outputs in a single query?
Question 2:
If the total count is 1 (not by status), we need to get the locationId also? How can we achieve this in the same query?
In the above case,
for "9u", we need to return A3
for "9v", we need to return A4
for "9t", we need to return A6
Note: We are using spring boot application with spring mongo.
I didn't go what you try to achieve, because the question doesn't mention it. But for you both question, I can answer
You can use $facet to use create multiple aggregation pipeline
By using the first $facet stage, you can use additional stage to get your locationId;
Here is the code
{
$project: {
firstQuery: 1,
secondQuery: {
$map: {
input: "$secondQuery",
as: "s",
in: {
$mergeObjects: [
"$$s",
{
$arrayElemAt: [
{
$map: {
input: {
$filter: {
input: "$firstQuery",
as: "f",
cond: { $eq: ["$$f._id.geoHash", "$$s._id" ] }
}
},
in: { locationId: "$$this.locationId" }
}
},
0
]
}
]
}
}
}
}
}
Working Mongo playground
Note: Make sure { $eq: ["$$f._id.geoHash", "$$s._id" ] } will give one unique object. Else need another idea. And I feel your posted both queries more like same. So there can be a easy solution. But I answered based on the question you posted

$geoNear no result from near coordinates

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")
}
}
}
}
])

In MongoDB, how do I use a field in the document as input to a $geoWithin/$centerSphere expression?

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}}

How to group longitude and latitude reducing the decimal places of those points?

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.

MongoDB: Try to get explanation on aggregation

I try to get information on an aggregate I do on MongoDB 3.2.5 using {explain:true} in order to see if my indices are used :
db.mycollection.aggregate([{
"$geoNear": {
"near": {
type: "Point",
coordinates: [2.48043, 49.14128]
},
"spherical": true,
"distanceField": "distance",
"maxDistance": 500
}
}, {
"$match": {
"date": {
$gt: new ISODate("2016-01-01T01:01:01Z")
}
}
}, {
"$sort": {
"score": -1,
"distance": 1
}
}], {
explain: true
});
As a result I only got the stages aggregate :
{
"waitedMS": NumberLong(0),
"stages": [{
"$geoNear": {
"near": {
"type": "Point",
"coordinates": [
2.48043,
49.14128
]
},
"distanceField": "distance",
"limit": NumberLong(100),
"maxDistance": 500,
"query": {
},
"spherical": true,
"distanceMultiplier": 1
}
}, {
"$match": {
"date": {
"$gt": ISODate("2016-01-01T01:01:01Z")
}
}
}, {
"$sort": {
"sortKey": {
"score": -1,
"distance": 1
}
}
}],
"ok": 1
}
I don't have any information about doc scanned, index used, etc...
Can someone help me, please ?
as per manual
The operation returns a cursor with the document that contains
detailed information regarding the processing of the aggregation
pipeline. For example, the document may show, among other details,
which index, if any, the operation used.
So there is a kind of ambiguity, but probably if you swap $geoNear with $match - explain should show something more.