2016-09-11 6 views
1

Я работаю над приложением, в котором есть рестораны, каждый с доставкой, и должен отвечать за пользователя, который рестораны могут доставить в его текущее местоположение.Hibernate Запрос по поиску всех объектов, пересекающих точку

Я получаю тривиальное решение с hibernate-пространственным, но когда я вхожу в hibernate-поиск полнотекстового поиска в сочетании с географическим (и из-за масштабируемости), я еще не нашел решения. Некоторые идеи/предложения/примеры?

Например, в спящем Spatial запрос, как это:

SELECT r FROM Restaurant r WHERE within(:point, r.coverage) 

, где, очевидно, покрытие покрытие ресторан.

Я думаю, что решение справиться с ней с помощью спящего режима - это добавить фильтр, но все образцы, которые я нашел, не были с чем-то похожим.

ответ

0

Я нашел решение после нескольких незначительных головных болей.

я реализую мост для геометрического поля:

import com.spatial4j.core.context.jts.JtsSpatialContext; 
import com.spatial4j.core.shape.jts.JtsGeometry; 
import com.vividsolutions.jts.geom.Geometry; 
import org.apache.lucene.document.Document; 
import org.apache.lucene.index.IndexableField; 
import org.apache.lucene.spatial.SpatialStrategy; 
import org.apache.lucene.spatial.prefix.RecursivePrefixTreeStrategy; 
import org.apache.lucene.spatial.prefix.tree.GeohashPrefixTree; 
import org.apache.lucene.spatial.prefix.tree.SpatialPrefixTree; 
import org.apache.lucene.spatial.serialized.SerializedDVStrategy; 
import org.hibernate.search.bridge.FieldBridge; 
import org.hibernate.search.bridge.LuceneOptions; 

/** 
* @see https://docs.jboss.org/hibernate/stable/search/reference/en-US/html/ch04.html#section-custom-bridges 
* @see http://www.gossamer-threads.com/lists/lucene/java-user/254444 
*/ 
public class GeometryBridge implements FieldBridge { 

    // http://unterbahn.com/2009/11/metric-dimensions-of-geohash-partitions-at-the-equator/ 
    public static final int GEOHASH_MAX_LEVELS = 9; // 4.78m 

    public void set(String name, Object value, Document document, LuceneOptions luceneOptions) { 
     JtsSpatialContext spatialContext = JtsSpatialContext.GEO; 
     SpatialPrefixTree grid = new GeohashPrefixTree(spatialContext, 22); 

     // Preparing the tree strategy field 
     SpatialStrategy treeStrategy = new RecursivePrefixTreeStrategy(grid, name); 
     for (IndexableField field: treeStrategy.createIndexableFields(
      new JtsGeometry((Geometry) value, spatialContext, false, true))) { 
      document.add(field); 
     } 

     // Preparing the verify strategy field 
     SerializedDVStrategy verifyStrategy = new SerializedDVStrategy(spatialContext, "serialized_" + name); 
     for (IndexableField field: verifyStrategy.createIndexableFields(
      new JtsGeometry((Geometry) value, spatialContext, false, true))) { 
      document.add(field); 
     } 
    } 

} 

An позже пользовательского фильтра для фильтрации запроса:

import com.spatial4j.core.context.jts.JtsSpatialContext; 
import com.spatial4j.core.shape.Point; 
import org.apache.lucene.search.ConstantScoreQuery; 
import org.apache.lucene.search.Filter; 
import org.apache.lucene.search.FilteredQuery; 
import org.apache.lucene.search.Query; 
import org.apache.lucene.search.QueryWrapperFilter; 
import org.apache.lucene.spatial.SpatialStrategy; 
import org.apache.lucene.spatial.prefix.RecursivePrefixTreeStrategy; 
import org.apache.lucene.spatial.prefix.tree.GeohashPrefixTree; 
import org.apache.lucene.spatial.prefix.tree.SpatialPrefixTree; 
import org.apache.lucene.spatial.query.SpatialArgs; 
import org.apache.lucene.spatial.query.SpatialOperation; 
import org.apache.lucene.spatial.serialized.SerializedDVStrategy; 
import org.hibernate.search.annotations.Factory; 
import org.hibernate.search.filter.impl.CachingWrapperFilter; 

/** 
* @see https://vimeo.com/106843184 
* @see http://www.slideshare.net/lucenerevolution/lucene-solr-4-spatial-extended-deep-dive 
*/ 
public class CoverageFilterFactory { 

    public static final String NAME = "coverage"; 

    private String field; 
    private double latitude; 
    private double longitude; 

    public void setField(String field) { 
     this.field = field; 
    } 

    public void setLatitude(double latitude) { 
     this.latitude = latitude; 
    } 

    public void setLongitude(double longitude) { 
     this.longitude = longitude; 
    } 

