DATAMONGO-1348 - Convert GeoJson used in NearQuery.

We now convert GeoJsonPoint used in NearQuery into its according format. This also requires to convert any given min/maxDistance as well as the distanceMultiplier into meters (metric system).

Along the way we fixed an issue where the actual Query used along with NearQuery was not properly mapped to the domain types properties.

Original pull request: #339.
This commit is contained in:
Christoph Strobl
2015-12-17 08:52:08 +01:00
committed by Mark Paluch
parent 39859b73a3
commit f8560aac0b
5 changed files with 132 additions and 6 deletions

View File

@@ -972,7 +972,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
Document nearDocument = near.toDocument();
Document command = new Document("geoNear", collection);
command.putAll(nearDocument);
command.putAll(queryMapper.getMappedObject(nearDocument, Optional.empty()));
if (nearDocument.containsKey("query")) {
Document query = (Document) nearDocument.get("query");

View File

@@ -15,6 +15,8 @@
*/
package org.springframework.data.mongodb.core.query;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Arrays;
import org.bson.Document;
@@ -25,6 +27,8 @@ import org.springframework.data.geo.Metric;
import org.springframework.data.geo.Metrics;
import org.springframework.data.geo.Point;
import org.springframework.lang.Nullable;
import org.springframework.data.mongodb.core.geo.GeoJson;
import org.springframework.data.mongodb.core.geo.GeoJsonPoint;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
@@ -47,6 +51,8 @@ public final class NearQuery {
private @Nullable Long num;
private @Nullable Long skip;
private static int PRECISION = 8;
/**
* Creates a new {@link NearQuery}.
*
@@ -416,25 +422,53 @@ public final class NearQuery {
}
if (maxDistance != null) {
document.put("maxDistance", maxDistance.getNormalizedValue());
document.put("maxDistance", getDistanceValueInRadiantsOrMeters(maxDistance));
}
if (minDistance != null) {
document.put("minDistance", minDistance.getNormalizedValue());
document.put("minDistance", getDistanceValueInRadiantsOrMeters(minDistance));
}
if (metric != null) {
document.put("distanceMultiplier", metric.getMultiplier());
document.put(
"distanceMultiplier",
usesMetricSystem() ? getMetricSystemNormalizer(metric).divide(new BigDecimal(1000), PRECISION + 3,
RoundingMode.HALF_UP).doubleValue() : metric.getMultiplier());
}
if (num != null) {
document.put("num", num);
}
document.put("near", Arrays.asList(point.getX(), point.getY()));
if (point instanceof GeoJsonPoint) {
document.put("near", point);
} else {
document.put("near", Arrays.asList(point.getX(), point.getY()));
}
document.put("spherical", spherical);
document.put("spherical", spherical ? spherical : point instanceof GeoJson);
return document;
}
private double getDistanceValueInRadiantsOrMeters(Distance distance) {
return usesMetricSystem() ? getDistanceInMeters(distance) : distance.getNormalizedValue();
}
private double getDistanceInMeters(Distance distance) {
return new BigDecimal(distance.getValue()).multiply(getMetricSystemNormalizer(distance.getMetric()))
.multiply(new BigDecimal(1000)).doubleValue();
}
private BigDecimal getMetricSystemNormalizer(Metric metric) {
return new BigDecimal(Metrics.KILOMETERS.getMultiplier()).divide(new BigDecimal(metric.getMultiplier()), PRECISION,
RoundingMode.HALF_UP);
}
private boolean usesMetricSystem() {
return point instanceof GeoJson;
}
}

View File

@@ -63,6 +63,7 @@ import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.convert.MongoCustomConversions;
import org.springframework.data.mongodb.core.convert.QueryMapper;
import org.springframework.data.mongodb.core.geo.GeoJsonPoint;
import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexCreator;
import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
@@ -76,6 +77,7 @@ import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.NearQuery;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.data.mongodb.test.util.IsBsonObject;
import org.springframework.test.util.ReflectionTestUtils;
import com.mongodb.DB;
@@ -165,7 +167,10 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
when(aggregateIterable.into(any())).thenReturn(Collections.emptyList());
this.mappingContext = new MongoMappingContext();
mappingContext.afterPropertiesSet();
this.converter = spy(new MappingMongoConverter(new DefaultDbRefResolver(factory), mappingContext));
converter.afterPropertiesSet();
this.template = new MongoTemplate(factory, converter);
}
@@ -966,6 +971,50 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
verify(findIterable).projection(eq(new Document()));
}
@Test // DATAMONGO-1348
public void geoNearShouldMapQueryCorrectly() {
when(db.runCommand(any(Document.class), eq(Document.class))).thenReturn(mock(Document.class));
NearQuery query = NearQuery.near(new Point(1, 1));
query.query(Query.query(Criteria.where("customName").is("rand al'thor")));
template.geoNear(query, WithNamedFields.class);
ArgumentCaptor<Document> capture = ArgumentCaptor.forClass(Document.class);
verify(this.db, times(1)).runCommand(capture.capture(), any(Class.class));
assertThat(capture.getValue(), IsBsonObject.isBsonObject().containing("query.custom-named-field", "rand al'thor")
.notContaining("query.customName"));
}
@Test // DATAMONGO-1348
public void geoNearShouldMapGeoJsonPointCorrectly() {
when(db.runCommand(any(Document.class), eq(Document.class))).thenReturn(mock(Document.class));
NearQuery query = NearQuery.near(new GeoJsonPoint(1, 2));
query.query(Query.query(Criteria.where("customName").is("rand al'thor")));
template.geoNear(query, WithNamedFields.class);
ArgumentCaptor<Document> capture = ArgumentCaptor.forClass(Document.class);
verify(this.db, times(1)).runCommand(capture.capture(), any(Class.class));
assertThat(
capture.getValue(),
IsBsonObject.isBsonObject().containing("near.type", "Point").containing("near.coordinates.[0]", 1D)
.containing("near.coordinates.[1]", 2D));
}
static class WithNamedFields {
@Id String id;
String name;
@Field("custom-named-field") String customName;
}
@Test // DATAMONGO-2155
public void saveVersionedEntityShouldCallUpdateCorrectly() {

View File

@@ -104,6 +104,17 @@ public class GeoJsonTests {
assertThat(result.getAverageDistance().getMetric()).isEqualTo(Metrics.KILOMETERS);
}
@Test // DATAMONGO-1135
public void geoNearWithMiles() {
NearQuery geoNear = NearQuery.near(new GeoJsonPoint(-73, 40), Metrics.MILES).num(10).maxDistance(93.2057);
GeoResults<Venue2DSphere> result = template.geoNear(geoNear, Venue2DSphere.class);
assertThat(result.getContent().size(), is(not(0)));
assertThat(result.getAverageDistance().getMetric(), is((Metric) Metrics.MILES));
}
@Test // DATAMONGO-1135
public void withinPolygon() {

View File

@@ -17,6 +17,7 @@ package org.springframework.data.mongodb.core.query;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.springframework.data.mongodb.test.util.IsBsonObject.*;
import org.junit.Test;
import org.springframework.data.domain.PageRequest;
@@ -26,6 +27,7 @@ import org.springframework.data.geo.Metric;
import org.springframework.data.geo.Metrics;
import org.springframework.data.geo.Point;
import org.springframework.data.mongodb.core.DocumentTestUtils;
import org.springframework.data.mongodb.core.geo.GeoJsonPoint;
/**
* Unit tests for {@link NearQuery}.
@@ -141,4 +143,34 @@ public class NearQueryUnitTests {
assertThat(DocumentTestUtils.getTypedValue(query.toDocument(), "num", Long.class), is(num));
}
@Test // DATAMONGO-1348
public void shouldUseMetersForGeoJsonData() {
NearQuery query = NearQuery.near(new GeoJsonPoint(27.987901, 86.9165379));
query.maxDistance(1);
assertThat(query.toDocument(), isBsonObject().containing("maxDistance", Metrics.KILOMETERS.getMultiplier() * 1000)
.containing("distanceMultiplier", Metrics.KILOMETERS.getMultiplier() / 1000));
}
@Test // DATAMONGO-1348
public void shouldUseMetersForGeoJsonDataWhenDistanceInKilometers() {
NearQuery query = NearQuery.near(new GeoJsonPoint(27.987901, 86.9165379));
query.maxDistance(new Distance(1, Metrics.KILOMETERS));
assertThat(query.toDocument(),
isBsonObject().containing("maxDistance", 1000D).containing("distanceMultiplier", 0.001D));
}
@Test // DATAMONGO-1348
public void shouldUseMetersForGeoJsonDataWhenDistanceInMiles() {
NearQuery query = NearQuery.near(new GeoJsonPoint(27.987901, 86.9165379));
query.maxDistance(new Distance(1, Metrics.MILES));
assertThat(query.toDocument(),
isBsonObject().containing("maxDistance", 1609.34383D).containing("distanceMultiplier", 0.00160934383D));
}
}