added geo spatial queries in SpatialRepository

This commit is contained in:
Michael Hunger
2011-10-22 03:14:36 +02:00
parent 63a7d2a8e0
commit 933e2a7070
8 changed files with 137 additions and 71 deletions

View File

@@ -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)

View File

@@ -76,7 +76,7 @@
<dependency>
<groupId>org.neo4j</groupId>
<artifactId>neo4j-rest-graphdb</artifactId>
<version>1.5-SNAPSHOT</version>
<version>1.5.M02.U1</version>
<exclusions>
<exclusion>
<groupId>org.neo4j</groupId>

View File

@@ -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<S extends PropertyContainer, T> implements GraphRepository<T>, NamedIndexRepository<T>, SpatialRepository<T> {
/*
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<T> 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<T> findWithinWellKnownText( final String indexName, String wellKnownText) {
return geoQuery(indexName, "withinWKTGeometry", wellKnownText);
}
@Override
public ClosableIterable<T> 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<T> 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<T> geoQuery(String indexName, String geoQuery, Object params) {
final IndexHits<S> 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<S extends PropertyContainer, T> im
}
}
private class GeoNodeIndexHitsWrapper extends IndexHitsWrapper {
public GeoNodeIndexHitsWrapper(IndexHits<S> 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()));
}
}
}

View File

@@ -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 <a href="http://en.wikipedia.org/wiki/Well-known_text">Well Known Text Spatial Format</a>
* Right now requires a field: @Indexed(type = POINT, indexName = "...") String wkt;
* inside the entity.
*/
public interface SpatialRepository<T> {
ClosableIterable<T> findByBoundingBox(String indexName, double lowerLeftLat,
double lowerLeftLon,
double upperRightLat,
double upperRightLon);
ClosableIterable<T> findWithinBoundingBox(String indexName, double lowerLeftLat,
double lowerLeftLon,
double upperRightLat,
double upperRightLon);
ClosableIterable<T> findWithinDistance( final String indexName, final double lat, double lon, double distanceKm);
ClosableIterable<T> findWithinWellKnownText( final String indexName, String wellKnownText);
}

View File

@@ -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;

View File

@@ -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<Person> teamMembers = personRepository.findByBoundingBox( "personLayer", 55, 15, 57, 17 );
assertThat(asCollection(teamMembers), hasItems(testTeam.michael, testTeam.david, testTeam.emil));
Iterable<Person> teamMembers = personRepository.findWithinBoundingBox("personLayer", 55, 15, 57, 17);
assertThat(asCollection(teamMembers), hasItems(testTeam.michael, testTeam.david));
}
@Test

View File

@@ -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<Person> teamMembers = personRepository.findByBoundingBox( "personLayer", 55, 15, 57, 17 );
assertThat(asCollection(teamMembers), hasItems(testTeam.michael, testTeam.david, testTeam.emil));
public void testFindPeopleWithinBoundingBox() {
Iterable<Person> teamMembers = personRepository.findWithinBoundingBox("personLayer", 55, 15, 57, 17);
assertThat(asCollection(teamMembers), hasItems(testTeam.michael, testTeam.david));
}
@Test
public void testFindPeopleWithinPolygon() {
Iterable<Person> 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<Person> teamMembers = personRepository.findWithinDistance("personLayer", 16,56,70);
assertThat(asCollection(teamMembers), hasItems(testTeam.michael, testTeam.david));
}
}

View File

@@ -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();