diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java index 2bd0e7569..a8f0babcf 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java @@ -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"); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/NearQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/NearQuery.java index ef72c5030..fba9c6ad0 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/NearQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/NearQuery.java @@ -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; + } + } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java index 7da8e4eca..057766160 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java @@ -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 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 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() { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoJsonTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoJsonTests.java index f8e8b977e..3f105680f 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoJsonTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoJsonTests.java @@ -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 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() { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/NearQueryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/NearQueryUnitTests.java index 0a96b29bf..1472ca3ca 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/NearQueryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/NearQueryUnitTests.java @@ -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)); + } }