    @Factory 
    public Filter getFilter() { 
     JtsSpatialContext spatialContext = JtsSpatialContext.GEO; 
     Point point = spatialContext.makePoint(latitude, longitude); 
     SpatialArgs spatialArgs = new SpatialArgs(SpatialOperation.Intersects, point); 

     SpatialPrefixTree grid = new GeohashPrefixTree(spatialContext, GeometryBridge.GEOHASH_MAX_LEVELS); 
     SpatialStrategy treeStrategy = new RecursivePrefixTreeStrategy(grid, field); 

     SerializedDVStrategy verifyStrategy = new SerializedDVStrategy(spatialContext, "serialized_" + field); 

     Query treeQuery = new ConstantScoreQuery(treeStrategy.makeFilter(spatialArgs)); 
     Query combinedQuery = new FilteredQuery(treeQuery, 
      verifyStrategy.makeFilter(spatialArgs), 
      FilteredQuery.QUERY_FIRST_FILTER_STRATEGY); 

     return new CachingWrapperFilter(new QueryWrapperFilter(combinedQuery)); 
    } 

} 

И, наконец, вот код для пользовательских зимуют хранилище:

import java.util.List; 
import java.util.Locale; 
import java.util.stream.Collectors; 
import javax.persistence.EntityManager; 
import org.apache.lucene.search.Sort; 
import org.hibernate.search.annotations.Spatial; 
import org.hibernate.search.jpa.FullTextEntityManager; 
import org.hibernate.search.jpa.FullTextQuery; 
import org.hibernate.search.jpa.Search; 
import org.hibernate.search.query.dsl.QueryBuilder; 
import org.hibernate.search.query.dsl.Unit; 
import org.hibernate.search.spatial.DistanceSortField; 
import org.springframework.beans.factory.annotation.Autowired; 
import org.springframework.data.domain.Page; 
import org.springframework.data.domain.PageImpl; 
import org.springframework.data.domain.Pageable; 

public class CompanyRepositoryImpl implements CompanyRepositoryCustom { 

    @Autowired 
    private EntityManager entityManager; 

    @Override 
    public Page<SearchResult> search(Locale locale, double latitude, double longitude, 
     Order.DeliveryMode deliveryMode, String text, Pageable pageRequest) { 
     FullTextEntityManager fullTextEntityManager = Search.getFullTextEntityManager(entityManager); 

     String analyzerName; 
     switch (locale.getLanguage()) { 
      case "es": analyzerName = SPANISH_NAME_FIELD_DISCRIMINATOR; break; 
      case "en": analyzerName = ENGLISH_NAME_FIELD_DISCRIMINATOR; break; 
      default: throw new RuntimeException("Unexpected language found"); 
     } 

     QueryBuilder queryBuilder = fullTextEntityManager.getSearchFactory() 
      .buildQueryBuilder() 
      .forEntity(Company.class) 
       .overridesForField(Company_.name.getName(), analyzerName) 
       .overridesForField(Company_.groups.getName() + "." + Group_.name.getName(), analyzerName) 
       .overridesForField(Company_.products.getName() + "." + Product_.name.getName(), analyzerName) 
      .get(); 
     org.apache.lucene.search.Query luceneQuery = queryBuilder 
      .spatial() 
       .within(30000, Unit.KM) 
        .ofLatitude(latitude) 
        .andLongitude(longitude) 
      .createQuery(); 
     // Adding text if corresponds 
     if (text != null) { 
      luceneQuery = queryBuilder 
       .bool() 
        .must(luceneQuery) 
        .must(queryBuilder 
         .keyword() 
          .onField(Company_.name.getName()) 
           .boostedTo(2f) 
          .andField(Company_.headline.getName()) 
          .andField(Company_.groups.getName() + "." + Group_.name.getName()) 
          .andField(Company_.products.getName() + "." + Product_.name.getName()) 
          .matching(text) 
         .createQuery() 
        ) 
       .createQuery(); 
     } 

     // wrap Lucene query in a javax.persistence.Query 
     FullTextQuery fullTextQuery = fullTextEntityManager.createFullTextQuery(luceneQuery); 
     fullTextQuery.setProjection(FullTextQuery.SPATIAL_DISTANCE, FullTextQuery.THIS); 
     fullTextQuery.setSpatialParameters(latitude, longitude, Spatial.COORDINATES_DEFAULT_FIELD); 

     // Applying filters 
     if (deliveryMode != null) { 
      fullTextQuery.enableFullTextFilter(DeliveryModeFilterFactory.NAME).setParameter("deliveryMode", deliveryMode); 
      if (deliveryMode == Order.DeliveryMode.DELIVERY) { 
       fullTextQuery.enableFullTextFilter(CoverageFilterFactory.NAME) 
        .setParameter("field", "coverage") 
        .setParameter("latitude", latitude) 
        .setParameter("longitude", longitude); 
      } 
     } 

     // Sorting the results 
     Sort distanceSort = new Sort(new DistanceSortField(latitude, longitude, Spatial.COORDINATES_DEFAULT_FIELD)); 
     fullTextQuery.setSort(distanceSort); 

     // Execute Search 
     // Caution: The number of results might be slightly different from getResultList().size() because getResultList() may be not in sync with the database at the time of query. 
     long total = fullTextQuery.getResultSize(); 
     List<Object[]> result = fullTextQuery 
      .setMaxResults(pageRequest.getPageSize()) 
      .setFirstResult(pageRequest.getOffset()) 
      .getResultList(); 

     // Transforming the results 
     List<SearchResult> content = result.stream() 
      .map(o -> new SearchResult((double) o[0], (Company) o[1])) 
      .collect(Collectors.toList()); 

     return new PageImpl<>(content, pageRequest, total); 
    } 

}