Skip to content

Commit

Permalink
[Geo API 589] Preparation for Geo API updates - adding test coverage …
Browse files Browse the repository at this point in the history
…and improving documentation of existing Geo features.
  • Loading branch information
trishagee committed Aug 25, 2014
1 parent 30115c3 commit 9ce001d
Show file tree
Hide file tree
Showing 7 changed files with 482 additions and 57 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -190,8 +190,13 @@ public GeoNearBuilder setIncludeLocations(final String includeLocations) {
* the query. If this value is false, the query returns a document multiple times if the document has multiple matching location
* fields. See $uniqueDocs for more information.
*
* @param uniqueDocuments true if only unique documents are required in the return value
* @return this builder
* @see <a href="http://docs.mongodb.org/master/reference/operator/query/uniqueDocs/#op._S_uniqueDocs">uniqueDocs</a>
* @deprecated Deprecated since server version 2.6: Geospatial queries no longer return duplicate results. The $uniqueDocs operator
* has no impact on results.
*/
@Deprecated
public GeoNearBuilder setUniqueDocuments(final Boolean uniqueDocuments) {
this.uniqueDocuments = uniqueDocuments;
return this;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ public enum FilterOperator {

NEAR_SPHERE("$nearSphere"),

/**
* @deprecated New in server version 2.4: $geoWithin replaces $within which is deprecated.
*/
@Deprecated
WITHIN("$within", "within"),

GEO_WITHIN("$geoWithin", "geoWithin") {
Expand Down
134 changes: 134 additions & 0 deletions morphia/src/test/java/org/mongodb/morphia/geo/LegacyCoordsTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package org.mongodb.morphia.geo;

import com.mongodb.DBObject;
import com.mongodb.MongoException;
import org.junit.Test;
import org.mongodb.morphia.TestBase;
import org.mongodb.morphia.query.Query;

import java.util.List;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertThat;
import static org.mongodb.morphia.testutil.IndexMatcher.doesNotHaveIndexNamed;
import static org.mongodb.morphia.testutil.IndexMatcher.hasIndexNamed;

/**
* This test shows how to define an entity that uses the legacy co-ordinate pairs standard, which works with MongoDB server versions 2.2 and
* earlier. If you are using a server version higher than 2.2 (i.e. 2.4 and onwards) you should store location information as <a
* href="http://docs.mongodb.org/manual/reference/glossary/#term-geojson">GeoJSON</a> and consult the documentation for indexes and queries
* that work on this format. Storing the location as GeoJSON gives you access to a wider range of queries.
* <p/>
* This set of tests should run on all server versions.
*/
public class LegacyCoordsTest extends TestBase {
@Test
public void shouldCreateA2dIndexOnAnEntityWithArrayOfCoordinates() {
// given
PlaceWithLegacyCoords pointA = new PlaceWithLegacyCoords(new double[]{3.1, 5.2}, "Point A");
getDs().save(pointA);

// when
getDs().ensureIndexes();

// then
List<DBObject> indexes = getDs().getCollection(PlaceWithLegacyCoords.class).getIndexInfo();
assertThat(indexes, hasIndexNamed("location_2d"));
}

@Test
public void shouldReturnAllLocationsOrderedByDistanceFromQueryLocationWhenPerformingNearQuery() throws Exception {
// given
final PlaceWithLegacyCoords nearbyPlace = new PlaceWithLegacyCoords(new double[]{1.1, 2.3}, "Nearby Place");
getDs().save(nearbyPlace);
final PlaceWithLegacyCoords furtherAwayPlace = new PlaceWithLegacyCoords(new double[]{10.1, 12.3}, "Further Away Place");
getDs().save(furtherAwayPlace);
getDs().ensureIndexes();

// when
final List<PlaceWithLegacyCoords> found = getDs().find(PlaceWithLegacyCoords.class)
.field("location")
.near(1.0, 2.0)
.asList();

// then
assertThat(found, is(notNullValue()));
assertThat(found.size(), is(2));
assertThat(found.get(0), is(nearbyPlace));
assertThat(found.get(1), is(furtherAwayPlace));
}

@Test
public void shouldReturnOnlyThosePlacesWithinTheGivenRadius() throws Exception {
// given
final PlaceWithLegacyCoords nearbyPlace = new PlaceWithLegacyCoords(new double[]{1.1, 2.3}, "Nearby Place");
getDs().save(nearbyPlace);
final PlaceWithLegacyCoords furtherAwayPlace = new PlaceWithLegacyCoords(new double[]{10.1, 12.3}, "Further Away Place");
getDs().save(furtherAwayPlace);
getDs().ensureIndexes();

// when
final List<PlaceWithLegacyCoords> found = getDs().find(PlaceWithLegacyCoords.class)
.field("location")
.near(1.0, 2.0, 1.5)
.asList();
// then
assertThat(found, is(notNullValue()));
assertThat(found.size(), is(1));
assertThat(found.get(0), is(nearbyPlace));
}

@Test
public void shouldNotReturnAnyResultsIfNoLocationsWithinGivenRadius() throws Exception {
// given
final PlaceWithLegacyCoords nearbyPlace = new PlaceWithLegacyCoords(new double[]{1.1, 2.3}, "Nearby Place");
getDs().save(nearbyPlace);
getDs().ensureIndexes();

// when
Query<PlaceWithLegacyCoords> locationQuery = getDs().find(PlaceWithLegacyCoords.class)
.field("location")
.near(1.0, 2.0, 0.1);
// then
assertThat(locationQuery.asList().size(), is(0));
assertThat(locationQuery.get(), is(nullValue()));
}

@Test
public void shouldFindPointWithExactMatch() {
// given
final PlaceWithLegacyCoords nearbyPlace = new PlaceWithLegacyCoords(new double[]{1.1, 2.3}, "Nearby Place");
getDs().save(nearbyPlace);
getDs().ensureIndexes();

// when
List<PlaceWithLegacyCoords> found = getDs().find(PlaceWithLegacyCoords.class)
.field("location")
.equal(new double[]{1.1, 2.3})
.asList();

// then
assertThat(found, is(notNullValue()));
assertThat(found.size(), is(1));
assertThat(found.get(0), is(nearbyPlace));
}

@Test(expected = MongoException.class)
public void shouldThrowAnExceptionIfQueryingWithoutA2dIndex() throws Exception {
// given
final PlaceWithLegacyCoords nearbyPlace = new PlaceWithLegacyCoords(new double[]{1.1, 2.3}, "Nearby Place");
getDs().save(nearbyPlace);
List<DBObject> indexes = getDs().getCollection(PlaceWithLegacyCoords.class).getIndexInfo();
assertThat(indexes, doesNotHaveIndexNamed("location_2d"));

// when
getDs().find(PlaceWithLegacyCoords.class)
.field("location")
.near(0, 0)
.get();

// then expect the Exception
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
package org.mongodb.morphia.geo;

import org.junit.Test;
import org.mongodb.morphia.TestBase;
import org.mongodb.morphia.query.Shape;

import java.util.List;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertThat;

/**
* Although this tests the old legacy coordinate system of storing location, this set of tests shows the functionality that's available with
* these coordinates in later versions of the server that also support GeoJSON. In order to get full geo querying functionality, you should
* use GeoJSON for storing your location not legacy co-ordinates.
* <p/>
* This test requires server version 2.4 or above as it uses $geoWithin.
*/
public class LegacyCoordsWithWithinQueries extends TestBase {
@Test
public void shouldReturnOnlyThePointsWithinTheGivenCircle() throws Exception {
// given
checkMinServerVersion(2.4);

final PlaceWithLegacyCoords expectedPoint = new PlaceWithLegacyCoords(new double[]{1.1, 2.3}, "Near point");
getDs().save(expectedPoint);
final PlaceWithLegacyCoords otherPoint = new PlaceWithLegacyCoords(new double[]{3.1, 5.2}, "Further point");
getDs().save(otherPoint);
getDs().ensureIndexes();

// when
final List<PlaceWithLegacyCoords> found = getDs().find(PlaceWithLegacyCoords.class)
.field("location")
.within(Shape.center(new Shape.Point(1, 2), 1.1))
.asList();

// then
assertThat(found.size(), is(1));
assertThat(found.get(0), is(expectedPoint));
}

@Test
public void shouldReturnPointOnBoundaryOfQueryCircle() throws Exception {
// given
checkMinServerVersion(2.4);

final PlaceWithLegacyCoords expectedPoint = new PlaceWithLegacyCoords(new double[]{1, 1}, "place");
getDs().save(expectedPoint);
getDs().ensureIndexes();

// when - search with circle with an edge that exactly covers the point
final PlaceWithLegacyCoords found = getDs().find(PlaceWithLegacyCoords.class)
.field("location")
.within(Shape.center(new Shape.Point(0, 1), 1))
.get();
// then
assertThat(found, is(expectedPoint));
}

@Test
public void shouldReturnPointOnBoundaryOfQueryCircleWithSphericalGeometry() throws Exception {
// given
checkMinServerVersion(2.4);

final PlaceWithLegacyCoords expectedPoint = new PlaceWithLegacyCoords(new double[]{1, 1}, "place");
getDs().save(expectedPoint);
getDs().ensureIndexes();

// when - search with circle with an edge that exactly covers the point
final PlaceWithLegacyCoords found = getDs().find(PlaceWithLegacyCoords.class)
.field("location")
.within(Shape.centerSphere(new Shape.Point(0, 1), 1))
.get();
// then
assertThat(found, is(expectedPoint));
}

@Test
public void shouldNotReturnAnyPointsIfNothingInsideCircle() throws Exception {
// given
checkMinServerVersion(2.4);

final PlaceWithLegacyCoords point = new PlaceWithLegacyCoords(new double[]{1, 1}, "place");
getDs().save(point);
getDs().ensureIndexes();

// when - search with circle that does not cover the only point
final PlaceWithLegacyCoords found = getDs().find(PlaceWithLegacyCoords.class)
.field("location")
.within(Shape.center(new Shape.Point(2, 2), 0.5))
.get();
// then
assertThat(found, is(nullValue()));
}

@Test
public void shouldReturnPointThatIsFullyInsideTheQueryBox() throws Exception {
// given
checkMinServerVersion(2.4);

final PlaceWithLegacyCoords expectedPoint = new PlaceWithLegacyCoords(new double[]{1, 1}, "place");
getDs().save(expectedPoint);
getDs().ensureIndexes();

// when - search with a box that covers the whole point
final PlaceWithLegacyCoords found = getDs().find(PlaceWithLegacyCoords.class)
.field("location")
.within(Shape.box(new Shape.Point(0, 0), new Shape.Point(2, 2)))
.get();
// then
assertThat(found, is(expectedPoint));
}

@Test
public void shouldNotReturnAnyValuesWhenTheQueryBoxDoesNotContainAnyPoints() throws Exception {
// given
checkMinServerVersion(2.4);

final PlaceWithLegacyCoords point = new PlaceWithLegacyCoords(new double[]{1, 1}, "place");
getDs().save(point);
getDs().ensureIndexes();

// when - search with a box that does not cover the point
final PlaceWithLegacyCoords found = getDs().find(PlaceWithLegacyCoords.class)
.field("location")
.within(Shape.box(new Shape.Point(0, 0), new Shape.Point(0.5, 0.5)))
.get();
// then
assertThat(found, is(nullValue()));
}

@Test
public void shouldReturnAPointThatIsFullyWithinQueryPolygon() throws Exception {
// given
checkMinServerVersion(2.4);

final PlaceWithLegacyCoords expectedPoint = new PlaceWithLegacyCoords(new double[]{1, 1}, "place");
getDs().save(expectedPoint);
getDs().ensureIndexes();

// when - search with polygon that contains expected point
final PlaceWithLegacyCoords found = getDs().find(PlaceWithLegacyCoords.class)
.field("location")
.within(Shape.polygon(new Shape.Point(0, 0),
new Shape.Point(0, 5),
new Shape.Point(2, 3),
new Shape.Point(1, 0)))
.get();
// then
assertThat(found, is(expectedPoint));
}

@Test
public void shouldNotReturnAnyValuesWhenTheQueryPolygonDoesNotContainAnyPoints() throws Exception {
// given
checkMinServerVersion(2.4);

final PlaceWithLegacyCoords point = new PlaceWithLegacyCoords(new double[]{7.3, 9.2}, "place");
getDs().save(point);
getDs().ensureIndexes();

// when - search with polygon that's nowhere near the given point
final PlaceWithLegacyCoords found = getDs().find(PlaceWithLegacyCoords.class)
.field("location")
.within(Shape.polygon(new Shape.Point(0, 0),
new Shape.Point(0, 5),
new Shape.Point(2, 3),
new Shape.Point(1, 0)))
.get();
// then
assertThat(found, is(nullValue()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package org.mongodb.morphia.geo;

import org.mongodb.morphia.annotations.Indexed;
import org.mongodb.morphia.utils.IndexDirection;

import java.util.Arrays;

@SuppressWarnings("unused")
class PlaceWithLegacyCoords {
@Indexed(IndexDirection.GEO2D)
private double[] location = new double[2];
private String name;

PlaceWithLegacyCoords(final double[] location, final String name) {
this.location = location;
this.name = name;
}

PlaceWithLegacyCoords() {
}

// equals(), hashCode() and toString() all needed for testing
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}

PlaceWithLegacyCoords place = (PlaceWithLegacyCoords) o;

if (!Arrays.equals(location, place.location)) {
return false;
}
if (!name.equals(place.name)) {
return false;
}

return true;
}

@Override
public int hashCode() {
int result = Arrays.hashCode(location);
result = 31 * result + name.hashCode();
return result;
}

@Override
public String toString() {
return "Place{"
+ "location=" + Arrays.toString(location)
+ ", name='" + name + '\''
+ '}';
}
}
Loading

0 comments on commit 9ce001d

Please sign in to comment.