diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/CountQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/CountQuery.java index 339155f57..6d61f6109 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/CountQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/CountQuery.java @@ -23,8 +23,8 @@ import java.util.List; import java.util.Map; import org.bson.Document; - import org.springframework.data.geo.Point; +import org.springframework.data.mongodb.core.query.MetricConversion; import org.springframework.lang.Nullable; import org.springframework.util.ObjectUtils; @@ -162,7 +162,8 @@ class CountQuery { boolean spheric = source.containsKey("$nearSphere"); Object $near = spheric ? source.get("$nearSphere") : source.get("$near"); - Number maxDistance = source.containsKey("$maxDistance") ? (Number) source.get("$maxDistance") : Double.MAX_VALUE; + Number maxDistance = getMaxDistance(source, $near, spheric); + List $centerMax = Arrays.asList(toCenterCoordinates($near), maxDistance); Document $geoWithinMax = new Document("$geoWithin", new Document(spheric ? "$centerSphere" : "$center", $centerMax)); @@ -197,6 +198,24 @@ class CountQuery { return new Document("$and", criteria); } + private static Number getMaxDistance(Document source, Object $near, boolean spheric) { + + Number maxDistance = Double.MAX_VALUE; + if(source.containsKey("$maxDistance")) { // legacy coordinate pair + maxDistance = (Number) source.get("$maxDistance"); + } else if ($near instanceof Document) { + Document nearDoc = (Document)$near; + if(nearDoc.containsKey("$maxDistance")) { + maxDistance = (Number) nearDoc.get("$maxDistance"); + // geojson is in Meters but we need radians x/(6378.1*1000) + if(spheric && nearDoc.containsKey("$geometry")) { + maxDistance = MetricConversion.metersToRadians(maxDistance.doubleValue()); + } + } + } + return maxDistance; + } + private static boolean containsNear(Document source) { return source.containsKey("$near") || source.containsKey("$nearSphere"); } @@ -220,10 +239,16 @@ class CountQuery { return Arrays.asList(((Point) value).getX(), ((Point) value).getY()); } - if (value instanceof Document && ((Document) value).containsKey("x")) { - - Document point = (Document) value; - return Arrays.asList(point.get("x"), point.get("y")); + if (value instanceof Document ) { + Document document = (Document) value; + if(document.containsKey("x")) { + Document point = document; + return Arrays.asList(point.get("x"), point.get("y")); + } + else if (document.containsKey("$geometry")) { + Document geoJsonPoint = document.get("$geometry", Document.class); + return geoJsonPoint.get("coordinates"); + } } return value; diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/MetricConversion.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/MetricConversion.java index 30c43f448..53cf84e47 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/MetricConversion.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/MetricConversion.java @@ -17,6 +17,7 @@ package org.springframework.data.mongodb.core.query; import java.math.BigDecimal; +import java.math.MathContext; import java.math.RoundingMode; import org.springframework.data.geo.Distance; @@ -27,6 +28,7 @@ import org.springframework.data.geo.Metrics; * {@link Metric} and {@link Distance} conversions using the metric system. * * @author Mark Paluch + * @author Christoph Strobl * @since 2.2 */ public class MetricConversion { @@ -61,6 +63,28 @@ public class MetricConversion { .doubleValue(); } + /** + * Return {@code distance} in radians (on an earth like sphere). + * + * @param distance must not be {@literal null}. + * @return distance in rads. + * @since 3.4 + */ + public static double toRadians(Distance distance) { + return metersToRadians(getDistanceInMeters(distance)); + } + + /** + * Return {@code distance} in radians (on an earth like sphere). + * + * @param meters + * @return distance in rads. + * @since 3.4 + */ + public static double metersToRadians(double meters) { + return BigDecimal.valueOf(meters).divide(METERS_MULTIPLIER, MathContext.DECIMAL64).doubleValue(); + } + /** * Return {@code metric} to meters multiplier. * diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/CountQueryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/CountQueryUnitTests.java index e6cd8abde..473227a85 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/CountQueryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/CountQueryUnitTests.java @@ -29,6 +29,7 @@ import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver; import org.springframework.data.mongodb.core.convert.MappingMongoConverter; import org.springframework.data.mongodb.core.convert.NoOpDbRefResolver; import org.springframework.data.mongodb.core.convert.QueryMapper; +import org.springframework.data.mongodb.core.geo.GeoJsonPoint; import org.springframework.data.mongodb.core.mapping.MongoMappingContext; import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; import org.springframework.data.mongodb.core.query.Criteria; @@ -155,6 +156,39 @@ class CountQueryUnitTests { "{\"$or\" : [ { \"name\": \"food\" }, {\"location\": {\"$geoWithin\": {\"$center\": [[-73.99171, 40.738868], 10.0]}}} ]}")); } + @Test // GH-4004 + void nearToGeoWithinWithMaxDistanceUsingGeoJsonSource() { + + Query source = query(new Criteria().orOperator(where("name").is("food"), + where("location").near(new GeoJsonPoint(-73.99171, 40.738868)).maxDistance(10))); + + org.bson.Document target = postProcessQueryForCount(source); + assertThat(target).isEqualTo(org.bson.Document.parse( + "{\"$or\" : [ { \"name\": \"food\" }, {\"location\": {\"$geoWithin\": {\"$center\": [[-73.99171, 40.738868], 10.0]}}} ]}")); + } + + @Test // GH-4004 + void nearSphereToGeoWithinWithoutMaxDistanceUsingGeoJsonSource() { + + Query source = query(new Criteria().orOperator(where("name").is("food"), + where("location").nearSphere(new GeoJsonPoint(-73.99171, 40.738868)))); + + org.bson.Document target = postProcessQueryForCount(source); + assertThat(target).isEqualTo(org.bson.Document.parse( + "{\"$or\" : [ { \"name\": \"food\" }, {\"location\": {\"$geoWithin\": {\"$centerSphere\": [[-73.99171, 40.738868], 1.7976931348623157E308]}}} ]}")); + } + + @Test // GH-4004 + void nearSphereToGeoWithinWithMaxDistanceUsingGeoJsonSource() { + + Query source = query(new Criteria().orOperator(where("name").is("food"), where("location") + .nearSphere(new GeoJsonPoint(-73.99171, 40.738868)).maxDistance/*in meters for geojson*/(10d))); + + org.bson.Document target = postProcessQueryForCount(source); + assertThat(target).isEqualTo(org.bson.Document.parse( + "{\"$or\" : [ { \"name\": \"food\" }, {\"location\": {\"$geoWithin\": {\"$centerSphere\": [[-73.99171, 40.738868], 1.567855942887398E-6]}}} ]}")); + } + private org.bson.Document postProcessQueryForCount(Query source) { org.bson.Document intermediate = mapper.getMappedObject(source.getQueryObject(), (MongoPersistentEntity) null); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/MetricConversionUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/MetricConversionUnitTests.java index fc8b072f3..9447cfc38 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/MetricConversionUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/MetricConversionUnitTests.java @@ -65,4 +65,18 @@ public class MetricConversionUnitTests { assertThat(multiplier).isCloseTo(0.00062137, offset(0.000000001)); } + @Test // GH-4004 + void shouldConvertMetersToRadians/* on an earth like sphere with r=6378.137km */() { + assertThat(MetricConversion.metersToRadians(1000)).isCloseTo(0.000156785594d, offset(0.000000001)); + } + + @Test // GH-4004 + void shouldConvertKilometersToRadians/* on an earth like sphere with r=6378.137km */() { + assertThat(MetricConversion.toRadians(new Distance(1, Metrics.KILOMETERS))).isCloseTo(0.000156785594d, offset(0.000000001)); + } + + @Test // GH-4004 + void shouldConvertMilesToRadians/* on an earth like sphere with r=6378.137km */() { + assertThat(MetricConversion.toRadians(new Distance(1, Metrics.MILES))).isCloseTo(0.000252321328d, offset(0.000000001)); + } }