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
This commit is contained in:
committed by
Mark Paluch
parent
de2a0373bb
commit
ee712f67db
@@ -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<Object> $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;
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user