/* * Hibernate Search, full-text search for your domain model * * License: GNU Lesser General Public License (LGPL), version 2.1 or later * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>. */ package org.hibernate.search.test.jpa; import java.util.List; import org.apache.lucene.search.Sort; import org.hibernate.search.annotations.Spatial; import org.hibernate.search.jpa.FullTextQuery; import org.hibernate.search.query.dsl.QueryBuilder; import org.hibernate.search.query.dsl.Unit; import org.hibernate.search.spatial.DistanceSortField; import org.hibernate.search.test.spatial.DoubleIndexedPOI; import org.hibernate.search.test.spatial.POI; import org.hibernate.search.testsupport.TestForIssue; import org.junit.After; import org.junit.Assert; import org.junit.Test; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; import static org.hibernate.search.test.util.ResourceCleanupFunctions.withinEntityManager; import static org.hibernate.search.test.util.ResourceCleanupFunctions.withinTransaction; /** * Hibernate Search spatial : unit tests on quering POIs * * @author Nicolas Helleringer * @author Davide Di Somma <davide.disomma@gmail.com> */ public class SpatialQueryingJPATest extends JPATestCase { // Use a large, but reasonable distance: Elasticsearch 5 can't handle Double.MAX_VALUE for instance private static final double LARGE_DISTANCE_KM = 40_000; @After public void cleanup() { withinEntityManager( factory, em -> { withinTransaction( em, () -> { em.createQuery( "from " + POI.class.getName() ).getResultList().forEach( entity -> em.remove( entity ) ); em.createQuery( "from " + DoubleIndexedPOI.class.getName() ).getResultList().forEach( entity -> em.remove( entity ) ); } ); } ); } @Test public void testDistanceProjectionWithoutDistanceSort() throws Exception { POI poi = new POI( 1, "Distance to 24,32 : 0", 24.0d, 32.0d, "" ); POI poi2 = new POI( 2, "Distance to 24,32 : 10.16", 24.0d, 31.9d, "" ); POI poi3 = new POI( 3, "Distance to 24,32 : 11.12", 23.9d, 32.0d, "" ); POI poi4 = new POI( 4, "Distance to 24,32 : 15.06", 23.9d, 32.1d, "" ); POI poi5 = new POI( 5, "Distance to 24,32 : 22.24", 24.2d, 32.0d, "" ); POI poi6 = new POI( 6, "Distance to 24,32 : 24.45", 24.2d, 31.9d, "" ); withinEntityManager( factory, em -> { withinTransaction( em, () -> { em.persist( poi ); em.persist( poi2 ); em.persist( poi3 ); } ); em.clear(); //Two different groups so that the query run on multiple segments: withinTransaction( em, () -> { em.persist( poi4 ); em.persist( poi5 ); em.persist( poi6 ); } ); em.clear(); withinTransaction( em, () -> { double centerLatitude = 24.0d; double centerLongitude = 32.0d; final QueryBuilder builder = em.getSearchFactory().buildQueryBuilder().forEntity( POI.class ).get(); org.apache.lucene.search.Query luceneQuery = builder.spatial().onField( "location" ) .within( 100, Unit.KM ).ofLatitude( centerLatitude ).andLongitude( centerLongitude ).createQuery(); FullTextQuery hibQuery = em.createFullTextQuery( luceneQuery, POI.class ); hibQuery.setProjection( FullTextQuery.THIS, FullTextQuery.SPATIAL_DISTANCE ); hibQuery.setSpatialParameters( centerLatitude, centerLongitude, "location" ); hibQuery.setSort( builder.sort().byField( "idSort" ).createSort() ); List results = hibQuery.getResultList(); Assert.assertEquals( 6, results.size() ); Object[] firstResult = (Object[]) results.get( 0 ); Object[] secondResult = (Object[]) results.get( 1 ); Object[] thirdResult = (Object[]) results.get( 2 ); Object[] fourthResult = (Object[]) results.get( 3 ); Object[] fifthResult = (Object[]) results.get( 4 ); Object[] sixthResult = (Object[]) results.get( 5 ); Assert.assertEquals( 0.0, (Double) firstResult[1], 0.01 ); Assert.assertEquals( 10.1582, (Double) secondResult[1], 0.01 ); Assert.assertEquals( 11.1195, (Double) thirdResult[1], 0.01 ); Assert.assertEquals( 15.0636, (Double) fourthResult[1], 0.01 ); Assert.assertEquals( 22.239, (Double) fifthResult[1], 0.02 ); Assert.assertEquals( 24.446, (Double) sixthResult[1], 0.02 ); } ); } ); } @Test @TestForIssue(jiraKey = "HSEARCH-2324") public void testDistanceProjectionWithoutDistanceSortMissingCoordinates() throws Exception { POI poi1 = new POI( 1, "Distance to 24,32 : unknown (incomplete coordinates)", null, 32d, "" ); POI poi2 = new POI( 2, "Distance to 24,32 : unknown (incomplete coordinates)", 24d, null, "" ); POI poi3 = new POI( 3, "Distance to 23,32 : unknown (incomplete coordinates)", null, null, "" ); withinEntityManager( factory, em -> { withinTransaction( em, () -> { em.persist( poi1 ); em.persist( poi2 ); em.persist( poi3 ); } ); em.clear(); withinTransaction( em, () -> { double centerLatitude = 24.0d; double centerLongitude = 32.0d; final QueryBuilder builder = em.getSearchFactory().buildQueryBuilder().forEntity( POI.class ).get(); org.apache.lucene.search.Query luceneQuery = builder.spatial().onField( "location" ) .within( LARGE_DISTANCE_KM, Unit.KM ).ofLatitude( centerLatitude ).andLongitude( centerLongitude ).createQuery(); FullTextQuery hibQuery = em.createFullTextQuery( luceneQuery, POI.class ); hibQuery.setProjection( FullTextQuery.THIS, FullTextQuery.SPATIAL_DISTANCE ); hibQuery.setSpatialParameters( centerLatitude, centerLongitude, "location" ); hibQuery.setSort( builder.sort().byField( "idSort" ).createSort() ); List results = hibQuery.getResultList(); Assert.assertEquals( "Missing coordinates should never appear in spatial query results", 0, results.size() ); hibQuery = em.createFullTextQuery( builder.all().createQuery(), POI.class ); hibQuery.setProjection( FullTextQuery.THIS, FullTextQuery.SPATIAL_DISTANCE ); hibQuery.setSpatialParameters( centerLatitude, centerLongitude, "location" ); hibQuery.setSort( builder.sort().byField( "idSort" ).createSort() ); results = hibQuery.getResultList(); Assert.assertEquals( 3, results.size() ); Object[] firstResult = (Object[]) results.get( 0 ); Object[] secondResult = (Object[]) results.get( 1 ); Object[] thirdResult = (Object[]) results.get( 2 ); Assert.assertNull( firstResult[1] ); Assert.assertNull( secondResult[1] ); Assert.assertNull( thirdResult[1] ); } ); } ); } @Test public void testDistanceSort() throws Exception { POI poi = new POI( 1, "Distance to 24,32 : 0", 24.0d, 32.0d, "" ); POI poi2 = new POI( 2, "Distance to 24,32 : 10.16", 24.0d, 31.9d, "" ); POI poi3 = new POI( 3, "Distance to 24,32 : 11.12", 23.9d, 32.0d, "" ); POI poi4 = new POI( 4, "Distance to 24,32 : 15.06", 23.9d, 32.1d, "" ); POI poi5 = new POI( 5, "Distance to 24,32 : 22.24", 24.2d, 32.0d, "" ); POI poi6 = new POI( 6, "Distance to 24,32 : 24.45", 24.2d, 31.9d, "" ); withinEntityManager( factory, em -> { withinTransaction( em, () -> { em.persist( poi ); em.persist( poi2 ); em.persist( poi3 ); } ); em.clear(); //Two different groups so that the query run on multiple segments: withinTransaction( em, () -> { em.persist( poi4 ); em.persist( poi5 ); em.persist( poi6 ); } ); em.clear(); withinTransaction( em, () -> { double centerLatitude = 24.0d; double centerLongitude = 32.0d; final QueryBuilder builder = em.getSearchFactory().buildQueryBuilder().forEntity( POI.class ).get(); org.apache.lucene.search.Query luceneQuery = builder.spatial().onField( "location" ) .within( 100, Unit.KM ).ofLatitude( centerLatitude ).andLongitude( centerLongitude ).createQuery(); FullTextQuery hibQuery = em.createFullTextQuery( luceneQuery, POI.class ); Sort distanceSort = new Sort( new DistanceSortField( centerLatitude, centerLongitude, "location" ) ); hibQuery.setSort( distanceSort ); hibQuery.setProjection( FullTextQuery.THIS, FullTextQuery.SPATIAL_DISTANCE ); hibQuery.setSpatialParameters( centerLatitude, centerLongitude, "location" ); List results = hibQuery.getResultList(); Object[] firstResult = (Object[]) results.get( 0 ); Object[] secondResult = (Object[]) results.get( 1 ); Object[] thirdResult = (Object[]) results.get( 2 ); Object[] fourthResult = (Object[]) results.get( 3 ); Object[] fifthResult = (Object[]) results.get( 4 ); Object[] sixthResult = (Object[]) results.get( 5 ); Assert.assertEquals( 0.0, (Double) firstResult[1], 0.01 ); Assert.assertEquals( 10.1582, (Double) secondResult[1], 0.01 ); Assert.assertEquals( 11.1195, (Double) thirdResult[1], 0.01 ); Assert.assertEquals( 15.0636, (Double) fourthResult[1], 0.01 ); Assert.assertEquals( 22.239, (Double) fifthResult[1], 0.02 ); Assert.assertEquals( 24.446, (Double) sixthResult[1], 0.02 ); distanceSort = new Sort( new DistanceSortField( centerLatitude, centerLongitude, "location", true ) ); hibQuery.setSort( distanceSort ); results = hibQuery.getResultList(); firstResult = (Object[]) results.get( 0 ); secondResult = (Object[]) results.get( 1 ); thirdResult = (Object[]) results.get( 2 ); fourthResult = (Object[]) results.get( 3 ); fifthResult = (Object[]) results.get( 4 ); sixthResult = (Object[]) results.get( 5 ); Assert.assertEquals( 24.446, (Double) firstResult[1], 0.02 ); Assert.assertEquals( 22.239, (Double) secondResult[1], 0.02 ); Assert.assertEquals( 15.0636, (Double) thirdResult[1], 0.01 ); Assert.assertEquals( 11.1195, (Double) fourthResult[1], 0.01 ); Assert.assertEquals( 10.1582, (Double) fifthResult[1], 0.01 ); Assert.assertEquals( 0.0, (Double) sixthResult[1], 0.01 ); } ); } ); } @Test public void testDistanceSort2() throws Exception { withinEntityManager( factory, em -> { withinTransaction( em, () -> { int cnt = 0; for ( double[] c : new double[][] { {41.04389845, -74.06328534}, {40.64383333, -73.75050000}, {40.75666667, -73.98650000}, {40.69416667, -73.78166667}, {40.75802992, -73.98532391}, {40.75802992, -73.98532391}, {50.77687257, 6.08431213}, {50.78361600, 6.07003500}, {50.76066667, 6.08866667}, {50.77683333, 6.08466667}, {50.77650000, 6.08416667}, } ) { em.persist( new POI( cnt, "Test_" + cnt, c[0], c[1], "" ) ); ++cnt; } } ); withinTransaction( em, () -> { double centerLatitude = 50.7753455; double centerLongitude = 6.083886799999959; final QueryBuilder builder = em.getSearchFactory().buildQueryBuilder().forEntity( POI.class ).get(); org.apache.lucene.search.Query luceneQuery = builder.spatial().onField( "location" ) .within( 1.8097233616663808, Unit.KM ) .ofLatitude( centerLatitude ) .andLongitude( centerLongitude ) .createQuery(); FullTextQuery hibQuery = em.createFullTextQuery( luceneQuery, POI.class ); Sort distanceSort = new Sort( new DistanceSortField( centerLatitude, centerLongitude, "location" ) ); hibQuery.setSort( distanceSort ); hibQuery.setMaxResults( 1000 ); hibQuery.setProjection( FullTextQuery.THIS, FullTextQuery.SPATIAL_DISTANCE ); hibQuery.setSpatialParameters( centerLatitude, centerLongitude, "location" ); @SuppressWarnings( "unchecked" ) List<Object[]> results = hibQuery.getResultList(); for ( Object[] result : results ) { POI poi = (POI)result[0]; String message = poi.getName() + " (" + poi.getLatitude() + ", " + poi.getLongitude() + ") is not at " + centerLatitude + ", " + centerLongitude; Assert.assertThat( message, ( (Double) result[1] ).doubleValue(), is( not( 0.0 ) ) ); } } ); } ); } @Test @TestForIssue(jiraKey = "HSEARCH-2324") public void testDistanceSortMissingCoordinates() throws Exception { POI poi = new POI( 0, "Distance to 24,32 : 10.16km", 24.0d, 31.9d, "" ); POI poi2 = new POI( 1, "Distance to 24,32 : unknown, 4361.00km if interpreted as 0,0", null, null, "" ); withinEntityManager( factory, em -> { withinTransaction( em, () -> { em.persist( poi ); em.persist( poi2 ); } ); withinTransaction( em, () -> { double centerLatitude = 24.0d; double centerLongitude = 32.0d; final QueryBuilder builder = em.getSearchFactory().buildQueryBuilder().forEntity( POI.class ).get(); org.apache.lucene.search.Query luceneQuery = builder.all().createQuery(); FullTextQuery hibQuery = em.createFullTextQuery( luceneQuery, POI.class ); Sort distanceSort = new Sort( new DistanceSortField( centerLatitude, centerLongitude, "location" ) ); hibQuery.setSort( distanceSort ); hibQuery.setProjection( FullTextQuery.THIS, FullTextQuery.SPATIAL_DISTANCE ); hibQuery.setSpatialParameters( centerLatitude, centerLongitude, "location" ); List results = hibQuery.getResultList(); Object[] firstResult = (Object[]) results.get( 0 ); Object[] secondResult = (Object[]) results.get( 1 ); Assert.assertEquals( 10.1582, (Double) firstResult[1], 0.01 ); Assert.assertNull( secondResult[1] ); distanceSort = new Sort( new DistanceSortField( centerLatitude, centerLongitude, "location", true ) ); hibQuery.setSort( distanceSort ); results = hibQuery.getResultList(); firstResult = (Object[]) results.get( 0 ); secondResult = (Object[]) results.get( 1 ); Assert.assertNull( firstResult[1] ); Assert.assertEquals( 10.1582, (Double) secondResult[1], 0.01 ); } ); } ); } @Test @TestForIssue(jiraKey = "HSEARCH-2322") public void testDistanceSortMissingCoordinatesWholeSegment() throws Exception { POI poi = new POI( 0, "Distance to 24,32 : 10.16km", 24.0d, 31.9d, "" ); POI poi2 = new POI( 1, "Distance to 24,32 : unknown, 4361.00km if interpreted as 0,0", null, null, "" ); withinEntityManager( factory, em -> { withinTransaction( em, () -> em.persist( poi ) ); /* * Create the POI with a missing value in a separate transaction, so that * the document will be alone in one segment (or so it seems...?) */ withinTransaction( em, () -> em.persist( poi2 ) ); withinTransaction( em, () -> { double centerLatitude = 24.0d; double centerLongitude = 32.0d; final QueryBuilder builder = em.getSearchFactory().buildQueryBuilder().forEntity( POI.class ).get(); org.apache.lucene.search.Query luceneQuery = builder.all().createQuery(); FullTextQuery hibQuery = em.createFullTextQuery( luceneQuery, POI.class ); Sort distanceSort = new Sort( new DistanceSortField( centerLatitude, centerLongitude, "location" ) ); hibQuery.setSort( distanceSort ); hibQuery.setProjection( FullTextQuery.THIS, FullTextQuery.SPATIAL_DISTANCE ); hibQuery.setSpatialParameters( centerLatitude, centerLongitude, "location" ); List results = hibQuery.getResultList(); Object[] firstResult = (Object[]) results.get( 0 ); Object[] secondResult = (Object[]) results.get( 1 ); Assert.assertEquals( 10.1582, (Double) firstResult[1], 0.01 ); Assert.assertNull( secondResult[1] ); distanceSort = new Sort( new DistanceSortField( centerLatitude, centerLongitude, "location", true ) ); hibQuery.setSort( distanceSort ); results = hibQuery.getResultList(); firstResult = (Object[]) results.get( 0 ); secondResult = (Object[]) results.get( 1 ); Assert.assertNull( firstResult[1] ); Assert.assertEquals( 10.1582, (Double) secondResult[1], 0.01 ); } ); } ); } @Test public void testDistanceSortWithMaxResult() throws Exception { POI poi = new POI( 1, "Distance to 24,32 : 0", 24.0d, 32.0d, "" ); POI poi2 = new POI( 2, "Distance to 24,32 : 10.16", 24.0d, 31.9d, "" ); POI poi3 = new POI( 3, "Distance to 24,32 : 11.12", 23.9d, 32.0d, "" ); POI poi4 = new POI( 4, "Distance to 24,32 : 15.06", 23.9d, 32.1d, "" ); POI poi5 = new POI( 5, "Distance to 24,32 : 22.24", 24.2d, 32.0d, "" ); POI poi6 = new POI( 6, "Distance to 24,32 : 24.45", 24.2d, 31.9d, "" ); withinEntityManager( factory, em -> { withinTransaction( em, () -> { em.persist( poi ); em.persist( poi2 ); em.persist( poi3 ); } ); em.clear(); //Two different groups so that the query run on multiple segments: withinTransaction( em, () -> { em.persist( poi4 ); em.persist( poi5 ); em.persist( poi6 ); } ); em.clear(); withinTransaction( em, () -> { double centerLatitude = 24.0d; double centerLongitude = 32.0d; final QueryBuilder builder = em.getSearchFactory().buildQueryBuilder().forEntity( POI.class ).get(); org.apache.lucene.search.Query luceneQuery = builder.spatial().onField( "location" ).within( 100, Unit.KM ).ofLatitude( centerLatitude ) .andLongitude( centerLongitude ).createQuery(); FullTextQuery hibQuery = em.createFullTextQuery( luceneQuery, POI.class ); Sort distanceSort = new Sort( new DistanceSortField( centerLatitude, centerLongitude, "location" ) ); hibQuery.setSort( distanceSort ); hibQuery.setProjection( FullTextQuery.THIS, FullTextQuery.SPATIAL_DISTANCE ); // Set max results to 3 when 6 documents are stored: hibQuery.setMaxResults( 3 ); hibQuery.setSpatialParameters( centerLatitude, centerLongitude, "location" ); List results = hibQuery.getResultList(); Object[] firstResult = (Object[]) results.get( 0 ); Object[] secondResult = (Object[]) results.get( 1 ); Object[] thirdResult = (Object[]) results.get( 2 ); Assert.assertEquals( 0.0, (Double) firstResult[1], 0.01 ); Assert.assertEquals( 10.1582, (Double) secondResult[1], 0.01 ); Assert.assertEquals( 11.1195, (Double) thirdResult[1], 0.01 ); } ); } ); } @Test public void testDoubleIndexedDistanceProjection() throws Exception { DoubleIndexedPOI poi1 = new DoubleIndexedPOI( 1, "Distance to 24,32 : 0", 24.0d, 32.0d, "" ); DoubleIndexedPOI poi2 = new DoubleIndexedPOI( 2, "Distance to 24,32 : 10.16", 24.0d, 31.9d, "" ); DoubleIndexedPOI poi3 = new DoubleIndexedPOI( 3, "Distance to 24,32 : 11.12", 23.9d, 32.0d, "" ); DoubleIndexedPOI poi4 = new DoubleIndexedPOI( 4, "Distance to 24,32 : 15.06", 23.9d, 32.1d, "" ); DoubleIndexedPOI poi5 = new DoubleIndexedPOI( 5, "Distance to 24,32 : 22.24", 24.2d, 32.0d, "" ); DoubleIndexedPOI poi6 = new DoubleIndexedPOI( 6, "Distance to 24,32 : 24.45", 24.2d, 31.9d, "" ); withinEntityManager( factory, em -> { withinTransaction( em, () -> { em.persist( poi1 ); em.persist( poi2 ); em.persist( poi3 ); } ); em.clear(); //Two different groups so that the query run on multiple segments: withinTransaction( em, () -> { em.persist( poi4 ); em.persist( poi5 ); em.persist( poi6 ); } ); em.clear(); withinTransaction( em, () -> { double centerLatitude = 24.0d; double centerLongitude = 32.0d; final QueryBuilder builder = em.getSearchFactory().buildQueryBuilder().forEntity( DoubleIndexedPOI.class ).get(); //Tests with FieldBridge org.apache.lucene.search.Query luceneQuery = builder.spatial().onField( "location" ) .within( 100, Unit.KM ).ofLatitude( centerLatitude ).andLongitude( centerLongitude ).createQuery(); FullTextQuery hibQuery = em.createFullTextQuery( luceneQuery, DoubleIndexedPOI.class ); hibQuery.setProjection( FullTextQuery.THIS, FullTextQuery.SPATIAL_DISTANCE ); hibQuery.setSpatialParameters( centerLatitude, centerLongitude, "location" ); hibQuery.setSort( builder.sort().byField( "idSort" ).createSort() ); List results = hibQuery.getResultList(); Object[] firstResult = (Object[]) results.get( 0 ); Object[] secondResult = (Object[]) results.get( 1 ); Object[] thirdResult = (Object[]) results.get( 2 ); Object[] fourthResult = (Object[]) results.get( 3 ); Object[] fifthResult = (Object[]) results.get( 4 ); Object[] sixthResult = (Object[]) results.get( 5 ); Assert.assertEquals( 0.0, (Double) firstResult[1], 0.01 ); Assert.assertEquals( 10.1582, (Double) secondResult[1], 0.01 ); Assert.assertEquals( 11.1195, (Double) thirdResult[1], 0.01 ); Assert.assertEquals( 15.0636, (Double) fourthResult[1], 0.01 ); Assert.assertEquals( 22.239, (Double) fifthResult[1], 0.02 ); Assert.assertEquals( 24.446, (Double) sixthResult[1], 0.02 ); //Tests with @Latitude+@Longitude luceneQuery = builder.spatial() .within( 100, Unit.KM ).ofLatitude( centerLatitude ).andLongitude( centerLongitude ).createQuery(); hibQuery = em.createFullTextQuery( luceneQuery, DoubleIndexedPOI.class ); hibQuery.setProjection( FullTextQuery.THIS, FullTextQuery.SPATIAL_DISTANCE ); hibQuery.setSpatialParameters( centerLatitude, centerLongitude, Spatial.COORDINATES_DEFAULT_FIELD ); hibQuery.setSort( builder.sort().byField( "idSort" ).createSort() ); results = hibQuery.getResultList(); firstResult = (Object[]) results.get( 0 ); secondResult = (Object[]) results.get( 1 ); thirdResult = (Object[]) results.get( 2 ); fourthResult = (Object[]) results.get( 3 ); fifthResult = (Object[]) results.get( 4 ); sixthResult = (Object[]) results.get( 5 ); Assert.assertEquals( 0.0, (Double) firstResult[1], 0.0001 ); Assert.assertEquals( 10.1582, (Double) secondResult[1], 0.01 ); Assert.assertEquals( 11.1195, (Double) thirdResult[1], 0.01 ); Assert.assertEquals( 15.0636, (Double) fourthResult[1], 0.01 ); Assert.assertEquals( 22.239, (Double) fifthResult[1], 0.02 ); Assert.assertEquals( 24.446, (Double) sixthResult[1], 0.02 ); } ); } ); } @Override public Class[] getAnnotatedClasses() { return new Class<?>[] { POI.class, DoubleIndexedPOI.class }; } }