From f8560aac0be222013bf21372123252861146e3bf Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Thu, 17 Dec 2015 08:52:08 +0100 Subject: [PATCH] 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. --- .../data/mongodb/core/MongoTemplate.java | 2 +- .../data/mongodb/core/query/NearQuery.java | 44 +++++++++++++++-- .../mongodb/core/MongoTemplateUnitTests.java | 49 +++++++++++++++++++ .../data/mongodb/core/geo/GeoJsonTests.java | 11 +++++ .../core/query/NearQueryUnitTests.java | 32 ++++++++++++ 5 files changed, 132 insertions(+), 6 deletions(-) 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)); + } }