/*
* 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.spatial;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Transient;
import org.apache.lucene.search.Sort;
import org.hibernate.Session;
import org.hibernate.search.FullTextQuery;
import org.hibernate.search.FullTextSession;
import org.hibernate.search.Search;
import org.hibernate.search.annotations.DocumentId;
import org.hibernate.search.annotations.Indexed;
import org.hibernate.search.annotations.Latitude;
import org.hibernate.search.annotations.Longitude;
import org.hibernate.search.annotations.Spatial;
import org.hibernate.search.annotations.SpatialMode;
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.SearchTestBase;
import org.hibernate.search.util.logging.impl.Log;
import org.hibernate.search.util.logging.impl.LoggerFactory;
import org.hibernate.testing.TestForIssue;
import org.junit.Assert;
import org.junit.Test;
/**
* Spatial search with sort by distance and paging orders entities
*
* @author PB
*/
@TestForIssue(jiraKey = "HSEARCH-1267")
public class SpatialSearchSortByDistanceAndPagingTest extends SearchTestBase {
private static final Log log = LoggerFactory.make();
private static final int EXPECTED_RESULTS_COUNT = 37;
private static final double CENTER_LAT = 54.0;
private static final double CENTER_LON = 18.0;
private static final double SEARCH_DISTANCE = 20.0;
private Map<Long, Integer> entitiesIdsSet;
private int idx;
@Test
public void testSortWithoutPaging_isOk() {
Assert.assertEquals( EXPECTED_RESULTS_COUNT, doSearch( SEARCH_DISTANCE, 50, true ) );
}
@Test
public void testSortWithPageSize5_notOk() {
Assert.assertEquals(
"sorting by distance and paging error",
EXPECTED_RESULTS_COUNT, doSearch( SEARCH_DISTANCE, 5, true )
);
}
@Test
public void testSortWithPageSize10_notOk() {
Assert.assertEquals(
"sorting by distance and paging error",
EXPECTED_RESULTS_COUNT, doSearch( SEARCH_DISTANCE, 10, true )
);
}
@Test
public void testNoSortWithPageSize5_isOk() {
Assert.assertEquals( EXPECTED_RESULTS_COUNT, doSearch( SEARCH_DISTANCE, 5, false ) );
}
@Test
public void testNoSortWithPageSize10_isOk() {
Assert.assertEquals( EXPECTED_RESULTS_COUNT, doSearch( SEARCH_DISTANCE, 10, false ) );
}
@Override
public void setUp() throws Exception {
super.setUp();
prepareTestData();
}
/**
* Search and iterate result pages.
*
* @return unique result count
*/
public int doSearch(double distance, int pageSize, boolean sortByDistance) {
log.debug(
String.format( Locale.ROOT,
"distance %.2f pageSize %d, sortByDistance %s",
distance, pageSize, sortByDistance
)
);
entitiesIdsSet = new HashMap<>();
idx = 0;
int firstResult = 0;
List result;
while ( (result = distanceSearch( CENTER_LAT, CENTER_LON, distance, firstResult, pageSize, sortByDistance ))
!= null && !result.isEmpty() ) {
printResults( result );
firstResult += pageSize;
}
return entitiesIdsSet.size();
}
/**
* Show result.
*/
private void printResults(List<GeoEntity> list) {
for ( GeoEntity entity : list ) {
log.debug(
String.format( Locale.ROOT,
"%d %f %d%s",
idx, entity.getDistance(), entity.getId(),
entitiesIdsSet.containsKey( entity.getId() )
? " was at index " + entitiesIdsSet.get( entity.getId() ) : ""
)
);
if ( !entitiesIdsSet.containsKey( entity.getId() ) ) {
entitiesIdsSet.put( entity.getId(), idx );
}
idx++;
}
}
/**
* Search GeoEntities starting from startLat and startLon within distance
*/
private List distanceSearch(
double startLat,
double startLon,
double distance,
int firstResult,
int maxResult,
boolean sortByDistance) {
List resultList = new ArrayList();
Session sessionHbn = openSession();
sessionHbn.beginTransaction();
FullTextSession fTxtSess = Search.getFullTextSession( sessionHbn );
QueryBuilder builder = fTxtSess.getSearchFactory().buildQueryBuilder().forEntity( GeoEntity.class ).get();
org.apache.lucene.search.Query luceneQuery = builder.spatial()
.within( distance, Unit.KM )
.ofLatitude( startLat )
.andLongitude( startLon )
.createQuery();
FullTextQuery hibQuery = fTxtSess.createFullTextQuery( luceneQuery, GeoEntity.class );
hibQuery.setProjection( FullTextQuery.THIS, FullTextQuery.SPATIAL_DISTANCE );
hibQuery.setSpatialParameters( startLat, startLon, Spatial.COORDINATES_DEFAULT_FIELD );
if ( sortByDistance ) {
Sort distanceSort = new Sort(
new DistanceSortField(
startLat,
startLon,
Spatial.COORDINATES_DEFAULT_FIELD
)
);
hibQuery.setSort( distanceSort );
}
hibQuery.setFirstResult( firstResult ).setMaxResults( maxResult );
List tmpList = hibQuery.list();
// copy distance from projection to entities
for ( Object obj[] : (List<Object[]>) tmpList ) {
GeoEntity entity = (GeoEntity) obj[0];
entity.setDistance( (Double) obj[1] );
resultList.add( entity );
}
sessionHbn.getTransaction().commit();
sessionHbn.close();
return resultList;
}
@Override
public Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
GeoEntity.class
};
}
@Entity
@Indexed
@Spatial(spatialMode = SpatialMode.RANGE)
public static class GeoEntity {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@DocumentId
private Long id;
@Latitude
private Double lat;
@Longitude
private Double lon;
@Transient
private Double distance;
private String value;
public GeoEntity() {
}
public GeoEntity(Double lat, Double lon, String value) {
super();
this.value = value;
this.lat = lat;
this.lon = lon;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Double getLat() {
return lat;
}
public void setLat(Double lat) {
this.lat = lat;
}
public Double getLon() {
return lon;
}
public void setLon(Double lon) {
this.lon = lon;
}
public Double getDistance() {
return distance;
}
public void setDistance(Double distance) {
this.distance = distance;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
private void prepareTestData() {
Session sessionHbn = openSession();
sessionHbn.beginTransaction();
sessionHbn.saveOrUpdate( new GeoEntity( 54.021861, 18.048349, "v00" ) );
sessionHbn.saveOrUpdate( new GeoEntity( 54.079361, 18.185003, "v01" ) );
sessionHbn.saveOrUpdate( new GeoEntity( 54.197314, 18.158194, "v02" ) );
sessionHbn.saveOrUpdate( new GeoEntity( 54.177621, 18.150250, "v03" ) );
sessionHbn.saveOrUpdate( new GeoEntity( 54.125537, 18.075425, "v04" ) );
sessionHbn.saveOrUpdate( new GeoEntity( 54.070479, 18.064328, "v05" ) );
sessionHbn.saveOrUpdate( new GeoEntity( 54.169191, 18.025334, "v06" ) );
sessionHbn.saveOrUpdate( new GeoEntity( 54.031556, 18.059142, "v07" ) );
sessionHbn.saveOrUpdate( new GeoEntity( 54.068104, 18.064764, "v08" ) );
sessionHbn.saveOrUpdate( new GeoEntity( 54.103417, 18.182243, "v09" ) );
sessionHbn.saveOrUpdate( new GeoEntity( 54.002537, 18.037648, "v10" ) );
sessionHbn.saveOrUpdate( new GeoEntity( 54.223375, 18.003011, "v11" ) );
sessionHbn.saveOrUpdate( new GeoEntity( 54.061524, 18.071648, "v12" ) );
sessionHbn.saveOrUpdate( new GeoEntity( 54.001563, 18.055457, "v13" ) );
sessionHbn.saveOrUpdate( new GeoEntity( 54.032163, 18.138202, "v14" ) );
sessionHbn.saveOrUpdate( new GeoEntity( 54.229162, 18.109120, "v15" ) );
sessionHbn.saveOrUpdate( new GeoEntity( 54.089290, 18.146594, "v16" ) );
sessionHbn.saveOrUpdate( new GeoEntity( 54.066932, 18.227589, "v17" ) );
sessionHbn.saveOrUpdate( new GeoEntity( 54.231832, 18.186478, "v18" ) );
sessionHbn.saveOrUpdate( new GeoEntity( 54.088117, 18.206227, "v19" ) );
sessionHbn.saveOrUpdate( new GeoEntity( 54.055539, 18.216111, "v20" ) );
sessionHbn.saveOrUpdate( new GeoEntity( 54.175193, 18.026838, "v21" ) );
sessionHbn.saveOrUpdate( new GeoEntity( 54.171762, 18.215527, "v22" ) );
sessionHbn.saveOrUpdate( new GeoEntity( 54.196967, 18.177212, "v23" ) );
sessionHbn.saveOrUpdate( new GeoEntity( 54.028481, 18.084819, "v24" ) );
sessionHbn.saveOrUpdate( new GeoEntity( 54.171071, 18.057752, "v25" ) );
sessionHbn.saveOrUpdate( new GeoEntity( 54.062881, 18.117777, "v26" ) );
sessionHbn.saveOrUpdate( new GeoEntity( 54.247034, 18.235728, "v27" ) );
sessionHbn.saveOrUpdate( new GeoEntity( 54.014431, 18.220235, "v28" ) );
sessionHbn.saveOrUpdate( new GeoEntity( 54.076813, 18.010077, "v29" ) );
sessionHbn.saveOrUpdate( new GeoEntity( 54.103647, 18.170640, "v30" ) );
sessionHbn.saveOrUpdate( new GeoEntity( 54.222894, 18.116137, "v31" ) );
sessionHbn.saveOrUpdate( new GeoEntity( 54.134499, 18.137614, "v32" ) );
sessionHbn.saveOrUpdate( new GeoEntity( 54.188910, 18.180216, "v33" ) );
sessionHbn.saveOrUpdate( new GeoEntity( 54.157758, 18.125557, "v34" ) );
sessionHbn.saveOrUpdate( new GeoEntity( 54.194089, 18.228482, "v35" ) );
sessionHbn.saveOrUpdate( new GeoEntity( 54.138517, 18.014723, "v36" ) );
sessionHbn.saveOrUpdate( new GeoEntity( 54.200781, 18.163288, "v37" ) );
sessionHbn.saveOrUpdate( new GeoEntity( 54.103558, 18.146598, "v38" ) );
sessionHbn.saveOrUpdate( new GeoEntity( 54.193829, 18.142770, "v39" ) );
sessionHbn.saveOrUpdate( new GeoEntity( 54.003273, 18.228929, "v40" ) );
sessionHbn.saveOrUpdate( new GeoEntity( 54.051599, 18.236313, "v41" ) );
sessionHbn.saveOrUpdate( new GeoEntity( 54.008487, 18.197262, "v42" ) );
sessionHbn.saveOrUpdate( new GeoEntity( 54.041120, 18.079304, "v43" ) );
sessionHbn.saveOrUpdate( new GeoEntity( 54.174614, 18.071497, "v44" ) );
sessionHbn.saveOrUpdate( new GeoEntity( 54.013968, 18.015511, "v45" ) );
sessionHbn.saveOrUpdate( new GeoEntity( 54.094368, 18.036610, "v46" ) );
sessionHbn.saveOrUpdate( new GeoEntity( 54.025288, 18.144333, "v47" ) );
sessionHbn.saveOrUpdate( new GeoEntity( 54.075499, 18.206458, "v48" ) );
sessionHbn.saveOrUpdate( new GeoEntity( 54.095646, 18.033243, "v49" ) );
sessionHbn.getTransaction().commit();
sessionHbn.close();
log.debug( "test data saved" );
}
}