package org.elasticsearch.common.lucene.spatial; import com.spatial4j.core.context.SpatialContext; import com.spatial4j.core.shape.Point; import com.spatial4j.core.shape.Rectangle; import com.spatial4j.core.shape.Shape; import org.apache.lucene.document.Field; import org.apache.lucene.document.Fieldable; import org.apache.lucene.search.Filter; import org.apache.lucene.search.Query; import org.elasticsearch.common.geo.GeoShapeConstants; import org.elasticsearch.common.geo.ShapeRelation; import org.elasticsearch.common.lucene.spatial.prefix.NodeTokenStream; import org.elasticsearch.common.lucene.spatial.prefix.tree.Node; import org.elasticsearch.common.lucene.spatial.prefix.tree.SpatialPrefixTree; import org.elasticsearch.index.mapper.FieldMapper; import java.util.List; /** * Abstraction of the logic used to index and filter Shapes. */ public abstract class SpatialStrategy { private final FieldMapper.Names fieldName; private final double distanceErrorPct; private final SpatialPrefixTree prefixTree; private ThreadLocal<NodeTokenStream> nodeTokenStream = new ThreadLocal<NodeTokenStream>() { @Override protected NodeTokenStream initialValue() { return new NodeTokenStream(); } }; /** * Creates a new SpatialStrategy that will index and Filter using the * given field * * @param fieldName Name of the field that the Strategy will index in and Filter * @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 */ protected SpatialStrategy(FieldMapper.Names fieldName, SpatialPrefixTree prefixTree, double distanceErrorPct) { this.fieldName = fieldName; this.prefixTree = prefixTree; this.distanceErrorPct = distanceErrorPct; } /** * Converts the given Shape into its indexable format. Implementations * should not store the Shape value as well. * * @param shape Shape to convert ints its indexable format * @return Fieldable for indexing the Shape */ public Fieldable createField(Shape shape) { int detailLevel = prefixTree.getLevelForDistance( calcDistanceFromErrPct(shape, distanceErrorPct, GeoShapeConstants.SPATIAL_CONTEXT)); List<Node> nodes = prefixTree.getNodes(shape, detailLevel, true); NodeTokenStream tokenStream = nodeTokenStream.get(); tokenStream.setNodes(nodes); return new Field(fieldName.indexName(), tokenStream); } /** * Creates a Filter that will find all indexed Shapes that relate to the * given Shape * * @param shape Shape the indexed shapes will relate to * @param relation Nature of the relation * @return Filter for finding the related shapes */ public Filter createFilter(Shape shape, ShapeRelation relation) { switch (relation) { case INTERSECTS: return createIntersectsFilter(shape); case CONTAINS: return createContainsFilter(shape); case DISJOINT: return createDisjointFilter(shape); default: throw new UnsupportedOperationException("Shape Relation [" + relation.getRelationName() + "] not currently supported"); } } /** * Creates a Query that will find all indexed Shapes that relate to the * given Shape * * @param shape Shape the indexed shapes will relate to * @param relation Nature of the relation * @return Query for finding the related shapes */ public Query createQuery(Shape shape, ShapeRelation relation) { switch (relation) { case INTERSECTS: return createIntersectsQuery(shape); case CONTAINS: return createContainsQuery(shape); case DISJOINT: return createDisjointQuery(shape); default: throw new UnsupportedOperationException("Shape Relation [" + relation.getRelationName() + "] not currently supported"); } } /** * Creates a Filter that will find all indexed Shapes that intersect with * the given Shape * * @param shape Shape to find the intersection Shapes of * @return Filter finding the intersecting indexed Shapes */ public abstract Filter createIntersectsFilter(Shape shape); /** * Creates a Query that will find all indexed Shapes that intersect with * the given Shape * * @param shape Shape to find the intersection Shapes of * @return Query finding the intersecting indexed Shapes */ public abstract Query createIntersectsQuery(Shape shape); /** * Creates a Filter that will find all indexed Shapes that are disjoint * to the given Shape * * @param shape Shape to find the disjoint Shapes of * @return Filter for finding the disjoint indexed Shapes */ public abstract Filter createDisjointFilter(Shape shape); /** * Creates a Query that will find all indexed Shapes that are disjoint * to the given Shape * * @param shape Shape to find the disjoint Shapes of * @return Query for finding the disjoint indexed Shapes */ public abstract Query createDisjointQuery(Shape shape); /** * Creates a Filter that will find all indexed Shapes that are properly * contained within the given Shape (the indexed Shapes will not have * any area outside of the given Shape). * * @param shape Shape to find the contained Shapes of * @return Filter for finding the contained indexed Shapes */ public abstract Filter createContainsFilter(Shape shape); /** * Creates a Query that will find all indexed Shapes that are properly * contained within the given Shape (the indexed Shapes will not have * any area outside of the given Shape). * * @param shape Shape to find the contained Shapes of * @return Query for finding the contained indexed Shapes */ public abstract Query createContainsQuery(Shape shape); /** * Returns the name of the field this Strategy applies to * * @return Name of the field the Strategy applies to */ public FieldMapper.Names getFieldName() { return fieldName; } /** * Returns the distance error percentage for this Strategy * * @return Distance error percentage for the Strategy */ public double getDistanceErrorPct() { return distanceErrorPct; } /** * Returns the {@link SpatialPrefixTree} used by this Strategy * * @return SpatialPrefixTree used by the Strategy */ public SpatialPrefixTree getPrefixTree() { return prefixTree; } /** * Computes the distance given a shape and the {@code distErrPct}. The * algorithm is the fraction of the distance from the center of the query * shape to its furthest bounding box corner. * * @param shape Mandatory. * @param distErrPct 0 to 0.5 * @param ctx Mandatory * @return A distance (in degrees). */ protected final double calcDistanceFromErrPct(Shape shape, double distErrPct, SpatialContext ctx) { if (distErrPct < 0 || distErrPct > 0.5) { throw new IllegalArgumentException("distErrPct " + distErrPct + " must be between [0 to 0.5]"); } if (distErrPct == 0 || shape instanceof Point) { return 0; } Rectangle bbox = shape.getBoundingBox(); //The diagonal distance should be the same computed from any opposite corner, // and this is the longest distance that might be occurring within the shape. double diagonalDist = ctx.getDistCalc().distance( ctx.makePoint(bbox.getMinX(), bbox.getMinY()), bbox.getMaxX(), bbox.getMaxY()); return diagonalDist * 0.5 * distErrPct; } }