Browse Source

Fix rewrite near & nearSphere count queries using geoJson to geoWithin.

$near and $nearSphere queries are not supported via countDocuments and the used aggregation match stage and need to be rewritten to $geoWithin. The existing logic did not cover usage of geoJson types, which is fixed now. In case of nearSphere it is also required to convert the $maxDistance argument (given in meters for geoJson) to radians which is used by $geoWithin $centerSphere.

Closes #4004
Original pull request: #4006.
Related to #2925
3.4.x
Christoph Strobl 4 years ago committed by Mark Paluch
parent
commit
ee712f67db
No known key found for this signature in database
GPG Key ID: 4406B84C1661DCD1
  1. 37
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/CountQuery.java
  2. 24
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/MetricConversion.java
  3. 34
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/CountQueryUnitTests.java
  4. 14
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/MetricConversionUnitTests.java

37
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/CountQuery.java

@ -23,8 +23,8 @@ import java.util.List; @@ -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 { @@ -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<Object> $centerMax = Arrays.asList(toCenterCoordinates($near), maxDistance);
Document $geoWithinMax = new Document("$geoWithin",
new Document(spheric ? "$centerSphere" : "$center", $centerMax));
@ -197,6 +198,24 @@ class CountQuery { @@ -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 { @@ -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;

24
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/MetricConversion.java

@ -17,6 +17,7 @@ @@ -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; @@ -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 { @@ -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.
*

34
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; @@ -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 { @@ -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);

14
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/MetricConversionUnitTests.java

@ -65,4 +65,18 @@ public class MetricConversionUnitTests { @@ -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));
}
}

Loading…
Cancel
Save