问题
I would like to execute a query where the distance is a dynamic field from the collection.
Example of entries in the collection:
{
name:'myName',
location: {lat:10, lng:20},
maximumDistance: 10
}
{
name:'myName2',
location: {lat:20, lng:20},
maximumDistance: 100
}
My goal is to retrieve all the elements from this collection where this location is close to a given location, let's say (10,10) but where the calculated distance is less than this maximumDistance field.
I can set maxDistance to a constant value (high enough to retrieve all the elements I want) in the near query and then do the filter in my java code but I would prefer to do it on the query it is maybe too SQL oriented.
回答1:
You won't be able to do this with a normal query as you can't dynamically set the distance per document. As of MongoDB 2.4 you can do this with the aggregation framework as they have added the geoNear operator to the starts of pipelines.
The first stage will be the geoNear which is very similar to the geonear command. We will also get the distance from the point specified (10,10) to the document as a result.
The second stage we will need to use the project operator to ad the different between the maximumDistance field and the computed geoNear distance.
Lastly, we match those documents that have a positive delta ((max - distance) > 0).
Here is the pipeline using the Asynchronous Java Driver's helper classes.
package example;
import static com.allanbank.mongodb.builder.AggregationProjectFields.include;
import static com.allanbank.mongodb.builder.QueryBuilder.where;
import static com.allanbank.mongodb.builder.expression.Expressions.field;
import static com.allanbank.mongodb.builder.expression.Expressions.set;
import static com.allanbank.mongodb.builder.expression.Expressions.subtract;
import com.allanbank.mongodb.bson.element.ArrayElement;
import com.allanbank.mongodb.builder.Aggregate;
import com.allanbank.mongodb.builder.AggregationGeoNear;
import com.allanbank.mongodb.builder.GeoJson;
public class AggregateGeoNear {
public static void main(String[] args) {
Aggregate aggregate = Aggregate
.builder()
.geoNear(
AggregationGeoNear.builder()
.location(GeoJson.p(10, 10))
.distanceField("distance"))
.project(
include("name", "location", "maximumDistance"),
set("delta",
subtract(field("maximumDistance"),
field("distance"))))
.match(where("delta").greaterThanOrEqualTo(0)).build();
System.out
.println(new ArrayElement("pipeline", aggregate.getPipeline()));
}
}
And here is the pipeline is created:
pipeline : [
{
'$geoNear' : {
near : [
10,
10
],
distanceField : 'distance',
spherical : false,
uniqueDocs : true
}
},
{
'$project' : {
name : 1,
location : 1,
maximumDistance : 1,
delta : {
'$subtract' : [
'$maximumDistance',
'$distance'
]
}
}
},
{
'$match' : {
delta : { '$gte' : 0 }
}
}
]
HTH - Rob.
P.S. The builders above are using a pre-release of the 1.2.0 version of the driver. The code is going through the build matrix as I type and should be released by Friday, March 22, 2013.
回答2:
regarding how to solve it with Spring Data, I have solve it by building a new GeoNearOperation implementation, since it's mandatory to include the maxDistance field name within the projection:
public class GeoNearOperation2 implements AggregationOperation {
private NearQuery nearQuery;
public GeoNearOperation2(NearQuery nearQuery) {
this.nearQuery = nearQuery;
}
public DBObject toDBObject(AggregationOperationContext context) {
DBObject dbObject = context.getMappedObject(nearQuery.toDBObject());
dbObject.put("distanceField", "distance");
return new BasicDBObject("$geoNear",dbObject);
}
}
来源:https://stackoverflow.com/questions/15536064/mongodb-near-geonear-query-with-variable-distance