diff --git a/src/integration/java/org/springframework/data/couchbase/repository/DimensionalQueryTests.java b/src/integration/java/org/springframework/data/couchbase/repository/DimensionalQueryTests.java
index 27edc43d..3ad49cab 100644
--- a/src/integration/java/org/springframework/data/couchbase/repository/DimensionalQueryTests.java
+++ b/src/integration/java/org/springframework/data/couchbase/repository/DimensionalQueryTests.java
@@ -83,7 +83,15 @@ public class DimensionalQueryTests {
assertTrue(expectedKeys.contains(party.getKey()));
}
+ //with this one, testparty-2 is within the bounding box but not in correct distance
parties = repository.findByLocationNear(new Point(0, 0), new Distance(2.5));
+ assertEquals(2, parties.size());
+ for (Party party : parties) {
+ assertTrue(expectedKeys.contains(party.getKey()));
+ }
+
+ //here we adjust the distance so that testparty-2 falls just on the edge
+ parties = repository.findByLocationNear(new Point(0, 0), new Distance(2.8284271247461903));
expectedKeys.add("testparty-2");
assertEquals(3, parties.size());
for (Party party : parties) {
@@ -125,17 +133,19 @@ public class DimensionalQueryTests {
@Test
public void testByLocationInCircle() {
- Circle zone1 = new Circle(new Point(-5,5), new Distance(5.5));
- Circle zone2 = new Circle(new Point(6, -6), new Distance(10));
+ Circle zoneBboxFalse = new Circle(new Point(-5,5), new Distance(5.5));
+ Circle zoneEdge = new Circle(new Point(-5, 0), new Distance(5));
+ Circle zoneInside = new Circle(new Point(6, -6), new Distance(10));
Circle zoneEmpty = new Circle(new Point(6,6), new Distance(3));
- List parties = repository.findByLocationWithin(zone1);
+ List parties = repository.findByLocationWithin(zoneBboxFalse);
+ assertEquals(0, parties.size());
+ parties = repository.findByLocationWithin(zoneEdge);
assertEquals(1, parties.size());
assertEquals("testparty-0", parties.get(0).getKey());
- parties = repository.findByLocationWithin(zone2);
-
+ parties = repository.findByLocationWithin(zoneInside);
assertEquals(12, parties.size()); //all the parties except the special one at 100, 100
parties = repository.findByLocationWithin(zoneEmpty);
@@ -163,14 +173,22 @@ public class DimensionalQueryTests {
@Test
public void testByLocationInPolygon() {
- //bounding box of (-10.5, -0.5),(0.5, 10.5)
- Polygon zone1 = new Polygon(
- new Point(-10.5, -0.5),
- new Point(-10.5, 10.5),
- new Point(0.5, 10.5),
- new Point(0.4, 1));
+ //slightly skewed square triangle that cuts just short of (0,0)
+ //bounding box of (-1,-1),(0.5,1)
+ Polygon zoneFalsePositive = new Polygon(
+ new Point(-1, 1),
+ new Point(0.5, 1),
+ new Point(-1, -1)
+ );
+ //square triangle that comes through (0,0)
+ //bounding box of (-1,-1),(1,1)
+ Polygon zoneEdge = new Polygon(
+ new Point(-1, 1),
+ new Point(1, 1),
+ new Point(-1, -1)
+ );
//bounding box of (-4, -16),(16,4)
- Polygon zone2 = new Polygon(
+ Polygon zoneWithin = new Polygon(
new Point(-4, -10),
new Point(-3, 4),
new Point(14, 2),
@@ -186,13 +204,13 @@ public class DimensionalQueryTests {
new Point(6, 5),
new Point(6, 3));
- List parties = repository.findByLocationWithin(zone1);
+ List parties = repository.findByLocationWithin(zoneFalsePositive);
+ assertEquals("points outside a polygon but within bounding box shouldn't be considered within", 0, parties.size());
- assertEquals(1, parties.size());
- assertEquals("testparty-0", parties.get(0).getKey());
-
- parties = repository.findByLocationWithin(zone2);
+ parties = repository.findByLocationWithin(zoneEdge);
+ assertEquals("point on edge of a polygon shouldn't be considered within", 0, parties.size());
+ parties = repository.findByLocationWithin(zoneWithin);
assertEquals(12, parties.size()); //all the parties except the special one at 100, 100
parties = repository.findByLocationWithin(zoneEmpty);
diff --git a/src/integration/java/org/springframework/data/couchbase/repository/Party.java b/src/integration/java/org/springframework/data/couchbase/repository/Party.java
index fe5cac9c..7456f86c 100644
--- a/src/integration/java/org/springframework/data/couchbase/repository/Party.java
+++ b/src/integration/java/org/springframework/data/couchbase/repository/Party.java
@@ -82,6 +82,7 @@ public class Party {
return "Party{" +
"name='" + name + '\'' +
", eventDate=" + eventDate +
+ ", location=" + location +
'}';
}
}
diff --git a/src/main/asciidoc/repository.adoc b/src/main/asciidoc/repository.adoc
index 418f4132..311e240e 100644
--- a/src/main/asciidoc/repository.adoc
+++ b/src/main/asciidoc/repository.adoc
@@ -350,7 +350,7 @@ Multi-dimensionality concept is interesting, it means you can craft views that a
Couchbase's Spatial View support querying through ranges that represent "lowest" and "highest" values in each dimension, so for 2D it represents a bounding box, with the southwest-most point [x,y] as `startRange` and northeast-most point [x,y] as `endRange`.
-WARNING: Some forms of Geographical queries can only be approximated, for instance querying with a `Polygon` or a `Circle` can only be approximated to the containing bounding `Box` (in DEBUG log level this will be made explicit with each such query).
+TIP: Even though Couchbase Spatial View engine only support Bounding Box querying, the Spring Data Couchbase framework will attempt to remove false positives for you when querying with a `Polygon` or a `Circle` (in TRACE log level each false positive elimination will be logged). Note that a point on the edge of a `Polygon` is *not* considered within (whereas it is when dealing with a `Circle`).
The following query derivation keywords and parameters relative to geographical data in Spring Data are supported for Spatial Views:
@@ -367,67 +367,7 @@ The following query derivation keywords and parameters relative to geographical
IMPORTANT: For "within" types of queries, the expected parameters map to geographical 2D data. Classes from the `org.springframework.data.geo` package are usually expected, but Polygon and Boxes can also be expressed as arrays of `Point`s.
-TIP: Further dimensions are supported through keywords other than Within and Near and require numerical input.
-
-=== Removing False Positives
-`Circle` and `Polygon` will be approximated to their surrounding bounding `Box`, but we provide `PointInShapeEvaluator` (`AwtPointInShapeEvaluator`) to be used on the results if you want to remove false positives.
-
-Here is an example of a repository using a spatial view with 3 dimensions (x, y and opening hours).
-Second example demonstrates querying it within a `Polygon` then removing false positives in a second pass:
-
-.Example of dimensional repository
-====
-[source,java]
-----
-public interface StoreRepository extends PagingAndSortingRepository {
-
- @Dimensional(designDocument = "store", spatialViewName = "byLocationOpening", dimensions = 3)
- public List findByLocationWithinAndOpenBefore(Polygon zone, Integer minOpeningHourMinute);
-
-}
-----
-====
-
-.Query a spatial view and eliminate false positives on the geo aspect
-====
-[source,java]
-----
-// Load the bean, or @Autowire it
-StoreRepository repo = ctx.getBean(StoreRepository.class);
-
-// Prepare an arbitrary polygon (a triangle)
-Polygon zone = new Polygon(
- new Point(2.0, 0.0),
- new Point(4.0, 0.0),
- new Point(3.0, -5.5),
- new Point(2.0, 0.0)); //we explicitly closed the polygon
-
-// Find all stores located inside the triangle.
-// Use 3rd dimension to also query on opening hours, making sure one can go there at 8:00am
-List stores = repo.findByLocationWithinAndOpenBefore(zone, 800);
-
-// The view will return all matching stores that are actually within the enclosing rectangle around the polygon
-// So we remove false positives
-PointInShapeEvaluator evaluator = new AwtPointInShapeEvaluator();
-List truePositives = new ArrayList(stores.size());
-for (Store store : stores) {
- if (evaluator.pointInPolygon(store.getLocation(), zone)) {
- truePositives.add(store);
- } //else this is a false positive
-}
-
-// Alternatively, use "removeFalsePositives" to get the list right away.
-truePositives = evaluator.removeFalsePositives(stores,
- //this method needs a Converter to know where to find the location
- new Converter() {
- @Override
- public Point convert(Store source) {
- return source.getLocation();
- }
- },
- zone);
-----
-====
+Further dimensions are supported through keywords other than Within and Near and require numerical input.
[[couchbase.repository.consistency]]
== Querying with consistency
diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/SpatialViewBasedQuery.java b/src/main/java/org/springframework/data/couchbase/repository/query/SpatialViewBasedQuery.java
index bae0f607..4f8f7909 100644
--- a/src/main/java/org/springframework/data/couchbase/repository/query/SpatialViewBasedQuery.java
+++ b/src/main/java/org/springframework/data/couchbase/repository/query/SpatialViewBasedQuery.java
@@ -16,14 +16,26 @@
package org.springframework.data.couchbase.repository.query;
+import java.beans.PropertyDescriptor;
+import java.util.ArrayList;
+import java.util.List;
+
import com.couchbase.client.java.view.SpatialViewQuery;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.springframework.beans.BeanWrapper;
+import org.springframework.beans.BeanWrapperImpl;
import org.springframework.data.couchbase.core.CouchbaseOperations;
+import org.springframework.data.couchbase.core.CouchbaseTemplate;
+import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
import org.springframework.data.couchbase.core.query.Dimensional;
+import org.springframework.data.couchbase.repository.query.support.GeoUtils;
+import org.springframework.data.mapping.PersistentEntity;
+import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.repository.query.ParametersParameterAccessor;
import org.springframework.data.repository.query.RepositoryQuery;
+import org.springframework.data.repository.query.parser.Part;
import org.springframework.data.repository.query.parser.PartTree;
/**
@@ -64,17 +76,20 @@ public class SpatialViewBasedQuery implements RepositoryQuery {
SpatialViewQueryCreator creator = new SpatialViewQueryCreator(dimensions,
tree, new ParametersParameterAccessor(method.getParameters(), runtimeParams),
baseSpatialQuery, operations.getConverter());
- SpatialViewQuery finalQuery = creator.createQuery();
+ SpatialViewQueryCreator.SpatialViewQueryWrapper finalQuery = creator.createQuery();
//execute the spatial query
return execute(finalQuery);
}
- protected Object execute(SpatialViewQuery query) {
+ protected Object execute(SpatialViewQueryCreator.SpatialViewQueryWrapper query) {
if (LOG.isDebugEnabled()) {
- LOG.debug("Executing spatial view query: " + query.toString());
+ LOG.debug("Executing spatial view query: " + query.getQuery().toString());
}
- return operations.findBySpatialView(query, method.getEntityInformation().getJavaType());
+
+ return query.eliminate(
+ operations.findBySpatialView(query.getQuery(), method.getEntityInformation().getJavaType())
+ );
}
@Override
diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/SpatialViewQueryCreator.java b/src/main/java/org/springframework/data/couchbase/repository/query/SpatialViewQueryCreator.java
index ad0b4d23..7dd67bab 100644
--- a/src/main/java/org/springframework/data/couchbase/repository/query/SpatialViewQueryCreator.java
+++ b/src/main/java/org/springframework/data/couchbase/repository/query/SpatialViewQueryCreator.java
@@ -16,18 +16,30 @@
package org.springframework.data.couchbase.repository.query;
+import java.util.ArrayList;
import java.util.Iterator;
+import java.util.List;
import com.couchbase.client.java.document.json.JsonArray;
import com.couchbase.client.java.view.SpatialViewQuery;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.BeanWrapper;
+import org.springframework.beans.BeanWrapperImpl;
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
import org.springframework.data.couchbase.core.query.Dimensional;
+import org.springframework.data.couchbase.repository.query.support.AwtPointInShapeEvaluator;
import org.springframework.data.couchbase.repository.query.support.GeoUtils;
+import org.springframework.data.couchbase.repository.query.support.PointInShapeEvaluator;
import org.springframework.data.domain.Sort;
+import org.springframework.data.geo.Box;
+import org.springframework.data.geo.Circle;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.Point;
+import org.springframework.data.geo.Polygon;
import org.springframework.data.geo.Shape;
+import org.springframework.data.mapping.PropertyPath;
import org.springframework.data.repository.query.ParameterAccessor;
import org.springframework.data.repository.query.parser.AbstractQueryCreator;
import org.springframework.data.repository.query.parser.Part;
@@ -58,7 +70,9 @@ import org.springframework.data.repository.query.parser.PartTree;
*
* Additionally, {@link PartTree#isLimiting()} will trigger usage of {@link SpatialViewQuery#limit(int) limit}.
*/
-public class SpatialViewQueryCreator extends AbstractQueryCreator {
+public class SpatialViewQueryCreator extends AbstractQueryCreator {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(SpatialViewQueryCreator.class);
private final SpatialViewQuery query;
private final PartTree tree;
@@ -67,6 +81,7 @@ public class SpatialViewQueryCreator extends AbstractQueryCreator evaluators;
public SpatialViewQueryCreator(int dimensions, PartTree tree, ParameterAccessor parameters, SpatialViewQuery query,
CouchbaseConverter converter) {
@@ -77,6 +92,7 @@ public class SpatialViewQueryCreator extends AbstractQueryCreator();
}
@Override
@@ -85,10 +101,10 @@ public class SpatialViewQueryCreator extends AbstractQueryCreator iterator) {
+ private static void applyWithin(JsonArray startRange, JsonArray endRange, Iterator