From 933e2a7070fd1e9bd353a643f2ca9ea79ec56298 Mon Sep 17 00:00:00 2001 From: Michael Hunger Date: Sat, 22 Oct 2011 03:14:36 +0200 Subject: [PATCH] added geo spatial queries in SpatialRepository --- .../data/neo4j/aspects/support/IndexTest.java | 66 ++++++++++--------- spring-data-neo4j-rest/pom.xml | 2 +- .../repository/AbstractGraphRepository.java | 57 +++++++++++++--- .../neo4j/repository/SpatialRepository.java | 21 +++--- .../data/neo4j/model/Person.java | 6 +- .../neo4j/repository/GraphRepositoryTest.java | 28 ++++---- .../SpatialGraphRepositoryTest.java | 26 ++++++-- .../data/neo4j/repository/TestTeam.java | 2 +- 8 files changed, 137 insertions(+), 71 deletions(-) diff --git a/spring-data-neo4j-aspects/src/test/java/org/springframework/data/neo4j/aspects/support/IndexTest.java b/spring-data-neo4j-aspects/src/test/java/org/springframework/data/neo4j/aspects/support/IndexTest.java index 39f419fe7..90be05afe 100644 --- a/spring-data-neo4j-aspects/src/test/java/org/springframework/data/neo4j/aspects/support/IndexTest.java +++ b/spring-data-neo4j-aspects/src/test/java/org/springframework/data/neo4j/aspects/support/IndexTest.java @@ -16,16 +16,6 @@ package org.springframework.data.neo4j.aspects.support; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.springframework.data.neo4j.aspects.Person.NAME_INDEX; -import static org.springframework.data.neo4j.aspects.Person.persistedPerson; - -import java.util.Arrays; -import java.util.Collection; -import java.util.HashSet; - import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; @@ -47,6 +37,14 @@ import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.transaction.annotation.Transactional; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; + +import static org.junit.Assert.*; +import static org.springframework.data.neo4j.aspects.Person.NAME_INDEX; +import static org.springframework.data.neo4j.aspects.Person.persistedPerson; + @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = {"classpath:org/springframework/data/neo4j/aspects/support/Neo4jGraphPersistenceTest-context.xml"}) public class IndexTest extends EntityTestBase { @@ -163,51 +161,59 @@ public class IndexTest extends EntityTestBase { } @NodeEntity - static class InvalidSpatialIndexed { + static class InvalidSpatialIndexed1 { - @Indexed(indexType=IndexType.POINT, indexName = "InvalidSpatialIndexed") - String latlon; + @Indexed(indexType=IndexType.POINT, indexName = "InvalidSpatialIndexed1") + String wkt; + + public void setWkt(String wkt) { + this.wkt = wkt; + } + } + @NodeEntity + static class InvalidSpatialIndexed2 { @Indexed(indexType=IndexType.POINT) - String latlonNoIndexName; + String wkt; + + public void setWkt(String wkt) { + this.wkt = wkt; + } + } + @NodeEntity + static class InvalidSpatialIndexed3 { + @Indexed(indexType=IndexType.POINT, indexName = "pointLayer") - String latlonValid; - - public void setLatlonNoIndexName(String latlonNoIndexName) { - this.latlonNoIndexName = latlonNoIndexName; - } + String wkt; - public void setLatlon(String latlon) { - this.latlon = latlon; - } - public void setLatlonValid(String latlonValid) { - this.latlonValid = latlonValid; + public void setWkt(String wkt) { + this.wkt = wkt; } } @Test(expected = IllegalStateException.class) @Transactional public void indexAccessWithFullAndNoSpatialIndexNameShouldFail() { - InvalidSpatialIndexed invalidIndexed = persist(new InvalidSpatialIndexed()); + InvalidSpatialIndexed1 invalidIndexed = persist(new InvalidSpatialIndexed1()); String latlon = "POINT (55 15)"; - invalidIndexed.setLatlonNoIndexName(latlon); + invalidIndexed.setWkt(latlon); } @Test(expected = IllegalStateException.class) @Transactional public void indexAccessWithDefaultSpatialIndexNameShouldFail() { - InvalidSpatialIndexed invalidIndexed = persist(new InvalidSpatialIndexed()); + InvalidSpatialIndexed2 invalidIndexed = persist(new InvalidSpatialIndexed2()); String latlon = "POINT (55 15)"; - invalidIndexed.setLatlon( latlon); + invalidIndexed.setWkt( latlon); } @Test @Transactional public void indexAccessWithValidSpatialIndexName() { - InvalidSpatialIndexed invalidIndexed = persist(new InvalidSpatialIndexed()); + InvalidSpatialIndexed3 invalidIndexed = persist(new InvalidSpatialIndexed3()); String latlon = "POINT (55 15)"; - invalidIndexed.setLatlonValid( latlon); + invalidIndexed.setWkt( latlon); } @Test(expected = IllegalStateException.class) diff --git a/spring-data-neo4j-rest/pom.xml b/spring-data-neo4j-rest/pom.xml index 614969240..b8825a48e 100644 --- a/spring-data-neo4j-rest/pom.xml +++ b/spring-data-neo4j-rest/pom.xml @@ -76,7 +76,7 @@ org.neo4j neo4j-rest-graphdb - 1.5-SNAPSHOT + 1.5.M02.U1 org.neo4j diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/AbstractGraphRepository.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/AbstractGraphRepository.java index a4f756656..42c115e68 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/AbstractGraphRepository.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/AbstractGraphRepository.java @@ -16,11 +16,6 @@ package org.springframework.data.neo4j.repository; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; - import org.apache.lucene.search.NumericRangeQuery; import org.neo4j.graphdb.Node; import org.neo4j.graphdb.NotFoundException; @@ -39,6 +34,14 @@ import org.springframework.data.neo4j.support.Neo4jTemplate; import org.springframework.data.neo4j.support.index.NoSuchIndexException; import org.springframework.data.neo4j.support.index.NullReadableIndex; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import static java.lang.String.format; +import static org.neo4j.helpers.collection.MapUtil.map; + /** * Repository like finder for Node and Relationship-Entities. Provides finder methods for direct access, access via {@link org.springframework.data.neo4j.core.TypeRepresentationStrategy} * and indexing. @@ -48,11 +51,35 @@ import org.springframework.data.neo4j.support.index.NullReadableIndex; */ @org.springframework.stereotype.Repository public abstract class AbstractGraphRepository implements GraphRepository, NamedIndexRepository, SpatialRepository { + + /* + index.query( LayerNodeIndex.WITHIN_WKT_GEOMETRY_QUERY, +                "withinWKTGeometry:POLYGON ((15 56, 15 57, 16 57, 16 56, 15 56))" ); + + hits = index.query( LayerNodeIndex.WITHIN_WKT_GEOMETRY_QUERY, +                "POLYGON ((15 56, 15 57, 16 57, 16 56, 15 56))" ); lon,lat +        assertTrue( hits.hasNext() ); + final String poly = String.format("POLYGON (())", lowerLeftLon, upperRightLon, lowerLeftLat, upperRightLat); + */ + @Override - public ClosableIterable findByBoundingBox( String indexName, double lowerLeftLat, - double lowerLeftLon, double upperRightLat, double upperRightLon ) - { - return findAllByQuery( indexName, "bbox", String.format("[%f, %f, %f, %f]", lowerLeftLon, upperRightLon, lowerLeftLat, upperRightLat) ); + public ClosableIterable findWithinWellKnownText( final String indexName, String wellKnownText) { + return geoQuery(indexName, "withinWKTGeometry", wellKnownText); + } + @Override + public ClosableIterable findWithinDistance( final String indexName, final double lat, double lon, double distanceKm) { + return geoQuery(indexName, "withinDistance", map("point", new Double[] { lon, lat}, "distanceInKm", distanceKm)); + } + + @Override + public ClosableIterable findWithinBoundingBox(final String indexName, final double lowerLeftLat, + final double lowerLeftLon, final double upperRightLat, final double upperRightLon) { + return geoQuery(indexName, "bbox", format("[%s, %s, %s, %s]", lowerLeftLon, upperRightLon, lowerLeftLat, upperRightLat)); + } + + private ClosableIterable geoQuery(String indexName, String geoQuery, Object params) { + final IndexHits indexHits = getIndex(indexName,null).query(geoQuery, params); + return new GeoNodeIndexHitsWrapper(indexHits); } public static final ClosableIterable EMPTY_CLOSABLE_ITERABLE = new ClosableIterable() { @@ -353,4 +380,16 @@ public abstract class AbstractGraphRepository im } } + private class GeoNodeIndexHitsWrapper extends IndexHitsWrapper { + public GeoNodeIndexHitsWrapper(IndexHits indexHits) { + super(indexHits); + } + + @Override + protected T underlyingObjectToObject(S result) { + final Number objectNodeId = (Number) result.getProperty("id"); + if (objectNodeId==null) return null; + return super.underlyingObjectToObject(getById(objectNodeId.longValue())); + } + } } diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/SpatialRepository.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/SpatialRepository.java index 66fd9fde9..a1538354e 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/SpatialRepository.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/SpatialRepository.java @@ -18,16 +18,21 @@ package org.springframework.data.neo4j.repository; import org.neo4j.helpers.collection.ClosableIterable; - - /** - * @author mh - * @since 29.03.11 + * Repository for spatial queries. + * WKT is well known text format like POINT( LON LAT ) POLYGON (( LON1 LAT1 LON2 LAT2 LON3 LAT3 LON1 LAT1 )) + * @see Well Known Text Spatial Format + * Right now requires a field: @Indexed(type = POINT, indexName = "...") String wkt; + * inside the entity. */ public interface SpatialRepository { - ClosableIterable findByBoundingBox(String indexName, double lowerLeftLat, - double lowerLeftLon, - double upperRightLat, - double upperRightLon); + ClosableIterable findWithinBoundingBox(String indexName, double lowerLeftLat, + double lowerLeftLon, + double upperRightLat, + double upperRightLon); + ClosableIterable findWithinDistance( final String indexName, final double lat, double lon, double distanceKm); + + ClosableIterable findWithinWellKnownText( final String indexName, String wellKnownText); } + diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/model/Person.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/model/Person.java index 1b55942ce..cdb16e236 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/model/Person.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/model/Person.java @@ -45,7 +45,7 @@ public class Person { @Indexed(indexType = IndexType.POINT, indexName="personLayer") private String wkt; - + @Max(100) @Min(0) @Indexed @@ -168,6 +168,10 @@ public class Person { this.wkt = locationInWkt; } + public void setLocation(double lon, double lat) { + this.wkt = "POINT ( "+lon+" "+lat+" )"; + } + @Override public String toString() { return "["+graphId+"] " + name; diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/GraphRepositoryTest.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/GraphRepositoryTest.java index 94b207608..d7322447d 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/GraphRepositoryTest.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/GraphRepositoryTest.java @@ -16,18 +16,6 @@ package org.springframework.data.neo4j.repository; -import static java.util.Arrays.asList; -import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; -import static org.junit.internal.matchers.IsCollectionContaining.hasItem; -import static org.junit.internal.matchers.IsCollectionContaining.hasItems; -import static org.neo4j.helpers.collection.IteratorUtil.addToCollection; -import static org.neo4j.helpers.collection.IteratorUtil.asCollection; - -import java.util.HashSet; -import java.util.Map; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.junit.Before; @@ -50,6 +38,18 @@ import org.springframework.test.context.transaction.BeforeTransaction; import org.springframework.test.context.transaction.TransactionalTestExecutionListener; import org.springframework.transaction.annotation.Transactional; +import java.util.HashSet; +import java.util.Map; + +import static java.util.Arrays.asList; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.internal.matchers.IsCollectionContaining.hasItem; +import static org.junit.internal.matchers.IsCollectionContaining.hasItems; +import static org.neo4j.helpers.collection.IteratorUtil.addToCollection; +import static org.neo4j.helpers.collection.IteratorUtil.asCollection; + @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration @TestExecutionListeners({CleanContextCacheTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, TransactionalTestExecutionListener.class}) @@ -90,8 +90,8 @@ public class GraphRepositoryTest { @Test public void testFindIterableOfPersonWithQueryAnnotationSpatial() { - Iterable teamMembers = personRepository.findByBoundingBox( "personLayer", 55, 15, 57, 17 ); - assertThat(asCollection(teamMembers), hasItems(testTeam.michael, testTeam.david, testTeam.emil)); + Iterable teamMembers = personRepository.findWithinBoundingBox("personLayer", 55, 15, 57, 17); + assertThat(asCollection(teamMembers), hasItems(testTeam.michael, testTeam.david)); } @Test diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/SpatialGraphRepositoryTest.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/SpatialGraphRepositoryTest.java index 250a846b6..0032e7798 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/SpatialGraphRepositoryTest.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/SpatialGraphRepositoryTest.java @@ -16,10 +16,6 @@ package org.springframework.data.neo4j.repository; -import static org.junit.Assert.assertThat; -import static org.junit.internal.matchers.IsCollectionContaining.hasItems; -import static org.neo4j.helpers.collection.IteratorUtil.asCollection; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.junit.Before; @@ -42,6 +38,10 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionTemplate; +import static org.junit.Assert.assertThat; +import static org.junit.internal.matchers.IsCollectionContaining.hasItems; +import static org.neo4j.helpers.collection.IteratorUtil.asCollection; + @RunWith(SpringJUnit4ClassRunner.class) @TestExecutionListeners({CleanContextCacheTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, TransactionalTestExecutionListener.class}) @ContextConfiguration(locations = {"classpath:org/springframework/data/neo4j/repository/GraphRepositoryTest-context.xml"}) @@ -81,8 +81,20 @@ public class SpatialGraphRepositoryTest { } @Test - public void testFindIterableOfPersonWithQueryAnnotationSpatial() { - Iterable teamMembers = personRepository.findByBoundingBox( "personLayer", 55, 15, 57, 17 ); - assertThat(asCollection(teamMembers), hasItems(testTeam.michael, testTeam.david, testTeam.emil)); + public void testFindPeopleWithinBoundingBox() { + Iterable teamMembers = personRepository.findWithinBoundingBox("personLayer", 55, 15, 57, 17); + assertThat(asCollection(teamMembers), hasItems(testTeam.michael, testTeam.david)); + } + + @Test + public void testFindPeopleWithinPolygon() { + Iterable teamMembers = personRepository.findWithinWellKnownText("personLayer", "POLYGON ((15 55, 15 57, 17 57, 17 55, 15 55))"); + assertThat(asCollection(teamMembers), hasItems(testTeam.michael, testTeam.david)); + } + + @Test + public void testFindPeopleWithinDistance() { + Iterable teamMembers = personRepository.findWithinDistance("personLayer", 16,56,70); + assertThat(asCollection(teamMembers), hasItems(testTeam.michael, testTeam.david)); } } diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/TestTeam.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/TestTeam.java index 946fd941d..e7ee4cb13 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/TestTeam.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/TestTeam.java @@ -46,7 +46,7 @@ public class TestTeam { david = new Person("David", 25); david.setBoss(emil); - david.setLocation( "POINT (16 56)" ); + david.setLocation( 16.5, 56.5 ); friendShip = michael.knows(david); friendShip.setYears(2); sdg = new Group();