Browse Source

DATAMONGO-1110 - Add support for $minDistance.

We now support $minDistance for NearQuery and Criteria. Please keep in mind that minDistance is only available for MongoDB 2.6 and better and can only be combined with $near or $nearSphere operator depending on the defined index type. Usage of $minDistance with NearQuery is only possible when a 2dsphere index is present. We also make sure $minDistance operator gets correctly nested when using GeoJSON types.

It is now possible to use a Range<Distance> parameter within the repository queries. This allows to define near queries like:

findByLocationNear(Point point, Range<Distance> distances);

The lower bound of the range is treated as the minimum distance while the upper one defines the maximum distance from the given point. In case a Distance parameter is provided it will serve as maxDistance.

Original pull request: #277.
pull/280/head
Christoph Strobl 11 years ago committed by Oliver Gierke
parent
commit
7e74ec6b62
  1. 29
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Criteria.java
  2. 76
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/NearQuery.java
  3. 5
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java
  4. 12
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ConvertingParameterAccessor.java
  5. 10
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParameterAccessor.java
  6. 64
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParameters.java
  7. 12
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParametersParameterAccessor.java
  8. 20
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryCreator.java
  9. 39
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoJsonTests.java
  10. 32
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoSpatial2DSphereTests.java
  11. 10
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoSpatial2DTests.java
  12. 36
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/CriteriaTests.java
  13. 15
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java
  14. 6
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/Person.java
  15. 3
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java
  16. 23
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoParametersParameterAccessorUnitTests.java
  17. 33
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoParametersUnitTests.java
  18. 57
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryCreatorUnitTests.java
  19. 16
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StubParameterAccessor.java
  20. 15
      src/main/asciidoc/reference/mongo-repositories.adoc
  21. 10
      src/main/asciidoc/reference/mongodb.adoc

29
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Criteria.java

