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.
Related
I have the following document in my MongoDB collection, which I would like to be able to do a query that check if point that is provided by the user is inside a bbox array that is stored in the collection.
{
"type": "Feature",
"properties": {
"place_id": 298104298,
"osm_type": "relation",
"osm_id": 80500,
"display_name": "Australia",
"place_rank": 4,
"category": "boundary",
"type": "administrative",
"importance": 0.8521350639151115,
"address": {
"country": "Australia",
"country_code": "au"
}
},
"bbox": [
72.2461932,
-55.3228175,
168.2261259,
-9.0880125
]
}
What I would like to do is a geoIntersect or geoWithin query.
for example:
[{
$match: {
bbox: {
$nearSphere: {
$geometry: {
type: 'Point',
coordinates: [
-73.9667,
40.78
]
}
}
}
}
}]
I have also tried
[{
$project: {
geometry: 0
}
}, {
$match: {
bbox: {
$geoWithin: {
$box: [
[
-73.9667,
40.78
],
[
40.78,
-73.9667
]
]
}
}
}
}]
However that did return results but wrong results the geo location should return NULL as the location is Antarctic Ice shield, Antarctica - (-73.9667,40.78)
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
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'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.