DATADOC-68 - Added support for geoNear command.
Introduced GeoResult value object as well as NearQuery. NearQuery allows definition of an origin and distances. Introduced a Metric interface and Metrics enum to carry commonly used metrics like kilometers and miles to ease the handling in NearQueries. Introduced Distance value object to capture distances in Metrics.
This commit is contained in:
@@ -25,7 +25,10 @@ import com.mongodb.DBObject;
|
||||
import com.mongodb.WriteResult;
|
||||
|
||||
import org.springframework.data.mongodb.core.convert.MongoConverter;
|
||||
import org.springframework.data.mongodb.core.geo.GeoResult;
|
||||
import org.springframework.data.mongodb.core.geo.GeoResults;
|
||||
import org.springframework.data.mongodb.core.index.IndexDefinition;
|
||||
import org.springframework.data.mongodb.core.query.NearQuery;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.core.query.Update;
|
||||
|
||||
@@ -255,6 +258,26 @@ public interface MongoOperations {
|
||||
* @return the converted collection
|
||||
*/
|
||||
<T> List<T> findAll(Class<T> entityClass, String collectionName);
|
||||
|
||||
/**
|
||||
* Returns {@link GeoResult} for all entities matching the given {@link NearQuery}. Will consider entity mapping
|
||||
* information to determine the collection the query is ran against.
|
||||
*
|
||||
* @param near must not be {@literal null}.
|
||||
* @param entityClass must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
<T> GeoResults<T> geoNear(NearQuery near, Class<T> entityClass);
|
||||
|
||||
/**
|
||||
* Returns {@link GeoResult} for all entities matching the given {@link NearQuery}.
|
||||
*
|
||||
* @param near must not be {@literal null}.
|
||||
* @param entityClass must not be {@literal null}.
|
||||
* @param collectionName the collection to trigger the query against. If no collection name is given the entity class will be inspected.
|
||||
* @return
|
||||
*/
|
||||
<T> GeoResults<T> geoNear(NearQuery near, Class<T> entityClass, String collectionName);
|
||||
|
||||
/**
|
||||
* Ensure that an index for the provided {@link IndexDefinition} exists for the collection indicated by the entity class.
|
||||
|
||||
@@ -27,6 +27,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.mongodb.BasicDBList;
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.CommandResult;
|
||||
import com.mongodb.DB;
|
||||
@@ -60,6 +61,10 @@ import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
|
||||
import org.springframework.data.mongodb.core.convert.MongoConverter;
|
||||
import org.springframework.data.mongodb.core.convert.MongoReader;
|
||||
import org.springframework.data.mongodb.core.convert.MongoWriter;
|
||||
import org.springframework.data.mongodb.core.geo.Distance;
|
||||
import org.springframework.data.mongodb.core.geo.GeoResult;
|
||||
import org.springframework.data.mongodb.core.geo.GeoResults;
|
||||
import org.springframework.data.mongodb.core.geo.Metric;
|
||||
import org.springframework.data.mongodb.core.index.IndexDefinition;
|
||||
import org.springframework.data.mongodb.core.index.MongoMappingEventPublisher;
|
||||
import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexCreator;
|
||||
@@ -72,10 +77,12 @@ import org.springframework.data.mongodb.core.mapping.event.AfterSaveEvent;
|
||||
import org.springframework.data.mongodb.core.mapping.event.BeforeConvertEvent;
|
||||
import org.springframework.data.mongodb.core.mapping.event.BeforeSaveEvent;
|
||||
import org.springframework.data.mongodb.core.mapping.event.MongoMappingEvent;
|
||||
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.jca.cci.core.ConnectionCallback;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Primary implementation of {@link MongoOperations}.
|
||||
@@ -430,6 +437,30 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
|
||||
return doFindOne(collectionName, new BasicDBObject(idKey, id), null, entityClass);
|
||||
}
|
||||
|
||||
public <T> GeoResults<T> geoNear(NearQuery near, Class<T> entityClass) {
|
||||
return geoNear(near, entityClass, determineCollectionName(entityClass));
|
||||
}
|
||||
|
||||
public <T> GeoResults<T> geoNear(NearQuery near, Class<T> entityClass, String collectionName) {
|
||||
|
||||
String collection = StringUtils.hasText(collectionName) ? collectionName : determineCollectionName(entityClass);
|
||||
BasicDBObject command = new BasicDBObject("geoNear", collection);
|
||||
command.putAll(near.toDBObject());
|
||||
|
||||
CommandResult commandResult = executeCommand(command);
|
||||
BasicDBList results = (BasicDBList) commandResult.get("results");
|
||||
DbObjectCallback<GeoResult<T>> callback = new GeoNearResultDbObjectCallback<T>(new ReadDbObjectCallback<T>(mongoConverter, entityClass), near.getMetric());
|
||||
List<GeoResult<T>> result = new ArrayList<GeoResult<T>>(results.size());
|
||||
|
||||
for (Object element : results) {
|
||||
result.add(callback.doWith((DBObject) element));
|
||||
}
|
||||
|
||||
double averageDistance = (Double) ((DBObject) commandResult.get("stats")).get("avgDistance");
|
||||
return new GeoResults<T>(result, new Distance(averageDistance, near.getMetric()));
|
||||
}
|
||||
|
||||
|
||||
// Find methods that take a Query to express the query and that return a single object that is also removed from the
|
||||
// collection in the database.
|
||||
|
||||
@@ -843,7 +874,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
|
||||
entityClass));
|
||||
}
|
||||
|
||||
protected <T> List<T> doFind(String collectionName, DBObject query, DBObject fields, Class<T> entityClass,
|
||||
protected <S, T> List<T> doFind(String collectionName, DBObject query, DBObject fields, Class<S> entityClass,
|
||||
CursorPreparer preparer, DbObjectCallback<T> objectCallback) {
|
||||
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityClass);
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
@@ -1258,7 +1289,37 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link DbObjectCallback} that assumes a {@link GeoResult} to be created, delegates actual content unmarshalling to
|
||||
* a delegate and creates a {@link GeoResult} from the result.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
static class GeoNearResultDbObjectCallback<T> implements DbObjectCallback<GeoResult<T>> {
|
||||
|
||||
private final DbObjectCallback<T> delegate;
|
||||
private final Metric metric;
|
||||
|
||||
/**
|
||||
* Creates a new {@link GeoNearResultDbObjectCallback} using the given {@link DbObjectCallback} delegate for
|
||||
* {@link GeoResult} content unmarshalling.
|
||||
*
|
||||
* @param delegate
|
||||
*/
|
||||
public GeoNearResultDbObjectCallback(DbObjectCallback<T> delegate, Metric metric) {
|
||||
Assert.notNull(delegate);
|
||||
this.delegate = delegate;
|
||||
this.metric = metric;
|
||||
}
|
||||
|
||||
public GeoResult<T> doWith(DBObject object) {
|
||||
|
||||
double distance = ((Double) object.get("dis")).doubleValue();
|
||||
DBObject content = (DBObject) object.get("obj");
|
||||
|
||||
T doWith = delegate.doWith(content);
|
||||
|
||||
return new GeoResult<T>(doWith, new Distance(distance, metric));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* Copyright 2010-2011 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.geo;
|
||||
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* Value object to represent distances in a given metric.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class Distance {
|
||||
|
||||
private final double value;
|
||||
private final Metric metric;
|
||||
|
||||
/**
|
||||
* Creates a new {@link Distance}.
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
public Distance(double value) {
|
||||
this(value, Metrics.NEUTRAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link Distance} with the given {@link Metric}.
|
||||
*
|
||||
* @param value
|
||||
* @param metric
|
||||
*/
|
||||
public Distance(double value, Metric metric) {
|
||||
this.value = value;
|
||||
this.metric = metric == null ? Metrics.NEUTRAL : metric;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the value
|
||||
*/
|
||||
public double getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the normalized value regarding the underlying {@link Metric}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public double getNormalizedValue() {
|
||||
return value / metric.getMultiplier();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the metric
|
||||
*/
|
||||
public Metric getMetric() {
|
||||
return metric;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given distance to the current one. The resulting {@link Distance} will be in the same metric as the
|
||||
* current one.
|
||||
*
|
||||
* @param other
|
||||
* @return
|
||||
*/
|
||||
public Distance add(Distance other) {
|
||||
double newNormalizedValue = getNormalizedValue() + other.getNormalizedValue();
|
||||
return new Distance(newNormalizedValue * metric.getMultiplier(), metric);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given {@link Distance} to the current one and forces the result to be in a given {@link Metric}.
|
||||
*
|
||||
* @param other
|
||||
* @param metric
|
||||
* @return
|
||||
*/
|
||||
public Distance add(Distance other, Metric metric) {
|
||||
double newLeft = getNormalizedValue() * metric.getMultiplier();
|
||||
double newRight = other.getNormalizedValue() * metric.getMultiplier();
|
||||
return new Distance(newLeft + newRight, metric);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (obj == null || !getClass().equals(obj.getClass())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Distance that = (Distance) obj;
|
||||
|
||||
return this.value == that.value && ObjectUtils.nullSafeEquals(this.metric, that.metric);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#hashCode()
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = 17;
|
||||
result += 31 * Double.doubleToLongBits(value);
|
||||
result += 31 * ObjectUtils.nullSafeHashCode(metric);
|
||||
return result;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append(value);
|
||||
|
||||
if (metric != null) {
|
||||
builder.append(" ").append(metric.toString());
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright 2011 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.geo;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Calue object capturing some arbitrary object plus a distance.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class GeoResult<T> {
|
||||
|
||||
private final T content;
|
||||
private final Distance distance;
|
||||
|
||||
/**
|
||||
* Creates a new {@link GeoResult} for the given content and distance.
|
||||
*
|
||||
* @param content must not be {@literal null}.
|
||||
* @param distance must not be {@literal null}.
|
||||
*/
|
||||
public GeoResult(T content, Distance distance) {
|
||||
Assert.notNull(content);
|
||||
Assert.notNull(distance);
|
||||
this.content = content;
|
||||
this.distance = distance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the actual content object.
|
||||
*
|
||||
* @return the content
|
||||
*/
|
||||
public T getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the distance the actual content object has from the origin.
|
||||
*
|
||||
* @return the distance
|
||||
*/
|
||||
public Distance getDistance() {
|
||||
return distance;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (obj == null || !getClass().equals(obj.getClass())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
GeoResult<?> that = (GeoResult<?>) obj;
|
||||
|
||||
return this.content.equals(that.content) && this.distance.equals(that.distance);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#hashCode()
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
|
||||
int result = 17;
|
||||
result += 31 * distance.hashCode();
|
||||
result += 31 * content.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("GeoResult [content: %s, distance: %s, ]", content.toString(), distance.toString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* Copyright 2011 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.geo;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.annotation.PersistenceConstructor;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Value object to capture {@link GeoResult}s as well as the average distance they have.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class GeoResults<T> implements Iterable<GeoResult<T>> {
|
||||
|
||||
private final List<GeoResult<T>> results;
|
||||
private final Distance averageDistance;
|
||||
|
||||
/**
|
||||
* Creates a new {@link GeoResults} instance manually calculating the average distance from the distance values of the
|
||||
* given {@link GeoResult}s.
|
||||
*
|
||||
* @param results must not be {@literal null}.
|
||||
*/
|
||||
public GeoResults(List<GeoResult<T>> results) {
|
||||
this(results, (Metric) null);
|
||||
}
|
||||
|
||||
public GeoResults(List<GeoResult<T>> results, Metric metric) {
|
||||
this(results, calculateAverageDistance(results, metric));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link GeoResults} instance from the given {@link GeoResult}s and average distance.
|
||||
*
|
||||
* @param results must not be {@literal null}.
|
||||
* @param averageDistance
|
||||
*/
|
||||
@PersistenceConstructor
|
||||
public GeoResults(List<GeoResult<T>> results, Distance averageDistance) {
|
||||
Assert.notNull(results);
|
||||
this.results = results;
|
||||
this.averageDistance = averageDistance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the averageDistance
|
||||
*/
|
||||
public Distance getAverageDistance() {
|
||||
return averageDistance;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Iterable#iterator()
|
||||
*/
|
||||
public Iterator<GeoResult<T>> iterator() {
|
||||
return results.iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the actual
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public List<GeoResult<T>> getContent() {
|
||||
return Collections.unmodifiableList(results);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (obj == null || !getClass().equals(obj.getClass())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
GeoResults<?> that = (GeoResults<?>) obj;
|
||||
|
||||
return this.results.equals(that.results) && this.averageDistance == that.averageDistance;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#hashCode()
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = 17;
|
||||
result += 31 * results.hashCode();
|
||||
result += 31 * averageDistance.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("GeoResults: [averageDistance: %s, results: %s]", averageDistance.toString(),
|
||||
StringUtils.collectionToCommaDelimitedString(results));
|
||||
}
|
||||
|
||||
private static Distance calculateAverageDistance(List<? extends GeoResult<?>> results, Metric metric) {
|
||||
|
||||
if (results.isEmpty()) {
|
||||
return new Distance(0, null);
|
||||
}
|
||||
|
||||
double averageDistance = 0;
|
||||
|
||||
for (GeoResult<?> result : results) {
|
||||
averageDistance += result.getDistance().getValue();
|
||||
}
|
||||
|
||||
return new Distance(averageDistance / results.size(), metric);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.springframework.data.mongodb.core.geo;
|
||||
|
||||
/**
|
||||
* Interface for {@link Metric}s that can be applied to a base scale.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public interface Metric {
|
||||
|
||||
/**
|
||||
* Returns the multiplier to calculate metrics values from a base scale.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
double getMultiplier();
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.springframework.data.mongodb.core.geo;
|
||||
|
||||
import org.springframework.data.mongodb.core.query.NearQuery;
|
||||
|
||||
/**
|
||||
* Commonly used {@link Metrics} for {@link NearQuery}s.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public enum Metrics implements Metric {
|
||||
|
||||
KILOMETERS(6378.137), MILES(3963.191), NEUTRAL(1);
|
||||
|
||||
private final double multiplier;
|
||||
|
||||
private Metrics(double multiplier) {
|
||||
this.multiplier = multiplier;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.geo.Metric#getMultiplier()
|
||||
*/
|
||||
public double getMultiplier() {
|
||||
return multiplier;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,266 @@
|
||||
/*
|
||||
* Copyright 2011 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 org.springframework.data.mongodb.core.geo.Distance;
|
||||
import org.springframework.data.mongodb.core.geo.Metric;
|
||||
import org.springframework.data.mongodb.core.geo.Metrics;
|
||||
import org.springframework.data.mongodb.core.geo.Point;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DBObject;
|
||||
|
||||
/**
|
||||
* Builder class to build near-queries.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class NearQuery {
|
||||
|
||||
private final DBObject criteria;
|
||||
private Query query;
|
||||
private Double maxDistance;
|
||||
private Metric metric;
|
||||
|
||||
/**
|
||||
* Creates a new {@link NearQuery}.
|
||||
*
|
||||
* @param point
|
||||
*/
|
||||
private NearQuery(Point point, Metric metric) {
|
||||
|
||||
Assert.notNull(point);
|
||||
|
||||
this.criteria = new BasicDBObject();
|
||||
this.criteria.put("near", point.asArray());
|
||||
|
||||
this.metric = metric;
|
||||
if (metric != null) {
|
||||
spherical(true);
|
||||
distanceMultiplier(metric);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link NearQuery} starting near the given coordinates.
|
||||
*
|
||||
* @param i
|
||||
* @param j
|
||||
* @return
|
||||
*/
|
||||
public static NearQuery near(double x, double y) {
|
||||
return near(x, y, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @return
|
||||
*/
|
||||
public static NearQuery near(double x, double y, Metric metric) {
|
||||
return near(new Point(x, y), metric);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link NearQuery} starting at the given {@link Point}.
|
||||
*
|
||||
* @param point must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static NearQuery near(Point point) {
|
||||
return near(point, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public static NearQuery near(Point point, Metric metric) {
|
||||
Assert.notNull(point);
|
||||
return new NearQuery(point, metric);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link Metric} underlying the actual query.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Metric getMetric() {
|
||||
return metric;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the number of results to return.
|
||||
*
|
||||
* @param num
|
||||
* @return
|
||||
*/
|
||||
public NearQuery num(int num) {
|
||||
this.criteria.put("num", num);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the max distance results shall have from the configured origin. Will normalize the given value using a
|
||||
* potentially already configured {@link Metric}.
|
||||
*
|
||||
* @param maxDistance
|
||||
* @return
|
||||
*/
|
||||
public NearQuery maxDistance(double maxDistance) {
|
||||
this.maxDistance = getNormalizedDistance(maxDistance, this.metric);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum distance supplied in a given metric. Will normalize the distance but not reconfigure the query's
|
||||
* {@link Metric}.
|
||||
*
|
||||
* @param maxDistance
|
||||
* @param metric must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public NearQuery maxDistance(double maxDistance, Metric metric) {
|
||||
Assert.notNull(metric);
|
||||
this.spherical(true);
|
||||
return maxDistance(getNormalizedDistance(maxDistance, metric));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum distance to the given {@link Distance}.
|
||||
*
|
||||
* @param distance
|
||||
* @return
|
||||
*/
|
||||
public NearQuery maxDistance(Distance distance) {
|
||||
Assert.notNull(distance);
|
||||
return maxDistance(distance.getValue(), distance.getMetric());
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures a distance multiplier the resulting distances get applied.
|
||||
*
|
||||
* @param distanceMultiplier
|
||||
* @return
|
||||
*/
|
||||
public NearQuery distanceMultiplier(double distanceMultiplier) {
|
||||
this.criteria.put("distanceMultiplier", distanceMultiplier);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the distance multiplier to the multiplier of the given {@link Metric}. Does <em>not</em> recalculate the
|
||||
* {@link #maxDistance(double)}.
|
||||
*
|
||||
* @param metric must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public NearQuery distanceMultiplier(Metric metric) {
|
||||
Assert.notNull(metric);
|
||||
return distanceMultiplier(metric.getMultiplier());
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures whether to return spherical values for the actual distance.
|
||||
*
|
||||
* @param spherical
|
||||
* @return
|
||||
*/
|
||||
public NearQuery spherical(boolean spherical) {
|
||||
this.criteria.put("spherical", spherical);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Will cause the results' distances being returned in kilometers. Sets {@link #distanceMultiplier(double)} and
|
||||
* {@link #spherical(boolean)} accordingly.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public NearQuery inKilometers() {
|
||||
return adaptMetric(Metrics.KILOMETERS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Will cause the results' distances being returned in miles. Sets {@link #distanceMultiplier(double)} and
|
||||
* {@link #spherical(boolean)} accordingly.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public NearQuery inMiles() {
|
||||
return adaptMetric(Metrics.MILES);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
|
||||
if (this.metric == null && maxDistance != null) {
|
||||
maxDistance(this.maxDistance, metric);
|
||||
}
|
||||
|
||||
spherical(true);
|
||||
return distanceMultiplier(metric);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an actual query to the {@link NearQuery} to restrict the objects considered for the actual near operation.
|
||||
*
|
||||
* @param query
|
||||
* @return
|
||||
*/
|
||||
public NearQuery query(Query query) {
|
||||
this.query = query;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link DBObject} built by the {@link NearQuery}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public DBObject toDBObject() {
|
||||
|
||||
BasicDBObject dbObject = new BasicDBObject(criteria.toMap());
|
||||
if (query != null) {
|
||||
dbObject.put("query", query.getQueryObject());
|
||||
}
|
||||
if (maxDistance != null) {
|
||||
dbObject.put("maxDistance", maxDistance);
|
||||
}
|
||||
|
||||
return dbObject;
|
||||
}
|
||||
|
||||
private double getNormalizedDistance(double distance, Metric metric) {
|
||||
return metric == null ? distance : distance / metric.getMultiplier();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2010-2011 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.geo;
|
||||
|
||||
import static org.springframework.data.mongodb.core.geo.Metrics.*;
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link Distance}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class DistanceUnitTests {
|
||||
|
||||
@Test
|
||||
public void defaultsMetricToNeutralOne() {
|
||||
assertThat(new Distance(2.5).getMetric(), is((Metric) Metrics.NEUTRAL));
|
||||
assertThat(new Distance(2.5, null).getMetric(), is((Metric) Metrics.NEUTRAL));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addsDistancesWithoutExplicitMetric() {
|
||||
Distance left = new Distance(2.5, KILOMETERS);
|
||||
Distance right = new Distance(2.5, KILOMETERS);
|
||||
assertThat(left.add(right), is(new Distance(5.0, KILOMETERS)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addsDistancesWithExplicitMetric() {
|
||||
Distance left = new Distance(2.5, KILOMETERS);
|
||||
Distance right = new Distance(2.5, KILOMETERS);
|
||||
assertThat(left.add(right, MILES), is(new Distance(3.106856281073925, MILES)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2011 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.geo;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link GeoResult}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class GeoResultUnitTests {
|
||||
|
||||
GeoResult<String> first = new GeoResult<String>("Foo", new Distance(2.5));
|
||||
GeoResult<String> second = new GeoResult<String>("Foo", new Distance(2.5));
|
||||
GeoResult<String> third = new GeoResult<String>("Bar", new Distance(2.5));
|
||||
GeoResult<String> fourth = new GeoResult<String>("Foo", new Distance(5.2));
|
||||
|
||||
@Test
|
||||
public void considersSameInstanceEqual() {
|
||||
|
||||
assertThat(first.equals(first), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void considersSameValuesAsEqual() {
|
||||
assertThat(first.equals(second), is(true));
|
||||
assertThat(second.equals(first), is(true));
|
||||
assertThat(first.equals(third), is(false));
|
||||
assertThat(third.equals(first), is(false));
|
||||
assertThat(first.equals(fourth), is(false));
|
||||
assertThat(fourth.equals(first), is(false));
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsNullContent() {
|
||||
new GeoResult(null, new Distance(2.5));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright 2011 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.geo;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class GeoResultsUnitTests {
|
||||
|
||||
}
|
||||
@@ -33,7 +33,7 @@ public class GeoSpatialAppConfig extends AbstractMongoConfiguration {
|
||||
@Override
|
||||
@Bean
|
||||
public Mongo mongo() throws Exception {
|
||||
return new Mongo("localhost");
|
||||
return new Mongo("127.0.0.1");
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
package org.springframework.data.mongodb.core.geo;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Collection;
|
||||
@@ -34,11 +34,9 @@ import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.data.mongodb.core.CollectionCallback;
|
||||
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||
import org.springframework.data.mongodb.core.Venue;
|
||||
import org.springframework.data.mongodb.core.geo.Box;
|
||||
import org.springframework.data.mongodb.core.geo.Circle;
|
||||
import org.springframework.data.mongodb.core.geo.Point;
|
||||
import org.springframework.data.mongodb.core.index.GeospatialIndex;
|
||||
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.monitor.ServerInfo;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
@@ -111,11 +109,13 @@ public class GeoSpatialTests {
|
||||
template.insert(new Venue("Maplewood, NJ", -74.2713, 40.73137));
|
||||
}
|
||||
|
||||
/*
|
||||
@Test
|
||||
public void geoNear() {
|
||||
GeoNearResult<Venue> geoNearResult = template.geoNear(new Query(Criteria.where("type").is("Office")), Venue.class,
|
||||
GeoNearCriteria.near(2,3).num(10).maxDistance(10).distanceMultiplier(10).spherical(true));
|
||||
}*/
|
||||
NearQuery geoNear = NearQuery.near(-73,40, Metrics.KILOMETERS).num(10).maxDistance(150);
|
||||
GeoResults<Venue> geoNearResult = template.geoNear(geoNear, Venue.class);
|
||||
|
||||
assertThat(geoNearResult.getContent().size(), is(not(0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withinCenter() {
|
||||
|
||||
@@ -19,7 +19,7 @@ public class GeoIndexedAppConfig extends AbstractMongoConfiguration {
|
||||
@Override
|
||||
@Bean
|
||||
public Mongo mongo() throws Exception {
|
||||
return new Mongo("localhost");
|
||||
return new Mongo("127.0.0.1");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package org.springframework.data.mongodb.core.query;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.mongodb.core.geo.Metrics;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class NearQueryUnitTests {
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsNullPoint() {
|
||||
NearQuery.near(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void settingUpNearWithMetricRecalculatesDistance() {
|
||||
|
||||
NearQuery query = NearQuery.near(2.5, 2.5, Metrics.KILOMETERS).maxDistance(150);
|
||||
|
||||
assertThat((Double) query.toDBObject().get("maxDistance"), is(0.02351783914331097));
|
||||
assertThat((Boolean) query.toDBObject().get("spherical"), is(true));
|
||||
assertThat((Double) query.toDBObject().get("distanceMultiplier"), is(Metrics.KILOMETERS.getMultiplier()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void settingMetricRecalculatesMaxDistance() {
|
||||
|
||||
NearQuery query = NearQuery.near(2.5, 2.5, Metrics.KILOMETERS).maxDistance(150);
|
||||
|
||||
assertThat((Double) query.toDBObject().get("maxDistance"), is(0.02351783914331097));
|
||||
assertThat((Double) query.toDBObject().get("distanceMultiplier"), is(Metrics.KILOMETERS.getMultiplier()));
|
||||
|
||||
query.inMiles();
|
||||
assertThat((Double) query.toDBObject().get("distanceMultiplier"), is(Metrics.MILES.getMultiplier()));
|
||||
|
||||
NearQuery.near(2.5, 2.5).maxDistance(150).inKilometers();
|
||||
assertThat((Double) query.toDBObject().get("maxDistance"), is(0.02351783914331097));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user