@ -441,7 +441,8 @@ public class Criteria implements CriteriaDefinition { @@ -441,7 +441,8 @@ public class Criteria implements CriteriaDefinition {
*/
public Criteria maxDistance(double maxDistance) {
if (createNearCriteriaForCommand("$near", maxDistance) || createNearCriteriaForCommand("$nearSphere", maxDistance)) {
if (createNearCriteriaForCommand("$near", "$maxDistance", maxDistance)
|| createNearCriteriaForCommand("$nearSphere", "$maxDistance", maxDistance)) {
return this;
}
@ -449,6 +450,25 @@ public class Criteria implements CriteriaDefinition { @@ -449,6 +450,25 @@ public class Criteria implements CriteriaDefinition {
return this;
}
/**
* Creates a geospatial criterion using a {@literal $minDistance} operation, for use with {@literal $near} or
* {@literal $nearSphere}.
*
* @param minDistance
* @return
* @since 1.7
*/
public Criteria minDistance(double minDistance) {
if (createNearCriteriaForCommand("$near", "$minDistance", minDistance)
|| createNearCriteriaForCommand("$nearSphere", "$minDistance", minDistance)) {
return this;
}
criteria.put("$minDistance", minDistance);
return this;
}
/**
* Creates a criterion using the {@literal $elemMatch} operator
*
@ -605,7 +625,7 @@ public class Criteria implements CriteriaDefinition { @@ -605,7 +625,7 @@ public class Criteria implements CriteriaDefinition {
}
}
private boolean createNearCriteriaForCommand(String command, double maxDistance) {
private boolean createNearCriteriaForCommand(String command, String operation, double maxDistance) {
if (!criteria.containsKey(command)) {
return false;
@ -615,14 +635,13 @@ public class Criteria implements CriteriaDefinition { @@ -615,14 +635,13 @@ public class Criteria implements CriteriaDefinition {
if (existingNearOperationValue instanceof DBObject) {
((DBObject) existingNearOperationValue).put("$maxDistance", maxDistance);
((DBObject) existingNearOperationValue).put(operation, maxDistance);
return true;
} else if (existingNearOperationValue instanceof GeoJson) {
BasicDBObject dbo = new BasicDBObject("$geometry", existingNearOperationValue)
.append("$maxDistance", maxDistance);
BasicDBObject dbo = new BasicDBObject("$geometry", existingNearOperationValue).append(operation, maxDistance);
criteria.put(command, dbo);
return true;

76
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/NearQuery.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2011-2014 the original author or authors.
* Copyright 2011-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -40,6 +40,7 @@ public final class NearQuery { @@ -40,6 +40,7 @@ public final class NearQuery {
private final Point point;
private Query query;
private Distance maxDistance;
private Distance minDistance;
private Metric metric;
private boolean spherical;
private Integer num;
@ -211,6 +212,63 @@ public final class NearQuery { @@ -211,6 +212,63 @@ public final class NearQuery {
return this;
}
/**
* Sets the minimum distance results shall have from the configured origin. If a {@link Metric} was set before the
* given value will be interpreted as being a value in that metric. E.g.
*
* <pre>
* NearQuery query = near(10.0, 20.0, Metrics.KILOMETERS).minDistance(150);
* </pre>
*
* Will set the minimum distance to 150 kilometers.
*
* @param minDistance
* @return
* @since 1.7
*/
public NearQuery minDistance(double minDistance) {
return minDistance(new Distance(minDistance, getMetric()));
}
/**
* Sets the minimum distance supplied in a given metric. Will normalize the distance but not reconfigure the query's
* result {@link Metric} if one was configured before.
*
* @param minDistance
* @param metric must not be {@literal null}.
* @return
* @since 1.7
*/
public NearQuery minDistance(double minDistance, Metric metric) {
Assert.notNull(metric);
return minDistance(new Distance(minDistance, metric));
}
/**
* Sets the minimum distance to the given {@link Distance}. Will set the returned {@link Metric} to be the one of the
* given {@link Distance} if no {@link Metric} was set before.
*
* @param distance must not be {@literal null}.
* @return
* @since 1.7
*/
public NearQuery minDistance(Distance distance) {
Assert.notNull(distance);
if (distance.getMetric() != Metrics.NEUTRAL) {
this.spherical(true);
}
if (this.metric == null) {
in(distance.getMetric());
}
this.minDistance = distance;
return this;
}
/**
* Returns the maximum {@link Distance}.
*
@ -220,6 +278,16 @@ public final class NearQuery { @@ -220,6 +278,16 @@ public final class NearQuery {
return this.maxDistance;
}
/**
* Returns the maximum {@link Distance}.
*
* @return
* @since 1.7
*/
public Distance getMinDistance() {
return this.minDistance;
}
/**
* Configures a {@link CustomMetric} with the given multiplier.
*
@ -352,7 +420,11 @@ public final class NearQuery { @@ -352,7 +420,11 @@ public final class NearQuery {
}
if (maxDistance != null) {
dbObject.put("maxDistance", this.maxDistance.getNormalizedValue());
dbObject.put("maxDistance", maxDistance.getNormalizedValue());
}
if (minDistance != null) {
dbObject.put("minDistance", minDistance.getNormalizedValue());
}
if (metric != null) {

5
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java

@ -366,6 +366,11 @@ public abstract class AbstractMongoQuery implements RepositoryQuery { @@ -366,6 +366,11 @@ public abstract class AbstractMongoQuery implements RepositoryQuery {
nearQuery.maxDistance(maxDistance).in(maxDistance.getMetric());
}
Distance minDistance = accessor.getMinDistance();
if (minDistance != null) {
nearQuery.minDistance(minDistance).in(minDistance.getMetric());
}
Pageable pageable = accessor.getPageable();
if (pageable != null) {
nearQuery.with(pageable);

12
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ConvertingParameterAccessor.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2011-2014 the original author or authors.
* Copyright 2011-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -104,6 +104,15 @@ public class ConvertingParameterAccessor implements MongoParameterAccessor { @@ -104,6 +104,15 @@ public class ConvertingParameterAccessor implements MongoParameterAccessor {
return delegate.getMaxDistance();
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.repository.query.MongoParameterAccessor#getMinDistance()
*/
@Override
public Distance getMinDistance() {
return delegate.getMinDistance();
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.repository.MongoParameterAccessor#getGeoNearLocation()
@ -252,4 +261,5 @@ public class ConvertingParameterAccessor implements MongoParameterAccessor { @@ -252,4 +261,5 @@ public class ConvertingParameterAccessor implements MongoParameterAccessor {
*/
Object nextConverted(MongoPersistentProperty property);
}
}

10
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParameterAccessor.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2011-2014 the original author or authors.
* Copyright 2011-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -50,4 +50,12 @@ public interface MongoParameterAccessor extends ParameterAccessor { @@ -50,4 +50,12 @@ public interface MongoParameterAccessor extends ParameterAccessor {
* @since 1.6
*/
TextCriteria getFullText();
/**
* Returns a {@link Distance} to be applied to {@literal $minDistance} for MongoDB geo queries.
*
* @return
* @since 1.7
*/
Distance getMinDistance();
}

64
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParameters.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2011-2014 the original author or authors.
* Copyright 2011-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -36,7 +36,8 @@ import org.springframework.data.repository.query.Parameters; @@ -36,7 +36,8 @@ import org.springframework.data.repository.query.Parameters;
*/
public class MongoParameters extends Parameters<MongoParameters, MongoParameter> {
private final Integer distanceIndex;
private final Integer minDistanceIndex;
private final Integer maxDistanceIndex;
private final Integer fullTextIndex;
private Integer nearIndex;
@ -51,9 +52,13 @@ public class MongoParameters extends Parameters<MongoParameters, MongoParameter> @@ -51,9 +52,13 @@ public class MongoParameters extends Parameters<MongoParameters, MongoParameter>
super(method);
List<Class<?>> parameterTypes = Arrays.asList(method.getParameterTypes());
this.distanceIndex = parameterTypes.indexOf(Distance.class);
this.fullTextIndex = parameterTypes.indexOf(TextCriteria.class);
int[] distances = distances(parameterTypes);
this.minDistanceIndex = distances[0];
this.maxDistanceIndex = distances[1];
if (this.nearIndex == null && isGeoNearMethod) {
this.nearIndex = getNearIndex(parameterTypes);
} else if (this.nearIndex == null) {
@ -62,13 +67,14 @@ public class MongoParameters extends Parameters<MongoParameters, MongoParameter> @@ -62,13 +67,14 @@ public class MongoParameters extends Parameters<MongoParameters, MongoParameter>
}
private MongoParameters(List<MongoParameter> parameters, Integer distanceIndex, Integer nearIndex,
Integer fullTextIndex) {
Integer fullTextIndex, Integer minDistanceIndex) {
super(parameters);
this.distanceIndex = distanceIndex;
this.maxDistanceIndex = distanceIndex;
this.nearIndex = nearIndex;
this.fullTextIndex = fullTextIndex;
this.minDistanceIndex = minDistanceIndex;
}
private final int getNearIndex(List<Class<?>> parameterTypes) {
@ -115,9 +121,21 @@ public class MongoParameters extends Parameters<MongoParameters, MongoParameter> @@ -115,9 +121,21 @@ public class MongoParameters extends Parameters<MongoParameters, MongoParameter>
* Returns the index of a {@link Distance} parameter to be used for geo queries.
*
* @return
* @deprecated since 1.7. Please use {@link #getMaxDistanceParameterIndex()} instead.
*/
@Deprecated
public int getDistanceIndex() {
return distanceIndex;
return getMaxDistanceParameterIndex();
}
/**
* Returns the index of the {@link Distance} parameter to be used for max distance in geo queries.
*
* @return
* @since 1.7
*/
public int getMaxDistanceParameterIndex() {
return maxDistanceIndex;
}
/**
@ -147,13 +165,45 @@ public class MongoParameters extends Parameters<MongoParameters, MongoParameter> @@ -147,13 +165,45 @@ public class MongoParameters extends Parameters<MongoParameters, MongoParameter>
return this.fullTextIndex != null && this.fullTextIndex.intValue() >= 0;
}
/**
* @return
* @since 1.7
*/
public boolean hasMinDistanceParameter() {
return minDistanceIndex != null && minDistanceIndex.intValue() >= 0;
}
/**
* @return
* @since 1.7
*/
public int getMinDistanceParameterIndex() {
return minDistanceIndex != null ? minDistanceIndex.intValue() : -1;
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.query.Parameters#createFrom(java.util.List)
*/
@Override
protected MongoParameters createFrom(List<MongoParameter> parameters) {
return new MongoParameters(parameters, this.distanceIndex, this.nearIndex, this.fullTextIndex);
return new MongoParameters(parameters, this.maxDistanceIndex, this.nearIndex, this.fullTextIndex,
this.minDistanceIndex);
}
private int[] distances(List<Class<?>> paramTypes) {
int maxDistance = paramTypes.lastIndexOf(Distance.class);
if (maxDistance == -1) {
return new int[] { -1, -1 };
}
int minDistance = paramTypes.indexOf(Distance.class);
if (minDistance == maxDistance) {
return new int[] { -1, maxDistance };
}
return new int[] { minDistance, maxDistance };
}
/**

12
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParametersParameterAccessor.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2011-2014 the original author or authors.
* Copyright 2011-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -53,6 +53,16 @@ public class MongoParametersParameterAccessor extends ParametersParameterAccesso @@ -53,6 +53,16 @@ public class MongoParametersParameterAccessor extends ParametersParameterAccesso
return index == -1 ? null : (Distance) getValue(index);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.repository.query.MongoParameterAccessor#getMinDistance()
*/
@Override
public Distance getMinDistance() {
int index = method.getParameters().getMinDistanceParameterIndex();
return index == -1 ? null : (Distance) getValue(index);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.repository.MongoParameterAccessor#getGeoNearLocation()

20
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryCreator.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2010-2014 the original author or authors.
* Copyright 2010-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -30,6 +30,8 @@ import org.springframework.data.geo.Point; @@ -30,6 +30,8 @@ import org.springframework.data.geo.Point;
import org.springframework.data.geo.Shape;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.context.PersistentPropertyPath;
import org.springframework.data.mongodb.core.index.GeoSpatialIndexType;
import org.springframework.data.mongodb.core.index.GeoSpatialIndexed;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.CriteriaDefinition;
@ -206,18 +208,24 @@ class MongoQueryCreator extends AbstractQueryCreator<Query, Criteria> { @@ -206,18 +208,24 @@ class MongoQueryCreator extends AbstractQueryCreator<Query, Criteria> {
case NEAR:
Distance distance = accessor.getMaxDistance();
Distance minDistance = accessor.getMinDistance();
Point point = accessor.getGeoNearLocation();
point = point == null ? nextAs(parameters, Point.class) : point;
boolean isSpherical = isSpherical(property);
if (distance == null) {
return criteria.near(point);
return isSpherical ? criteria.nearSphere(point) : criteria.near(point);
} else {
if (!Metrics.NEUTRAL.equals(distance.getMetric())) {
if (isSpherical || !Metrics.NEUTRAL.equals(distance.getMetric())) {
criteria.nearSphere(point);
} else {
criteria.near(point);
}
criteria.maxDistance(distance.getNormalizedValue());
if (minDistance != null) {
criteria.minDistance(minDistance.getNormalizedValue());
}
}
return criteria;
case WITHIN:
@ -393,4 +401,10 @@ class MongoQueryCreator extends AbstractQueryCreator<Query, Criteria> { @@ -393,4 +401,10 @@ class MongoQueryCreator extends AbstractQueryCreator<Query, Criteria> {
return source.replaceAll("\\*", ".*");
}
private boolean isSpherical(MongoPersistentProperty property) {
GeoSpatialIndexed index = property.findAnnotation(GeoSpatialIndexed.class);
return index != null && index.type().equals(GeoSpatialIndexType.GEO_2DSPHERE);
}
}

39
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoJsonTests.java

@ -278,6 +278,45 @@ public class GeoJsonTests { @@ -278,6 +278,45 @@ public class GeoJsonTests {
assertThat(result.geoJsonGeometryCollection, equalTo(obj.geoJsonGeometryCollection));
}
/**
* @see DATAMONGO-1110
*/
@Test
public void nearWithMinDistance() {
Point point = new GeoJsonPoint(-73.99171, 40.738868);
List<Venue2DSphere> venues = template.find(query(where("location").near(point).minDistance(0.01)),
Venue2DSphere.class);
assertThat(venues.size(), is(11));
}
/**
* @see DATAMONGO-1110
*/
@Test
public void nearSphereWithMinDistance() {
Point point = new GeoJsonPoint(-73.99171, 40.738868);
List<Venue2DSphere> venues = template.find(query(where("location").nearSphere(point).minDistance(0.01)),
Venue2DSphere.class);
assertThat(venues.size(), is(11));
}
/**
* @see DATAMONGO-1135
*/
@Test
public void nearWithMinAndMaxDistance() {
GeoJsonPoint point = new GeoJsonPoint(-73.99171, 40.738868);
Query query = query(where("location").near(point).minDistance(0.01).maxDistance(100));
List<Venue2DSphere> venues = template.find(query, Venue2DSphere.class);
assertThat(venues.size(), is(2));
}
private void addVenues() {
template.insert(new Venue2DSphere("Penn Station", -73.99408, 40.75057));

32
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoSpatial2DSphereTests.java

@ -18,17 +18,24 @@ package org.springframework.data.mongodb.core.geo; @@ -18,17 +18,24 @@ package org.springframework.data.mongodb.core.geo;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.springframework.data.mongodb.core.query.Criteria.*;
import static org.springframework.data.mongodb.core.query.Query.*;
import java.util.List;
import org.junit.Test;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.geo.GeoResults;
import org.springframework.data.geo.Metric;
import org.springframework.data.geo.Metrics;
import org.springframework.data.geo.Point;
import org.springframework.data.mongodb.core.IndexOperations;
import org.springframework.data.mongodb.core.Venue;
import org.springframework.data.mongodb.core.index.GeoSpatialIndexType;
import org.springframework.data.mongodb.core.index.GeospatialIndex;
import org.springframework.data.mongodb.core.index.IndexField;
import org.springframework.data.mongodb.core.index.IndexInfo;
import org.springframework.data.mongodb.core.query.NearQuery;
/**
* @author Christoph Strobl
@ -55,6 +62,30 @@ public class GeoSpatial2DSphereTests extends AbstractGeoSpatialTests { @@ -55,6 +62,30 @@ public class GeoSpatial2DSphereTests extends AbstractGeoSpatialTests {
assertThat(fields, hasItem(IndexField.geo("location")));
}
/**
* @see DATAMONGO-1110
*/
@Test
public void geoNearWithMinDistance() {
NearQuery geoNear = NearQuery.near(-73, 40, Metrics.KILOMETERS).num(10).minDistance(1);
GeoResults<Venue> result = template.geoNear(geoNear, Venue.class);
assertThat(result.getContent().size(), is(not(0)));
assertThat(result.getAverageDistance().getMetric(), is((Metric) Metrics.KILOMETERS));
}
/**
* @see DATAMONGO-1110
*/
@Test
public void nearSphereWithMinDistance() {
Point point = new Point(-73.99171, 40.738868);
List<Venue> venues = template.find(query(where("location").nearSphere(point).minDistance(0.01)), Venue.class);
assertThat(venues.size(), is(1));
}
@Override
protected void createIndex() {
template.indexOps(Venue.class).ensureIndex(new GeospatialIndex("location").typed(GeoSpatialIndexType.GEO_2DSPHERE));
@ -64,5 +95,4 @@ public class GeoSpatial2DSphereTests extends AbstractGeoSpatialTests { @@ -64,5 +95,4 @@ public class GeoSpatial2DSphereTests extends AbstractGeoSpatialTests {
protected void dropIndex() {
template.indexOps(Venue.class).dropIndex("location_2dsphere");
}
}

10
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoSpatial2DTests.java

@ -70,6 +70,16 @@ public class GeoSpatial2DTests extends AbstractGeoSpatialTests { @@ -70,6 +70,16 @@ public class GeoSpatial2DTests extends AbstractGeoSpatialTests {
assertThat(fields, hasItem(IndexField.geo("location")));
}
/**
* @see DATAMONGO-1110
*/
@Test
public void nearPointWithMinDistance() {
Point point = new Point(-73.99171, 40.738868);
List<Venue> venues = template.find(query(where("location").near(point).minDistance(0.01)), Venue.class);
assertThat(venues.size(), is(5));
}
@Override
protected void createIndex() {
template.indexOps(Venue.class).ensureIndex(new GeospatialIndex("location").typed(GeoSpatialIndexType.GEO_2D));

36
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/CriteriaTests.java

@ -211,4 +211,40 @@ public class CriteriaTests { @@ -211,4 +211,40 @@ public class CriteriaTests {
assertThat(dbo, IsBsonObject.isBsonObject().containing("foo.$nearSphere.$maxDistance", 50D));
}
/**
* @see DATAMONGO-1110
*/
@Test
public void minDistanceShouldBeMappedInsideNearWhenUsedAlongWithGeoJsonType() {
DBObject dbo = new Criteria("foo").near(new GeoJsonPoint(100, 200)).minDistance(50D).getCriteriaObject();
assertThat(dbo, IsBsonObject.isBsonObject().containing("foo.$near.$minDistance", 50D));
}
/**
* @see DATAMONGO-1110
*/
@Test
public void minDistanceShouldBeMappedInsideNearSphereWhenUsedAlongWithGeoJsonType() {
DBObject dbo = new Criteria("foo").nearSphere(new GeoJsonPoint(100, 200)).minDistance(50D).getCriteriaObject();
assertThat(dbo, IsBsonObject.isBsonObject().containing("foo.$nearSphere.$minDistance", 50D));
}
/**
* @see DATAMONGO-1110
*/
@Test
public void minAndMaxDistanceShouldBeMappedInsideNearSphereWhenUsedAlongWithGeoJsonType() {
DBObject dbo = new Criteria("foo").nearSphere(new GeoJsonPoint(100, 200)).minDistance(50D).maxDistance(100D)
.getCriteriaObject();
assertThat(dbo, IsBsonObject.isBsonObject().containing("foo.$nearSphere.$minDistance", 50D));
assertThat(dbo, IsBsonObject.isBsonObject().containing("foo.$nearSphere.$maxDistance", 100D));
}
}

15
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java

@ -1165,4 +1165,19 @@ public abstract class AbstractPersonRepositoryIntegrationTests { @@ -1165,4 +1165,19 @@ public abstract class AbstractPersonRepositoryIntegrationTests {
result.close();
}
}
/**
* @see DATAMONGO-1110
*/
@Test
public void executesGeoNearQueryForResultsCorrectlyWhenGivenMinAndMaxDistance() {
Point point = new Point(-73.99171, 40.738868);
dave.setLocation(point);
repository.save(dave);
GeoResults<Person> results = repository.findPersonByLocationNear(new Point(-73.99, 40.73), new Distance(0.01,
Metrics.KILOMETERS), new Distance(2000, Metrics.KILOMETERS));
assertThat(results.getContent().isEmpty(), is(false));
}
}

6
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/Person.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2010-2013 the original author or authors.
* Copyright 2010-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -21,6 +21,7 @@ import java.util.List; @@ -21,6 +21,7 @@ import java.util.List;
import java.util.Set;
import org.springframework.data.geo.Point;
import org.springframework.data.mongodb.core.index.GeoSpatialIndexType;
import org.springframework.data.mongodb.core.index.GeoSpatialIndexed;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.DBRef;
@ -32,6 +33,7 @@ import org.springframework.data.mongodb.core.mapping.Field; @@ -32,6 +33,7 @@ import org.springframework.data.mongodb.core.mapping.Field;
*
* @author Oliver Gierke
* @author Thomas Darimont
* @author Christoph Strobl
*/
@Document
public class Person extends Contact {
@ -49,7 +51,7 @@ public class Person extends Contact { @@ -49,7 +51,7 @@ public class Person extends Contact {
List<String> skills;
@GeoSpatialIndexed private Point location;
@GeoSpatialIndexed(type = GeoSpatialIndexType.GEO_2DSPHERE) private Point location;
private @Field("add") Address address;
private Set<Address> shippingAddresses;

3
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java

@ -169,6 +169,9 @@ public interface PersonRepository extends MongoRepository<Person, String>, Query @@ -169,6 +169,9 @@ public interface PersonRepository extends MongoRepository<Person, String>, Query
GeoResults<Person> findByLocationNear(Point point, Distance maxDistance);
/** @see DATAMONGO-1110 */
GeoResults<Person> findPersonByLocationNear(Point point, Distance maxDistance, Distance minDistance);
GeoPage<Person> findByLocationNear(Point point, Distance maxDistance, Pageable pageable);
List<Person> findByCreator(User user);

23
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoParametersParameterAccessorUnitTests.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2011-2014 the original author or authors.
* Copyright 2011-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -96,12 +96,33 @@ public class MongoParametersParameterAccessorUnitTests { @@ -96,12 +96,33 @@ public class MongoParametersParameterAccessorUnitTests {
equalTo("{ \"$text\" : { \"$search\" : \"data\"}}"));
}
/**
* @see DATAMONGO-1110
*/
@Test
public void shouldDetectMinAndMaxDistance() throws NoSuchMethodException, SecurityException {
Method method = PersonRepository.class.getMethod("findByLocationNear", Point.class, Distance.class, Distance.class);
MongoQueryMethod queryMethod = new MongoQueryMethod(method, metadata, context);
Distance min = new Distance(10, Metrics.KILOMETERS);
Distance max = new Distance(20, Metrics.KILOMETERS);
MongoParameterAccessor accessor = new MongoParametersParameterAccessor(queryMethod, new Object[] {
new Point(10, 20), min, max });
assertThat(accessor.getMinDistance(), is(min));
assertThat(accessor.getMaxDistance(), is(max));
}
interface PersonRepository extends Repository<Person, Long> {
List<Person> findByLocationNear(Point point);
List<Person> findByLocationNear(Point point, Distance distance);
List<Person> findByLocationNear(Point point, Distance minDistance, Distance maxDistance);
List<Person> findByFirstname(String firstname, TextCriteria fullText);
}
}

33
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoParametersUnitTests.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2011-2014 the original author or authors.
* Copyright 2011-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -50,7 +50,7 @@ public class MongoParametersUnitTests { @@ -50,7 +50,7 @@ public class MongoParametersUnitTests {
MongoParameters parameters = new MongoParameters(method, false);
assertThat(parameters.getNumberOfParameters(), is(2));
assertThat(parameters.getDistanceIndex(), is(1));
assertThat(parameters.getMaxDistanceParameterIndex(), is(1));
assertThat(parameters.getBindableParameters().getNumberOfParameters(), is(1));
Parameter parameter = parameters.getParameter(1);
@ -121,6 +121,33 @@ public class MongoParametersUnitTests { @@ -121,6 +121,33 @@ public class MongoParametersUnitTests {
assertThat(parameters.getParameter(parameters.getFullTextParameterIndex()).isSpecialParameter(), is(true));
}
/**
* @see DATAMONGO-1110
*/
@Test
public void shouldFindMinAndMaxDistanceParameters() throws NoSuchMethodException, SecurityException {
Method method = PersonRepository.class.getMethod("findByLocationNear", Point.class, Distance.class, Distance.class);
MongoParameters parameters = new MongoParameters(method, false);
assertThat(parameters.getMinDistanceParameterIndex(), is(1));
assertThat(parameters.getMaxDistanceParameterIndex(), is(2));
}
/**
* @see DATAMONGO-1110
*/
@Test
public void shouldNotHaveMinDistanceIfOnlyOneDistanceParameterPresent() throws NoSuchMethodException,
SecurityException {
Method method = PersonRepository.class.getMethod("findByLocationNear", Point.class, Distance.class);
MongoParameters parameters = new MongoParameters(method, false);
assertThat(parameters.getMinDistanceParameterIndex(), is(-1));
assertThat(parameters.getMaxDistanceParameterIndex(), is(1));
}
interface PersonRepository {
List<Person> findByLocationNear(Point point, Distance distance);
@ -136,5 +163,7 @@ public class MongoParametersUnitTests { @@ -136,5 +163,7 @@ public class MongoParametersUnitTests {
GeoResults<Person> validDoubleArrays(double[] first, @Near double[] second);
List<Person> findByNameAndText(String name, TextCriteria text);
List<Person> findByLocationNear(Point point, Distance min, Distance max);
}
}

57
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryCreatorUnitTests.java

@ -45,6 +45,8 @@ import org.springframework.data.mapping.context.MappingContext; @@ -45,6 +45,8 @@ import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mongodb.core.Person;
import org.springframework.data.mongodb.core.Venue;
import org.springframework.data.mongodb.core.convert.MongoConverter;
import org.springframework.data.mongodb.core.index.GeoSpatialIndexType;
import org.springframework.data.mongodb.core.index.GeoSpatialIndexed;
import org.springframework.data.mongodb.core.mapping.DBRef;
import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
@ -503,6 +505,54 @@ public class MongoQueryCreatorUnitTests { @@ -503,6 +505,54 @@ public class MongoQueryCreatorUnitTests {
assertThat(query, is(query(where("address.geo").within(shape))));
}
/**
* @see DATAMONGO-1110
*/
@Test
public void shouldCreateNearSphereQueryForSphericalProperty() {
Point point = new Point(10, 20);
PartTree tree = new PartTree("findByAddress2dSphere_GeoNear", User.class);
MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, point), context);
Query query = creator.createQuery();
assertThat(query, is(query(where("address2dSphere.geo").nearSphere(point))));
}
/**
* @see DATAMONGO-1110
*/
@Test
public void shouldCreateNearSphereQueryForSphericalPropertyHavingDistanceWithDefaultMetric() {
Point point = new Point(1.0, 1.0);
Distance distance = new Distance(1.0);
PartTree tree = new PartTree("findByAddress2dSphere_GeoNear", User.class);
MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, point, distance), context);
Query query = creator.createQuery();
assertThat(query, is(query(where("address2dSphere.geo").nearSphere(point).maxDistance(1.0))));
}
/**
* @see DATAMONGO-1110
*/
@Test
public void shouldCreateNearQueryForMinMaxDistance() {
Point point = new Point(10, 20);
Distance min = new Distance(10);
Distance max = new Distance(20);
PartTree tree = new PartTree("findByAddress_GeoNear", User.class);
MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, point, min, max), context);
Query query = creator.createQuery();
assertThat(query, is(query(where("address.geo").near(point).minDistance(10D).maxDistance(20D))));
}
interface PersonRepository extends Repository<Person, Long> {
List<Person> findByLocationNearAndFirstname(Point location, Distance maxDistance, String firstname);
@ -517,6 +567,8 @@ public class MongoQueryCreatorUnitTests { @@ -517,6 +567,8 @@ public class MongoQueryCreatorUnitTests {
List<String> emailAddresses;
Address address;
Address2dSphere address2dSphere;
}
static class Address {
@ -524,4 +576,9 @@ public class MongoQueryCreatorUnitTests { @@ -524,4 +576,9 @@ public class MongoQueryCreatorUnitTests {
String street;
Point geo;
}
static class Address2dSphere {
String street;
@GeoSpatialIndexed(type = GeoSpatialIndexType.GEO_2DSPHERE) Point geo;
}
}

16
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StubParameterAccessor.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2011-2014 the original author or authors.
* Copyright 2011-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -30,11 +30,13 @@ import org.springframework.data.repository.query.ParameterAccessor; @@ -30,11 +30,13 @@ import org.springframework.data.repository.query.ParameterAccessor;
* Simple {@link ParameterAccessor} that returns the given parameters unfiltered.
*
* @author Oliver Gierke
* @author Christoh Strobl
*/
class StubParameterAccessor implements MongoParameterAccessor {
private final Object[] values;
private Distance distance;
private Distance minDistance;
/**
* Creates a new {@link ConvertingParameterAccessor} backed by a {@link StubParameterAccessor} simply returning the
@ -54,7 +56,12 @@ class StubParameterAccessor implements MongoParameterAccessor { @@ -54,7 +56,12 @@ class StubParameterAccessor implements MongoParameterAccessor {
for (Object value : values) {
if (value instanceof Distance) {
this.distance = (Distance) value;
if (this.distance == null) {
this.distance = (Distance) value;
} else {
this.minDistance = this.distance;
this.distance = (Distance) value;
}
}
}
}
@ -99,6 +106,11 @@ class StubParameterAccessor implements MongoParameterAccessor { @@ -99,6 +106,11 @@ class StubParameterAccessor implements MongoParameterAccessor {
return distance;
}
@Override
public Distance getMinDistance() {
return minDistance;
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.query.ParameterAccessor#iterator()

15
src/main/asciidoc/reference/mongo-repositories.adoc

@ -210,6 +210,14 @@ NOTE: Note that for version 1.0 we currently don't support referring to paramete @@ -210,6 +210,14 @@ NOTE: Note that for version 1.0 we currently don't support referring to paramete
| `findByLocationNear(Point point)`
| `{"location" : {"$near" : [x,y]}}`
| `Near`
| `findByLocationNear(Point point, Distance max)`
| `{"location" : {"$near" : [x,y], "$maxDistance" : max}}`
| `Near`
| `findByLocationNear(Point point, Distance min, Distance max)`
| `{"location" : {"$near" : [x,y], "$minDistance" : min, "$maxDistance" : max}}`
| `Within`
| `findByLocationWithin(Circle circle)`
| `{"location" : {"$geoWithin" : {"$center" : [ [x, y], distance]}}}`
@ -283,6 +291,8 @@ Distance distance = new Distance(200, Metrics.KILOMETERS); @@ -283,6 +291,8 @@ Distance distance = new Distance(200, Metrics.KILOMETERS);
As you can see using a `Distance` equipped with a `Metric` causes `$nearSphere` clause to be added instead of a plain `$near`. Beyond that the actual distance gets calculated according to the `Metrics` used.
NOTE: Using `@GeoSpatialIndexed(type = GeoSpatialIndexType.GEO_2DSPHERE)` on the target property forces usage of `$nearSphere` operator.
==== Geo-near queries
[source,java]
@ -296,6 +306,11 @@ public interface PersonRepository extends MongoRepository<Person, String> @@ -296,6 +306,11 @@ public interface PersonRepository extends MongoRepository<Person, String>
// Metric: {'geoNear' : 'person', 'near' : [x, y], 'maxDistance' : distance,
// 'distanceMultiplier' : metric.multiplier, 'spherical' : true }
GeoResults<Person> findByLocationNear(Point location, Distance distance);
// Metric: {'geoNear' : 'person', 'near' : [x, y], 'minDistance' : min,
// 'maxDistance' : max, 'distanceMultiplier' : metric.multiplier,
// 'spherical' : true }
GeoResults<Person> findByLocationNear(Point location, Distance min, Distance max);
// {'geoNear' : 'location', 'near' : [x, y] }
GeoResults<Person> findByLocationNear(Point location);

10
src/main/asciidoc/reference/mongodb.adoc

@ -1013,6 +1013,7 @@ There are also methods on the Criteria class for geospatial queries. Here is a l @@ -1013,6 +1013,7 @@ There are also methods on the Criteria class for geospatial queries. Here is a l
* `Criteria` *withinSphere* `(Circle circle)` Creates a geospatial criterion using `$geoWithin $center` operators.
* `Criteria` *near* `(Point point)` Creates a geospatial criterion using a `$near `operation
* `Criteria` *nearSphere* `(Point point)` Creates a geospatial criterion using `$nearSphere$center` operations. This is only available for MongoDB 1.7 and higher.
* `Criteria` *minDistance* `(double minDistance)` Creates a geospatial criterion using the `$minDistance` operation, for use with $near.
* `Criteria` *maxDistance* `(double maxDistance)` Creates a geospatial criterion using the `$maxDistance` operation, for use with $near.
The `Query` class has some additional methods used to provide options for the query.
@ -1111,7 +1112,7 @@ List<Venue> venues = @@ -1111,7 +1112,7 @@ List<Venue> venues =
template.find(new Query(Criteria.where("location").within(box)), Venue.class);
----
To find venues near a `Point`, the following query can be used
To find venues near a `Point`, the following queries can be used
[source,java]
----
@ -1120,6 +1121,13 @@ List<Venue> venues = @@ -1120,6 +1121,13 @@ List<Venue> venues =
template.find(new Query(Criteria.where("location").near(point).maxDistance(0.01)), Venue.class);
----
[source,java]
----
Point point = new Point(-73.99171, 40.738868);
List<Venue> venues =
template.find(new Query(Criteria.where("location").near(point).minDistance(0.01).maxDistance(100)), Venue.class);
----
To find venues near a `Point` using spherical coordines the following query can be used
[source,java]

Loading…
Cancel
Save