diff --git a/pom.xml b/pom.xml index 3a9b4f5ad7..e7ff0e2492 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-mongodb-parent - 1.10.0.BUILD-SNAPSHOT + 1.10.0.DATAMONGO-1348-SNAPSHOT pom Spring Data MongoDB diff --git a/spring-data-mongodb-cross-store/pom.xml b/spring-data-mongodb-cross-store/pom.xml index 6554045e11..c09f11619a 100644 --- a/spring-data-mongodb-cross-store/pom.xml +++ b/spring-data-mongodb-cross-store/pom.xml @@ -6,7 +6,7 @@ org.springframework.data spring-data-mongodb-parent - 1.10.0.BUILD-SNAPSHOT + 1.10.0.DATAMONGO-1348-SNAPSHOT ../pom.xml @@ -48,7 +48,7 @@ org.springframework.data spring-data-mongodb - 1.10.0.BUILD-SNAPSHOT + 1.10.0.DATAMONGO-1348-SNAPSHOT diff --git a/spring-data-mongodb-distribution/pom.xml b/spring-data-mongodb-distribution/pom.xml index 2d02722262..9aece0c9f7 100644 --- a/spring-data-mongodb-distribution/pom.xml +++ b/spring-data-mongodb-distribution/pom.xml @@ -13,7 +13,7 @@ org.springframework.data spring-data-mongodb-parent - 1.10.0.BUILD-SNAPSHOT + 1.10.0.DATAMONGO-1348-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb-log4j/pom.xml b/spring-data-mongodb-log4j/pom.xml index ee5e3336db..10653f5125 100644 --- a/spring-data-mongodb-log4j/pom.xml +++ b/spring-data-mongodb-log4j/pom.xml @@ -1,11 +1,11 @@ 4.0.0 - + org.springframework.data spring-data-mongodb-parent - 1.10.0.BUILD-SNAPSHOT + 1.10.0.DATAMONGO-1348-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb/pom.xml b/spring-data-mongodb/pom.xml index edfa519fad..a0229dd6e5 100644 --- a/spring-data-mongodb/pom.xml +++ b/spring-data-mongodb/pom.xml @@ -11,7 +11,7 @@ org.springframework.data spring-data-mongodb-parent - 1.10.0.BUILD-SNAPSHOT + 1.10.0.DATAMONGO-1348-SNAPSHOT ../pom.xml 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 e7419a06ff..d205283bc1 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 @@ -1,5 +1,5 @@ /* - * Copyright 2010-2015 the original author or authors. + * Copyright 2010-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -652,13 +652,13 @@ public GeoResults geoNear(NearQuery near, Class entityClass, String co } String collection = StringUtils.hasText(collectionName) ? collectionName : determineCollectionName(entityClass); - DBObject nearDbObject = near.toDBObject(); + DBObject dbo = near.toDBObject(); + DBObject query = dbo.containsField("query") ? (DBObject) dbo.removeField("query") : new BasicDBObject(); BasicDBObject command = new BasicDBObject("geoNear", collection); - command.putAll(nearDbObject); + command.putAll(queryMapper.getMappedObject(dbo, null)); - if (nearDbObject.containsField("query")) { - DBObject query = (DBObject) nearDbObject.get("query"); + if(!query.keySet().isEmpty()) { command.put("query", queryMapper.getMappedObject(query, getPersistentEntity(entityClass))); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/MetricConversion.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/MetricConversion.java new file mode 100644 index 0000000000..1c5bc86b3b --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/MetricConversion.java @@ -0,0 +1,159 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.mongodb.core.query; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +import org.springframework.data.geo.Distance; +import org.springframework.data.geo.Metric; +import org.springframework.data.geo.Metrics; + +/** + * {@link Metric} and {@link Distance} conversions using the metric system. + * + * @author Mark Paluch + * @since 1.10 + */ +class MetricConversion { + + private static final BigDecimal METERS_MULTIPLIER = new BigDecimal(Metrics.KILOMETERS.getMultiplier()) + .multiply(new BigDecimal(1000)); + + // to achieve a calculation that is accurate to 0.3 meters + private static final int PRECISION = 8; + + /** + * Return meters to {@code metric} multiplier. + * + * @param metric + * @return + */ + protected static double getMetersToMetricMultiplier(Metric metric) { + + ConversionMultiplier conversionMultiplier = ConversionMultiplier.builder().from(METERS_MULTIPLIER).to(metric) + .build(); + return conversionMultiplier.multiplier().doubleValue(); + } + + /** + * Return {@code distance} in meters. + * + * @param distance + * @return + */ + protected static double getDistanceInMeters(Distance distance) { + return new BigDecimal(distance.getValue()).multiply(getMetricToMetersMultiplier(distance.getMetric())) + .doubleValue(); + } + + /** + * Return {@code metric} to meters multiplier. + * + * @param metric + * @return + */ + private static BigDecimal getMetricToMetersMultiplier(Metric metric) { + + ConversionMultiplier conversionMultiplier = ConversionMultiplier.builder().from(metric).to(METERS_MULTIPLIER) + .build(); + return conversionMultiplier.multiplier(); + } + + /** + * Provides a multiplier to convert between various metrics. Metrics must share the same base scale and provide a + * multiplier to convert between the base scale and its own metric. + * + * @author Mark Paluch + */ + private static class ConversionMultiplier { + + private final BigDecimal source; + private final BigDecimal target; + + ConversionMultiplier(Number source, Number target) { + + if (source instanceof BigDecimal) { + this.source = (BigDecimal) source; + } else { + this.source = new BigDecimal(source.doubleValue()); + } + + if (target instanceof BigDecimal) { + this.target = (BigDecimal) target; + } else { + this.target = new BigDecimal(target.doubleValue()); + } + } + + /** + * Returns the multiplier to convert a number from the {@code source} metric to the {@code target} metric. + * + * @return + */ + BigDecimal multiplier() { + return target.divide(source, PRECISION, RoundingMode.HALF_UP); + } + + /** + * Creates a new {@link ConversionMultiplierBuilder}. + * + * @return + */ + static ConversionMultiplierBuilder builder() { + return new ConversionMultiplierBuilder(); + } + + } + + /** + * Builder for {@link ConversionMultiplier}. + * + * @author Mark Paluch + */ + private static class ConversionMultiplierBuilder { + + private Number from; + private Number to; + + ConversionMultiplierBuilder() {} + + ConversionMultiplierBuilder from(Number from) { + this.from = from; + return this; + } + + ConversionMultiplierBuilder from(Metric from) { + this.from = from.getMultiplier(); + return this; + } + + ConversionMultiplierBuilder to(Number to) { + this.to = to; + return this; + } + + ConversionMultiplierBuilder to(Metric to) { + this.to = to.getMultiplier(); + return this; + } + + ConversionMultiplier build() { + return new ConversionMultiplier(this.from, this.to); + } + } +} 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 b847d192e6..5bb77d3989 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 @@ -1,5 +1,5 @@ /* - * Copyright 2011-2015 the original author or authors. + * Copyright 2011-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,8 @@ import org.springframework.data.geo.Metric; import org.springframework.data.geo.Metrics; import org.springframework.data.geo.Point; +import org.springframework.data.mongodb.core.geo.GeoJson; +import org.springframework.data.mongodb.core.geo.GeoJsonPoint; import org.springframework.util.Assert; import com.mongodb.BasicDBObject; @@ -30,10 +32,11 @@ /** * Builder class to build near-queries. - * + * * @author Oliver Gierke * @author Thomas Darimont * @author Christoph Strobl + * @author Mark Paluch */ public final class NearQuery { @@ -48,7 +51,7 @@ public final class NearQuery { /** * Creates a new {@link NearQuery}. - * + * * @param point */ private NearQuery(Point point, Metric metric) { @@ -65,9 +68,9 @@ private NearQuery(Point point, Metric metric) { /** * Creates a new {@link NearQuery} starting near the given coordinates. - * - * @param i - * @param j + * + * @param x + * @param y * @return */ public static NearQuery near(double x, double y) { @@ -78,7 +81,7 @@ public static NearQuery near(double x, double y) { * Creates a new {@link NearQuery} starting at the given coordinates using the given {@link Metric} to adapt given * values to further configuration. E.g. setting a {@link #maxDistance(double)} will be interpreted as a value of the * initially set {@link Metric}. - * + * * @param x * @param y * @param metric @@ -90,7 +93,7 @@ public static NearQuery near(double x, double y, Metric metric) { /** * Creates a new {@link NearQuery} starting at the given {@link Point}. - * + * * @param point must not be {@literal null}. * @return */ @@ -102,7 +105,7 @@ public static NearQuery near(Point point) { * Creates a {@link NearQuery} starting near the given {@link Point} using the given {@link Metric} to adapt given * values to further configuration. E.g. setting a {@link #maxDistance(double)} will be interpreted as a value of the * initially set {@link Metric}. - * + * * @param point must not be {@literal null}. * @param metric * @return @@ -115,7 +118,7 @@ public static NearQuery near(Point point, Metric metric) { /** * Returns the {@link Metric} underlying the actual query. If no metric was set explicitly {@link Metrics#NEUTRAL} * will be returned. - * + * * @return will never be {@literal null}. */ public Metric getMetric() { @@ -124,7 +127,7 @@ public Metric getMetric() { /** * Configures the maximum number of results to return. - * + * * @param num * @return */ @@ -135,7 +138,7 @@ public NearQuery num(int num) { /** * Configures the number of results to skip. - * + * * @param skip * @return */ @@ -146,7 +149,7 @@ public NearQuery skip(int skip) { /** * Configures the {@link Pageable} to use. - * + * * @param pageable must not be {@literal null} * @return */ @@ -161,13 +164,13 @@ public NearQuery with(Pageable pageable) { /** * Sets the max distance results shall have from the configured origin. If a {@link Metric} was set before the given * value will be interpreted as being a value in that metric. E.g. - * + * *
 	 * NearQuery query = near(10.0, 20.0, Metrics.KILOMETERS).maxDistance(150);
 	 * 
- * + * * Will set the maximum distance to 150 kilometers. - * + * * @param maxDistance * @return */ @@ -178,7 +181,7 @@ public NearQuery maxDistance(double maxDistance) { /** * Sets the maximum distance supplied in a given metric. Will normalize the distance but not reconfigure the query's * result {@link Metric} if one was configured before. - * + * * @param maxDistance * @param metric must not be {@literal null}. * @return @@ -192,7 +195,7 @@ public NearQuery maxDistance(double maxDistance, Metric metric) { /** * Sets the maximum distance to the given {@link Distance}. Will set the returned {@link Metric} to be the one of the * given {@link Distance} if no {@link Metric} was set before. - * + * * @param distance must not be {@literal null}. * @return */ @@ -215,13 +218,13 @@ public NearQuery maxDistance(Distance distance) { /** * Sets the minimum distance results shall have from the configured origin. If a {@link Metric} was set before the * given value will be interpreted as being a value in that metric. E.g. - * + * *
 	 * NearQuery query = near(10.0, 20.0, Metrics.KILOMETERS).minDistance(150);
 	 * 
- * + * * Will set the minimum distance to 150 kilometers. - * + * * @param minDistance * @return * @since 1.7 @@ -233,7 +236,7 @@ public NearQuery minDistance(double minDistance) { /** * Sets the minimum distance supplied in a given metric. Will normalize the distance but not reconfigure the query's * result {@link Metric} if one was configured before. - * + * * @param minDistance * @param metric must not be {@literal null}. * @return @@ -248,7 +251,7 @@ public NearQuery minDistance(double minDistance, Metric metric) { /** * Sets the minimum distance to the given {@link Distance}. Will set the returned {@link Metric} to be the one of the * given {@link Distance} if no {@link Metric} was set before. - * + * * @param distance must not be {@literal null}. * @return * @since 1.7 @@ -271,7 +274,7 @@ public NearQuery minDistance(Distance distance) { /** * Returns the maximum {@link Distance}. - * + * * @return */ public Distance getMaxDistance() { @@ -280,7 +283,7 @@ public Distance getMaxDistance() { /** * Returns the maximum {@link Distance}. - * + * * @return * @since 1.7 */ @@ -290,7 +293,7 @@ public Distance getMinDistance() { /** * Configures a {@link CustomMetric} with the given multiplier. - * + * * @param distanceMultiplier * @return */ @@ -302,7 +305,7 @@ public NearQuery distanceMultiplier(double distanceMultiplier) { /** * Configures the distance multiplier to the multiplier of the given {@link Metric}. - * + * * @deprecated use {@link #in(Metric)} instead. * @param metric must not be {@literal null}. * @return @@ -315,7 +318,7 @@ public NearQuery distanceMultiplier(Metric metric) { /** * Configures whether to return spherical values for the actual distance. - * + * * @param spherical * @return */ @@ -326,7 +329,7 @@ public NearQuery spherical(boolean spherical) { /** * Returns whether spharical values will be returned. - * + * * @return */ public boolean isSpherical() { @@ -336,7 +339,7 @@ public boolean isSpherical() { /** * Will cause the results' distances being returned in kilometers. Sets {@link #distanceMultiplier(double)} and * {@link #spherical(boolean)} accordingly. - * + * * @return */ public NearQuery inKilometers() { @@ -346,7 +349,7 @@ public NearQuery inKilometers() { /** * Will cause the results' distances being returned in miles. Sets {@link #distanceMultiplier(double)} and * {@link #spherical(boolean)} accordingly. - * + * * @return */ public NearQuery inMiles() { @@ -356,7 +359,7 @@ public NearQuery inMiles() { /** * Will cause the results' distances being returned in the given metric. Sets {@link #distanceMultiplier(double)} * accordingly as well as {@link #spherical(boolean)} if the given {@link Metric} is not {@link Metrics#NEUTRAL}. - * + * * @param metric the metric the results shall be returned in. Uses {@link Metrics#NEUTRAL} if {@literal null} is * passed. * @return @@ -368,7 +371,7 @@ public NearQuery in(Metric metric) { /** * Configures the given {@link Metric} to be used as base on for this query and recalculate the maximum distance if no * metric was set before. - * + * * @param metric */ private NearQuery adaptMetric(Metric metric) { @@ -383,7 +386,7 @@ private NearQuery adaptMetric(Metric metric) { /** * Adds an actual query to the {@link NearQuery} to restrict the objects considered for the actual near operation. - * + * * @param query must not be {@literal null}. * @return */ @@ -408,7 +411,7 @@ public Integer getSkip() { /** * Returns the {@link DBObject} built by the {@link NearQuery}. - * + * * @return */ public DBObject toDBObject() { @@ -420,25 +423,46 @@ public DBObject toDBObject() { } if (maxDistance != null) { - dbObject.put("maxDistance", maxDistance.getNormalizedValue()); + dbObject.put("maxDistance", getDistanceValueInRadiantsOrMeters(maxDistance)); } if (minDistance != null) { - dbObject.put("minDistance", minDistance.getNormalizedValue()); + dbObject.put("minDistance", getDistanceValueInRadiantsOrMeters(minDistance)); } if (metric != null) { - dbObject.put("distanceMultiplier", metric.getMultiplier()); + dbObject.put("distanceMultiplier", getDistanceMultiplier()); } if (num != null) { dbObject.put("num", num); } - dbObject.put("near", Arrays.asList(point.getX(), point.getY())); + if (usesGeoJson()) { + dbObject.put("near", point); + } else { + dbObject.put("near", Arrays.asList(point.getX(), point.getY())); + } - dbObject.put("spherical", spherical); + dbObject.put("spherical", spherical ? spherical : usesGeoJson()); return dbObject; } + + private double getDistanceMultiplier() { + return usesMetricSystem() ? MetricConversion.getMetersToMetricMultiplier(metric) : metric.getMultiplier(); + } + + private double getDistanceValueInRadiantsOrMeters(Distance distance) { + return usesMetricSystem() ? MetricConversion.getDistanceInMeters(distance) : distance.getNormalizedValue(); + } + + private boolean usesMetricSystem() { + return usesGeoJson(); + } + + private boolean usesGeoJson() { + return point instanceof GeoJsonPoint; + } + } 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 40af85d3e1..9a54895ece 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 @@ -1,5 +1,5 @@ /* - * Copyright 2010-2015 the original author or authors. + * Copyright 2010-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -50,7 +50,9 @@ import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver; import org.springframework.data.mongodb.core.convert.MappingMongoConverter; 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; import org.springframework.data.mongodb.core.mapreduce.MapReduceOptions; import org.springframework.data.mongodb.core.query.BasicQuery; @@ -58,6 +60,7 @@ 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.BasicDBObject; @@ -78,6 +81,7 @@ * * @author Oliver Gierke * @author Christoph Strobl + * @author Mark Paluch */ @RunWith(MockitoJUnitRunner.class) public class MongoTemplateUnitTests extends MongoOperationsUnitTests { @@ -107,7 +111,11 @@ public void setUp() { when(cursor.hint(anyString())).thenReturn(cursor); this.mappingContext = new MongoMappingContext(); + mappingContext.afterPropertiesSet(); + this.converter = new MappingMongoConverter(new DefaultDbRefResolver(factory), mappingContext); + converter.afterPropertiesSet(); + this.template = new MongoTemplate(factory, converter); } @@ -531,6 +539,54 @@ public void mapReduceShouldPickUpLimitFromOptionsEvenWhenQueryDefinesItDifferent assertThat(captor.getValue().getLimit(), is(1000)); } + /** + * @see DATAMONGO-1348 + */ + @Test + public void geoNearShouldMapQueryCorrectly() { + + when(db.command(Mockito.any(DBObject.class))).thenReturn(mock(CommandResult.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(DBObject.class); + verify(this.db, times(1)).command(capture.capture()); + + assertThat(capture.getValue(), IsBsonObject.isBsonObject().containing("query.custom-named-field", "rand al'thor") + .notContaining("query.customName")); + } + + /** + * @see DATAMONGO-1348 + */ + @Test + public void geoNearShouldMapGeoJsonPointCorrectly() { + + when(db.command(Mockito.any(DBObject.class))).thenReturn(mock(CommandResult.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(DBObject.class); + verify(this.db, times(1)).command(capture.capture()); + + 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; + } + class AutogenerateableId { @Id BigInteger id; diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java index 7a23b03782..8f3dff7ce8 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java @@ -54,6 +54,8 @@ import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.Venue; import org.springframework.data.mongodb.core.aggregation.AggregationTests.CarDescriptor.Entry; +import org.springframework.data.mongodb.core.geo.GeoJsonPoint; +import org.springframework.data.mongodb.core.index.GeoSpatialIndexType; import org.springframework.data.mongodb.core.index.GeospatialIndex; import org.springframework.data.mongodb.core.query.NearQuery; import org.springframework.data.mongodb.core.query.Query; @@ -71,7 +73,7 @@ /** * Tests for {@link MongoTemplate#aggregate(String, AggregationPipeline, Class)}. - * + * * @see DATAMONGO-586 * @author Tobias Trelle * @author Thomas Darimont @@ -134,7 +136,7 @@ private void cleanDb() { /** * Imports the sample dataset (zips.json) if necessary (e.g. if it doen't exist yet). The dataset can originally be * found on the mongodb aggregation framework example website: - * + * * @see http://docs.mongodb.org/manual/tutorial/aggregation-examples/. */ private void initSampleDataIfNecessary() { @@ -270,7 +272,7 @@ public void shouldDetectResultMismatch() { public void complexAggregationFrameworkUsageLargestAndSmallestCitiesByState() { /* //complex mongodb aggregation framework example from http://docs.mongodb.org/manual/tutorial/aggregation-examples/#largest-and-smallest-cities-by-state - db.zipInfo.aggregate( + db.zipInfo.aggregate( { $group: { _id: { @@ -377,18 +379,18 @@ public void complexAggregationFrameworkUsageLargestAndSmallestCitiesByState() { @Test public void findStatesWithPopulationOver10MillionAggregationExample() { /* - //complex mongodb aggregation framework example from + //complex mongodb aggregation framework example from http://docs.mongodb.org/manual/tutorial/aggregation-examples/#largest-and-smallest-cities-by-state - - db.zipcodes.aggregate( + + db.zipcodes.aggregate( { $group: { _id:"$state", totalPop:{ $sum:"$pop"} } }, - { - $sort: { _id: 1, "totalPop": 1 } + { + $sort: { _id: 1, "totalPop": 1 } }, { $match: { @@ -602,7 +604,7 @@ public void shouldThrowExceptionIfUnknownFieldIsReferencedInArithmenticExpressio /** * @see DATAMONGO-753 - * @see http + * @see http * ://stackoverflow.com/questions/18653574/spring-data-mongodb-aggregation-framework-invalid-reference-in-group * -operati */ @@ -632,7 +634,7 @@ public void allowsNestedFieldReferencesAsGroupIdsInGroupExpressions() { /** * @see DATAMONGO-753 - * @see http + * @see http * ://stackoverflow.com/questions/18653574/spring-data-mongodb-aggregation-framework-invalid-reference-in-group * -operati */ @@ -1048,6 +1050,57 @@ public void shouldSupportGeoNearQueriesForAggregationWithDistanceField() { assertThat((Double) firstResult.get("distance"), closeTo(117.620092203928, 0.00001)); } + /** + * @see DATAMONGO-1348 + */ + @Test + public void shouldSupportGeoJsonInGeoNearQueriesForAggregationWithDistanceField() { + + mongoTemplate.insert(new Venue("Penn Station", -73.99408, 40.75057)); + mongoTemplate.insert(new Venue("10gen Office", -73.99171, 40.738868)); + mongoTemplate.insert(new Venue("Flatiron Building", -73.988135, 40.741404)); + + mongoTemplate.indexOps(Venue.class) + .ensureIndex(new GeospatialIndex("location").typed(GeoSpatialIndexType.GEO_2DSPHERE)); + + NearQuery geoNear = NearQuery.near(new GeoJsonPoint(-73, 40), Metrics.KILOMETERS).num(10).maxDistance(150); + + Aggregation agg = newAggregation(Aggregation.geoNear(geoNear, "distance")); + AggregationResults result = mongoTemplate.aggregate(agg, Venue.class, DBObject.class); + + assertThat(result.getMappedResults(), hasSize(3)); + + DBObject firstResult = result.getMappedResults().get(0); + assertThat(firstResult.containsField("distance"), is(true)); + assertThat((Double) firstResult.get("distance"), closeTo(117.61940988193759, 0.00001)); + } + + /** + * @see DATAMONGO-1348 + */ + @Test + public void shouldSupportGeoJsonInGeoNearQueriesForAggregationWithDistanceFieldInMiles() { + + mongoTemplate.insert(new Venue("Penn Station", -73.99408, 40.75057)); + mongoTemplate.insert(new Venue("10gen Office", -73.99171, 40.738868)); + mongoTemplate.insert(new Venue("Flatiron Building", -73.988135, 40.741404)); + + mongoTemplate.indexOps(Venue.class) + .ensureIndex(new GeospatialIndex("location").typed(GeoSpatialIndexType.GEO_2DSPHERE)); + + NearQuery geoNear = NearQuery.near(new GeoJsonPoint(-73, 40), Metrics.KILOMETERS).num(10).maxDistance(150) + .inMiles(); + + Aggregation agg = newAggregation(Aggregation.geoNear(geoNear, "distance")); + AggregationResults result = mongoTemplate.aggregate(agg, Venue.class, DBObject.class); + + assertThat(result.getMappedResults(), hasSize(3)); + + DBObject firstResult = result.getMappedResults().get(0); + assertThat(firstResult.containsField("distance"), is(true)); + assertThat((Double) firstResult.get("distance"), closeTo(73.08517, 0.00001)); + } + /** * @see DATAMONGO-1133 */ 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 01d9611675..e8a2400cc6 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 @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -90,7 +90,7 @@ public void tearDown() { } /** - * @see DATAMONGO-1135 + * @see DATAMONGO-1348 */ @Test public void geoNear() { @@ -103,6 +103,20 @@ public void geoNear() { assertThat(result.getAverageDistance().getMetric(), is((Metric) Metrics.KILOMETERS)); } + /** + * @see DATAMONGO-1348 + */ + @Test + 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)); + } + /** * @see DATAMONGO-1135 */ diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/MetricConversionUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/MetricConversionUnitTests.java new file mode 100644 index 0000000000..303d15451c --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/MetricConversionUnitTests.java @@ -0,0 +1,79 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.mongodb.core.query; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import org.junit.Test; +import org.springframework.data.geo.Distance; +import org.springframework.data.geo.Metrics; + +/** + * Unit tests for {@link MetricConversion}. + * + * @author Mark Paluch + */ +public class MetricConversionUnitTests { + + /** + * @see DATAMONGO-1348 + */ + @Test + public void shouldConvertMilesToMeters() { + + Distance distance = new Distance(1, Metrics.MILES); + double distanceInMeters = MetricConversion.getDistanceInMeters(distance); + + assertThat(distanceInMeters, is(closeTo(1609.3438343d, 0.000000001))); + } + + /** + * @see DATAMONGO-1348 + */ + @Test + public void shouldConvertKilometersToMeters() { + + Distance distance = new Distance(1, Metrics.KILOMETERS); + double distanceInMeters = MetricConversion.getDistanceInMeters(distance); + + assertThat(distanceInMeters, is(closeTo(1000, 0.000000001))); + } + + /** + * @see DATAMONGO-1348 + */ + @Test + public void shouldCalculateMetersToKilometersMultiplier() { + + double multiplier = MetricConversion.getMetersToMetricMultiplier(Metrics.KILOMETERS); + + assertThat(multiplier, is(closeTo(0.001, 0.000000001))); + } + + /** + * @see DATAMONGO-1348 + */ + @Test + public void shouldCalculateMetersToMilesMultiplier() { + + double multiplier = MetricConversion.getMetersToMetricMultiplier(Metrics.MILES); + + assertThat(multiplier, is(closeTo(0.00062137, 0.000000001))); + } + +} 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 10232f56f7..bc678cf69c 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 @@ -1,5 +1,5 @@ /* - * Copyright 2011-2014 the original author or authors. + * Copyright 2011-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,10 @@ import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; +import static org.springframework.data.mongodb.test.util.IsBsonObject.*; + +import java.math.BigDecimal; +import java.math.RoundingMode; import org.junit.Test; import org.springframework.data.domain.PageRequest; @@ -26,10 +30,11 @@ import org.springframework.data.geo.Metrics; import org.springframework.data.geo.Point; import org.springframework.data.mongodb.core.DBObjectTestUtils; +import org.springframework.data.mongodb.core.geo.GeoJsonPoint; /** * Unit tests for {@link NearQuery}. - * + * * @author Oliver Gierke * @author Thomas Darimont * @author Christoph Strobl @@ -158,4 +163,119 @@ public void numShouldNotBeAlteredByQueryWithoutPageable() { assertThat(DBObjectTestUtils.getTypedValue(query.toDBObject(), "num", Integer.class), is(num)); } + + /** + * @see DATAMONGO-1348 + */ + @Test + public void shouldNotUseSphericalForLegacyPoint() { + + NearQuery query = NearQuery.near(new Point(27.987901, 86.9165379)); + + assertThat(query.toDBObject(), isBsonObject().containing("spherical", false)); + } + + /** + * @see DATAMONGO-1348 + */ + @Test + public void shouldUseSphericalForLegacyPointIfSet() { + + NearQuery query = NearQuery.near(new Point(27.987901, 86.9165379)); + query.spherical(true); + + assertThat(query.toDBObject(), isBsonObject().containing("spherical", true)); + } + + /** + * @see DATAMONGO-1348 + */ + @Test + public void shouldUseSphericalForGeoJsonData() { + + NearQuery query = NearQuery.near(new GeoJsonPoint(27.987901, 86.9165379)); + + assertThat(query.toDBObject(), isBsonObject().containing("spherical", true)); + } + + /** + * @see DATAMONGO-1348 + */ + @Test + public void shouldUseSphericalForGeoJsonDataIfSphericalIsFalse() { + + NearQuery query = NearQuery.near(new GeoJsonPoint(27.987901, 86.9165379)); + query.spherical(false); + + assertThat(query.toDBObject(), isBsonObject().containing("spherical", true)); + } + + /** + * @see DATAMONGO-1348 + */ + @Test + public void shouldUseMetersForGeoJsonData() { + + NearQuery query = NearQuery.near(new GeoJsonPoint(27.987901, 86.9165379)); + query.maxDistance(1); + + double meterToRadianMultiplier = BigDecimal.valueOf(1 / Metrics.KILOMETERS.getMultiplier() / 1000).// + setScale(8, RoundingMode.HALF_UP).// + doubleValue(); + assertThat(query.toDBObject(), isBsonObject().containing("maxDistance", Metrics.KILOMETERS.getMultiplier() * 1000) + .containing("distanceMultiplier", meterToRadianMultiplier)); + } + + /** + * @see DATAMONGO-1348 + */ + @Test + public void shouldUseMetersForGeoJsonDataWhenDistanceInKilometers() { + + NearQuery query = NearQuery.near(new GeoJsonPoint(27.987901, 86.9165379)); + query.maxDistance(new Distance(1, Metrics.KILOMETERS)); + + assertThat(query.toDBObject(), + isBsonObject().containing("maxDistance", 1000D).containing("distanceMultiplier", 0.001D)); + } + + /** + * @see DATAMONGO-1348 + */ + @Test + public void shouldUseMetersForGeoJsonDataWhenDistanceInMiles() { + + NearQuery query = NearQuery.near(new GeoJsonPoint(27.987901, 86.9165379)); + query.maxDistance(new Distance(1, Metrics.MILES)); + + assertThat(query.toDBObject(), + isBsonObject().containing("maxDistance", 1609.3438343D).containing("distanceMultiplier", 0.00062137D)); + } + + /** + * @see DATAMONGO-1348 + */ + @Test + public void shouldUseKilometersForDistanceWhenMaxDistanceInMiles() { + + NearQuery query = NearQuery.near(new GeoJsonPoint(27.987901, 86.9165379)); + query.maxDistance(new Distance(1, Metrics.MILES)).in(Metrics.KILOMETERS); + + assertThat(query.toDBObject(), + isBsonObject().containing("maxDistance", 1609.3438343D).containing("distanceMultiplier", 0.001D)); + } + + /** + * @see DATAMONGO-1348 + */ + @Test + public void shouldUseMilesForDistanceWhenMaxDistanceInKilometers() { + + NearQuery query = NearQuery.near(new GeoJsonPoint(27.987901, 86.9165379)); + query.maxDistance(new Distance(1, Metrics.KILOMETERS)).in(Metrics.MILES); + + assertThat(query.toDBObject(), + isBsonObject().containing("maxDistance", 1000D).containing("distanceMultiplier", 0.00062137D)); + } + }