package org.elasticsearch.common.lucene.spatial.prefix;
import com.spatial4j.core.shape.Shape;
import com.spatial4j.core.shape.jts.JtsGeometry;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.operation.buffer.BufferOp;
import com.vividsolutions.jts.operation.buffer.BufferParameters;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.*;
import org.elasticsearch.common.geo.GeoShapeConstants;
import org.elasticsearch.common.lucene.search.TermFilter;
import org.elasticsearch.common.lucene.search.XBooleanFilter;
import org.elasticsearch.common.lucene.spatial.SpatialStrategy;
import org.elasticsearch.common.lucene.spatial.prefix.tree.Node;
import org.elasticsearch.common.lucene.spatial.prefix.tree.SpatialPrefixTree;
import org.elasticsearch.common.geo.ShapeBuilder;
import org.elasticsearch.index.mapper.FieldMapper;
import java.util.List;
/**
* Implementation of {@link SpatialStrategy} that uses TermQuerys / TermFilters
* to query and filter for Shapes related to other Shapes.
*/
public class TermQueryPrefixTreeStrategy extends SpatialStrategy {
private static final double CONTAINS_BUFFER_DISTANCE = 0.5;
private static final BufferParameters BUFFER_PARAMETERS = new BufferParameters(3, BufferParameters.CAP_SQUARE);
/**
* Creates a new TermQueryPrefixTreeStrategy
*
* @param fieldName Name of the field the Strategy applies to
* @param prefixTree SpatialPrefixTree that will be used to represent Shapes
* @param distanceErrorPct Distance Error Percentage used to guide the
* SpatialPrefixTree on how precise it should be
*/
public TermQueryPrefixTreeStrategy(FieldMapper.Names fieldName, SpatialPrefixTree prefixTree, double distanceErrorPct) {
super(fieldName, prefixTree, distanceErrorPct);
}
/**
* {@inheritDoc}
*/
@Override
public Filter createIntersectsFilter(Shape shape) {
int detailLevel = getPrefixTree().getLevelForDistance(
calcDistanceFromErrPct(shape, getDistanceErrorPct(), GeoShapeConstants.SPATIAL_CONTEXT));
List<Node> nodes = getPrefixTree().getNodes(shape, detailLevel, false);
Term[] nodeTerms = new Term[nodes.size()];
for (int i = 0; i < nodes.size(); i++) {
nodeTerms[i] = getFieldName().createIndexNameTerm(nodes.get(i).getTokenString());
}
return new XTermsFilter(nodeTerms);
}
/**
* {@inheritDoc}
*/
@Override
public Query createIntersectsQuery(Shape shape) {
int detailLevel = getPrefixTree().getLevelForDistance(
calcDistanceFromErrPct(shape, getDistanceErrorPct(), GeoShapeConstants.SPATIAL_CONTEXT));
List<Node> nodes = getPrefixTree().getNodes(shape, detailLevel, false);
BooleanQuery query = new BooleanQuery();
for (Node node : nodes) {
query.add(new TermQuery(getFieldName().createIndexNameTerm(node.getTokenString())),
BooleanClause.Occur.SHOULD);
}
return new ConstantScoreQuery(query);
}
/**
* {@inheritDoc}
*/
@Override
public Filter createDisjointFilter(Shape shape) {
int detailLevel = getPrefixTree().getLevelForDistance(
calcDistanceFromErrPct(shape, getDistanceErrorPct(), GeoShapeConstants.SPATIAL_CONTEXT));
List<Node> nodes = getPrefixTree().getNodes(shape, detailLevel, false);
XBooleanFilter filter = new XBooleanFilter();
for (Node node : nodes) {
filter.addNot(new TermFilter(getFieldName().createIndexNameTerm(node.getTokenString())));
}
return filter;
}
/**
* {@inheritDoc}
*/
@Override
public Query createDisjointQuery(Shape shape) {
int detailLevel = getPrefixTree().getLevelForDistance(
calcDistanceFromErrPct(shape, getDistanceErrorPct(), GeoShapeConstants.SPATIAL_CONTEXT));
List<Node> nodes = getPrefixTree().getNodes(shape, detailLevel, false);
BooleanQuery query = new BooleanQuery();
query.add(new MatchAllDocsQuery(), BooleanClause.Occur.SHOULD);
for (Node node : nodes) {
query.add(new TermQuery(getFieldName().createIndexNameTerm(node.getTokenString())),
BooleanClause.Occur.MUST_NOT);
}
return new ConstantScoreQuery(query);
}
/**
* {@inheritDoc}
*/
@Override
public Filter createContainsFilter(Shape shape) {
Filter intersectsFilter = createIntersectsFilter(shape);
Geometry shapeGeometry = ShapeBuilder.toJTSGeometry(shape);
Geometry buffer = BufferOp.bufferOp(shapeGeometry, CONTAINS_BUFFER_DISTANCE, BUFFER_PARAMETERS);
Shape bufferedShape = new JtsGeometry(buffer.difference(shapeGeometry), GeoShapeConstants.SPATIAL_CONTEXT, true);
Filter bufferedFilter = createIntersectsFilter(bufferedShape);
XBooleanFilter filter = new XBooleanFilter();
filter.addShould(intersectsFilter);
filter.addNot(bufferedFilter);
return filter;
}
/**
* {@inheritDoc}
*/
@Override
public Query createContainsQuery(Shape shape) {
Query intersectsQuery = createIntersectsQuery(shape);
Geometry shapeGeometry = ShapeBuilder.toJTSGeometry(shape);
Geometry buffer = BufferOp.bufferOp(shapeGeometry, CONTAINS_BUFFER_DISTANCE, BUFFER_PARAMETERS);
Shape bufferedShape = new JtsGeometry(buffer.difference(shapeGeometry), GeoShapeConstants.SPATIAL_CONTEXT, true);
Query bufferedQuery = createIntersectsQuery(bufferedShape);
BooleanQuery query = new BooleanQuery();
query.add(intersectsQuery, BooleanClause.Occur.SHOULD);
query.add(bufferedQuery, BooleanClause.Occur.MUST_NOT);
return new ConstantScoreQuery(query);
}
}