mirror of
https://github.com/honeymoose/OpenSearch.git
synced 2025-02-17 02:14:54 +00:00
Remove backported Lucene 4 spatial code in favor of the released version in Lucene 4.1
This commit is contained in:
parent
0dfc2169d7
commit
8db436f107
6
pom.xml
6
pom.xml
@ -103,6 +103,12 @@
|
|||||||
<version>${lucene.version}</version>
|
<version>${lucene.version}</version>
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.lucene</groupId>
|
||||||
|
<artifactId>lucene-spatial</artifactId>
|
||||||
|
<version>${lucene.version}</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
<!-- START: dependencies that are shaded -->
|
<!-- START: dependencies that are shaded -->
|
||||||
|
@ -1,217 +0,0 @@
|
|||||||
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.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 Field 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);
|
|
||||||
// LUCENE 4 Upgrade: We should pass in the FieldType and use it here
|
|
||||||
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 WITHIN:
|
|
||||||
return createWithinFilter(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 WITHIN:
|
|
||||||
return createWithinQuery(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 createWithinFilter(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 createWithinQuery(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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,119 +1,111 @@
|
|||||||
package org.elasticsearch.common.lucene.spatial.prefix;
|
package org.elasticsearch.common.lucene.spatial;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
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.queries.TermsFilter;
|
import org.apache.lucene.queries.TermsFilter;
|
||||||
import org.apache.lucene.search.*;
|
import org.apache.lucene.search.BooleanClause;
|
||||||
|
import org.apache.lucene.search.BooleanQuery;
|
||||||
|
import org.apache.lucene.search.ConstantScoreQuery;
|
||||||
|
import org.apache.lucene.search.Filter;
|
||||||
|
import org.apache.lucene.search.MatchAllDocsQuery;
|
||||||
|
import org.apache.lucene.search.Query;
|
||||||
|
import org.apache.lucene.search.TermQuery;
|
||||||
|
import org.apache.lucene.spatial.SpatialStrategy;
|
||||||
|
import org.apache.lucene.spatial.prefix.PrefixTreeStrategy;
|
||||||
|
import org.apache.lucene.spatial.prefix.tree.Node;
|
||||||
|
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.util.BytesRef;
|
import org.apache.lucene.util.BytesRef;
|
||||||
import org.elasticsearch.common.geo.GeoShapeConstants;
|
import org.elasticsearch.common.geo.GeoShapeConstants;
|
||||||
import org.elasticsearch.common.geo.ShapeBuilder;
|
import org.elasticsearch.common.geo.ShapeBuilder;
|
||||||
import org.elasticsearch.common.lucene.search.TermFilter;
|
import org.elasticsearch.common.lucene.search.TermFilter;
|
||||||
import org.elasticsearch.common.lucene.search.XBooleanFilter;
|
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.index.mapper.FieldMapper;
|
import org.elasticsearch.index.mapper.FieldMapper;
|
||||||
|
import org.elasticsearch.index.mapper.FieldMapper.Names;
|
||||||
|
|
||||||
import java.util.List;
|
import com.spatial4j.core.context.SpatialContext;
|
||||||
|
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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of {@link SpatialStrategy} that uses TermQuerys / TermFilters
|
* Implementation of {@link SpatialStrategy} that uses TermQuerys / TermFilters
|
||||||
* to query and filter for Shapes related to other Shapes.
|
* to query and filter for Shapes related to other Shapes.
|
||||||
*/
|
*/
|
||||||
public class TermQueryPrefixTreeStrategy extends SpatialStrategy {
|
public final class XTermQueryPrefixTreeStategy extends PrefixTreeStrategy {
|
||||||
|
|
||||||
private static final double WITHIN_BUFFER_DISTANCE = 0.5;
|
private static final double WITHIN_BUFFER_DISTANCE = 0.5;
|
||||||
private static final BufferParameters BUFFER_PARAMETERS = new BufferParameters(3, BufferParameters.CAP_SQUARE);
|
private static final BufferParameters BUFFER_PARAMETERS = new BufferParameters(3, BufferParameters.CAP_SQUARE);
|
||||||
|
private final Names fieldName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new TermQueryPrefixTreeStrategy
|
* Creates a new XTermQueryPrefixTreeStategy
|
||||||
*
|
*
|
||||||
* @param fieldName Name of the field the Strategy applies to
|
|
||||||
* @param prefixTree SpatialPrefixTree that will be used to represent Shapes
|
* @param prefixTree SpatialPrefixTree that will be used to represent Shapes
|
||||||
* @param distanceErrorPct Distance Error Percentage used to guide the
|
* @param fieldName Name of the field the Strategy applies to
|
||||||
* SpatialPrefixTree on how precise it should be
|
|
||||||
*/
|
*/
|
||||||
public TermQueryPrefixTreeStrategy(FieldMapper.Names fieldName, SpatialPrefixTree prefixTree, double distanceErrorPct) {
|
public XTermQueryPrefixTreeStategy(SpatialPrefixTree prefixTree, FieldMapper.Names fieldName) {
|
||||||
super(fieldName, prefixTree, distanceErrorPct);
|
super(prefixTree, fieldName.indexName());
|
||||||
|
this.fieldName = fieldName;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double resolveDistErr(Shape shape, SpatialContext ctx, double distErrPct) {
|
||||||
|
return SpatialArgs.calcDistanceFromErrPct(shape, distErrPct, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Filter createIntersectsFilter(Shape shape) {
|
public Filter createIntersectsFilter(Shape shape) {
|
||||||
int detailLevel = getPrefixTree().getLevelForDistance(
|
int detailLevel = getGrid().getLevelForDistance(resolveDistErr(shape, ctx, getDistErrPct()));
|
||||||
calcDistanceFromErrPct(shape, getDistanceErrorPct(), GeoShapeConstants.SPATIAL_CONTEXT));
|
List<Node> nodes = getGrid().getNodes(shape, detailLevel, false);
|
||||||
List<Node> nodes = getPrefixTree().getNodes(shape, detailLevel, false);
|
|
||||||
|
|
||||||
BytesRef[] nodeTerms = new BytesRef[nodes.size()];
|
BytesRef[] nodeTerms = new BytesRef[nodes.size()];
|
||||||
for (int i = 0; i < nodes.size(); i++) {
|
for (int i = 0; i < nodes.size(); i++) {
|
||||||
nodeTerms[i] = new BytesRef(nodes.get(i).getTokenString());
|
nodeTerms[i] = new BytesRef(nodes.get(i).getTokenString());
|
||||||
}
|
}
|
||||||
return new TermsFilter(getFieldName().indexName(), nodeTerms);
|
return new TermsFilter(fieldName.indexName(), nodeTerms);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Query createIntersectsQuery(Shape shape) {
|
public Query createIntersectsQuery(Shape shape) {
|
||||||
int detailLevel = getPrefixTree().getLevelForDistance(
|
int detailLevel = getGrid().getLevelForDistance(resolveDistErr(shape, ctx, getDistErrPct()));
|
||||||
calcDistanceFromErrPct(shape, getDistanceErrorPct(), GeoShapeConstants.SPATIAL_CONTEXT));
|
List<Node> nodes = getGrid().getNodes(shape, detailLevel, false);
|
||||||
List<Node> nodes = getPrefixTree().getNodes(shape, detailLevel, false);
|
|
||||||
|
|
||||||
BooleanQuery query = new BooleanQuery();
|
BooleanQuery query = new BooleanQuery();
|
||||||
for (Node node : nodes) {
|
for (Node node : nodes) {
|
||||||
query.add(new TermQuery(getFieldName().createIndexNameTerm(node.getTokenString())),
|
query.add(new TermQuery(fieldName.createIndexNameTerm(node.getTokenString())),
|
||||||
BooleanClause.Occur.SHOULD);
|
BooleanClause.Occur.SHOULD);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ConstantScoreQuery(query);
|
return new ConstantScoreQuery(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Filter createDisjointFilter(Shape shape) {
|
public Filter createDisjointFilter(Shape shape) {
|
||||||
int detailLevel = getPrefixTree().getLevelForDistance(
|
int detailLevel = getGrid().getLevelForDistance(resolveDistErr(shape, ctx, getDistErrPct()));
|
||||||
calcDistanceFromErrPct(shape, getDistanceErrorPct(), GeoShapeConstants.SPATIAL_CONTEXT));
|
List<Node> nodes = getGrid().getNodes(shape, detailLevel, false);
|
||||||
List<Node> nodes = getPrefixTree().getNodes(shape, detailLevel, false);
|
|
||||||
|
|
||||||
XBooleanFilter filter = new XBooleanFilter();
|
XBooleanFilter filter = new XBooleanFilter();
|
||||||
for (Node node : nodes) {
|
for (Node node : nodes) {
|
||||||
filter.add(new TermFilter(getFieldName().createIndexNameTerm(node.getTokenString())), BooleanClause.Occur.MUST_NOT);
|
filter.add(new TermFilter(fieldName.createIndexNameTerm(node.getTokenString())), BooleanClause.Occur.MUST_NOT);
|
||||||
}
|
}
|
||||||
|
|
||||||
return filter;
|
return filter;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Query createDisjointQuery(Shape shape) {
|
public Query createDisjointQuery(Shape shape) {
|
||||||
int detailLevel = getPrefixTree().getLevelForDistance(
|
int detailLevel = getGrid().getLevelForDistance(resolveDistErr(shape, ctx, getDistErrPct()));
|
||||||
calcDistanceFromErrPct(shape, getDistanceErrorPct(), GeoShapeConstants.SPATIAL_CONTEXT));
|
List<Node> nodes = getGrid().getNodes(shape, detailLevel, false);
|
||||||
List<Node> nodes = getPrefixTree().getNodes(shape, detailLevel, false);
|
|
||||||
|
|
||||||
BooleanQuery query = new BooleanQuery();
|
BooleanQuery query = new BooleanQuery();
|
||||||
query.add(new MatchAllDocsQuery(), BooleanClause.Occur.SHOULD);
|
query.add(new MatchAllDocsQuery(), BooleanClause.Occur.SHOULD);
|
||||||
for (Node node : nodes) {
|
for (Node node : nodes) {
|
||||||
query.add(new TermQuery(getFieldName().createIndexNameTerm(node.getTokenString())),
|
query.add(new TermQuery(fieldName.createIndexNameTerm(node.getTokenString())),
|
||||||
BooleanClause.Occur.MUST_NOT);
|
BooleanClause.Occur.MUST_NOT);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ConstantScoreQuery(query);
|
return new ConstantScoreQuery(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Filter createWithinFilter(Shape shape) {
|
public Filter createWithinFilter(Shape shape) {
|
||||||
Filter intersectsFilter = createIntersectsFilter(shape);
|
Filter intersectsFilter = createIntersectsFilter(shape);
|
||||||
|
|
||||||
@ -129,10 +121,6 @@ public class TermQueryPrefixTreeStrategy extends SpatialStrategy {
|
|||||||
return filter;
|
return filter;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Query createWithinQuery(Shape shape) {
|
public Query createWithinQuery(Shape shape) {
|
||||||
Query intersectsQuery = createIntersectsQuery(shape);
|
Query intersectsQuery = createIntersectsQuery(shape);
|
||||||
|
|
||||||
@ -147,4 +135,35 @@ public class TermQueryPrefixTreeStrategy extends SpatialStrategy {
|
|||||||
|
|
||||||
return new ConstantScoreQuery(query);
|
return new ConstantScoreQuery(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Filter makeFilter(SpatialArgs args) {
|
||||||
|
if (args.getOperation() == SpatialOperation.Intersects) {
|
||||||
|
return createIntersectsFilter(args.getShape());
|
||||||
|
|
||||||
|
} else if (args.getOperation() == SpatialOperation.IsWithin) {
|
||||||
|
return createWithinFilter(args.getShape());
|
||||||
|
|
||||||
|
} else if (args.getOperation() == SpatialOperation.IsDisjointTo) {
|
||||||
|
return createDisjointFilter(args.getShape());
|
||||||
|
|
||||||
|
}
|
||||||
|
throw new UnsupportedOperationException("Shape Relation [" + args.getOperation().getName() + "] not currently supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Query makeQuery(SpatialArgs args) {
|
||||||
|
if (args.getOperation() == SpatialOperation.Intersects) {
|
||||||
|
return createIntersectsQuery(args.getShape());
|
||||||
|
|
||||||
|
} else if (args.getOperation() == SpatialOperation.IsWithin) {
|
||||||
|
return createWithinQuery(args.getShape());
|
||||||
|
|
||||||
|
} else if (args.getOperation() == SpatialOperation.IsDisjointTo) {
|
||||||
|
return createDisjointQuery(args.getShape());
|
||||||
|
|
||||||
|
}
|
||||||
|
throw new UnsupportedOperationException("Shape Relation [" + args.getOperation().getName() + "] not currently supported");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,58 +0,0 @@
|
|||||||
package org.elasticsearch.common.lucene.spatial.prefix;
|
|
||||||
|
|
||||||
import org.apache.lucene.analysis.TokenStream;
|
|
||||||
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
|
|
||||||
import org.elasticsearch.common.lucene.spatial.prefix.tree.Node;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Custom {@link TokenStream} used to convert a list of {@link Node} representing
|
|
||||||
* a Shape, into indexable terms.
|
|
||||||
*/
|
|
||||||
public final class NodeTokenStream extends TokenStream {
|
|
||||||
|
|
||||||
private final CharTermAttribute termAtt = addAttribute(CharTermAttribute.class);
|
|
||||||
|
|
||||||
private List<Node> nodes;
|
|
||||||
private Iterator<Node> iterator;
|
|
||||||
private CharSequence nextTokenStringNeedingLeaf = null;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final boolean incrementToken() throws IOException {
|
|
||||||
clearAttributes();
|
|
||||||
if (nextTokenStringNeedingLeaf != null) {
|
|
||||||
termAtt.append(nextTokenStringNeedingLeaf);
|
|
||||||
termAtt.append((char) Node.LEAF_BYTE);
|
|
||||||
nextTokenStringNeedingLeaf = null;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (iterator.hasNext()) {
|
|
||||||
Node cell = iterator.next();
|
|
||||||
CharSequence token = cell.getTokenString();
|
|
||||||
termAtt.append(token);
|
|
||||||
if (cell.isLeaf()) {
|
|
||||||
nextTokenStringNeedingLeaf = token;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void reset() throws IOException {
|
|
||||||
iterator = nodes.iterator();
|
|
||||||
nextTokenStringNeedingLeaf = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the Nodes that will be converted into their indexable form
|
|
||||||
*
|
|
||||||
* @param nodes Nodes to be converted
|
|
||||||
*/
|
|
||||||
public void setNodes(List<Node> nodes) {
|
|
||||||
this.nodes = nodes;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,140 +0,0 @@
|
|||||||
/*
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
||||||
* contributor license agreements. See the NOTICE file distributed with
|
|
||||||
* this work for additional information regarding copyright ownership.
|
|
||||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
|
||||||
* (the "License"); you may not use this file except in compliance with
|
|
||||||
* the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.elasticsearch.common.lucene.spatial.prefix.tree;
|
|
||||||
|
|
||||||
import com.spatial4j.core.context.SpatialContext;
|
|
||||||
import com.spatial4j.core.io.GeohashUtils;
|
|
||||||
import com.spatial4j.core.shape.Point;
|
|
||||||
import com.spatial4j.core.shape.Rectangle;
|
|
||||||
import com.spatial4j.core.shape.Shape;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A SpatialPrefixGrid based on Geohashes. Uses {@link GeohashUtils} to do all the geohash work.
|
|
||||||
*
|
|
||||||
* @lucene.experimental
|
|
||||||
*/
|
|
||||||
public class GeohashPrefixTree extends SpatialPrefixTree {
|
|
||||||
|
|
||||||
public GeohashPrefixTree(SpatialContext ctx, int maxLevels) {
|
|
||||||
super(ctx, maxLevels);
|
|
||||||
Rectangle bounds = ctx.getWorldBounds();
|
|
||||||
if (bounds.getMinX() != -180)
|
|
||||||
throw new IllegalArgumentException("Geohash only supports lat-lon world bounds. Got " + bounds);
|
|
||||||
int MAXP = getMaxLevelsPossible();
|
|
||||||
if (maxLevels <= 0 || maxLevels > MAXP)
|
|
||||||
throw new IllegalArgumentException("maxLen must be [1-" + MAXP + "] but got " + maxLevels);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Any more than this and there's no point (double lat & lon are the same).
|
|
||||||
*/
|
|
||||||
public static int getMaxLevelsPossible() {
|
|
||||||
return GeohashUtils.MAX_PRECISION;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getLevelForDistance(double dist) {
|
|
||||||
if (dist == 0)
|
|
||||||
return maxLevels;//short circuit
|
|
||||||
final int level = GeohashUtils.lookupHashLenForWidthHeight(dist, dist);
|
|
||||||
return Math.max(Math.min(level, maxLevels), 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Node getNode(Point p, int level) {
|
|
||||||
return new GhCell(GeohashUtils.encodeLatLon(p.getY(), p.getX(), level));//args are lat,lon (y,x)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Node getNode(String token) {
|
|
||||||
return new GhCell(token);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Node getNode(byte[] bytes, int offset, int len) {
|
|
||||||
return new GhCell(bytes, offset, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<Node> getNodes(Shape shape, int detailLevel, boolean inclParents) {
|
|
||||||
return shape instanceof Point ? super.getNodesAltPoint((Point) shape, detailLevel, inclParents) :
|
|
||||||
super.getNodes(shape, detailLevel, inclParents);
|
|
||||||
}
|
|
||||||
|
|
||||||
class GhCell extends Node {
|
|
||||||
GhCell(String token) {
|
|
||||||
super(GeohashPrefixTree.this, token);
|
|
||||||
}
|
|
||||||
|
|
||||||
GhCell(byte[] bytes, int off, int len) {
|
|
||||||
super(GeohashPrefixTree.this, bytes, off, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void reset(byte[] bytes, int off, int len) {
|
|
||||||
super.reset(bytes, off, len);
|
|
||||||
shape = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<Node> getSubCells() {
|
|
||||||
String[] hashes = GeohashUtils.getSubGeohashes(getGeohash());//sorted
|
|
||||||
List<Node> cells = new ArrayList<Node>(hashes.length);
|
|
||||||
for (String hash : hashes) {
|
|
||||||
cells.add(new GhCell(hash));
|
|
||||||
}
|
|
||||||
return cells;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getSubCellsSize() {
|
|
||||||
return 32;//8x4
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Node getSubCell(Point p) {
|
|
||||||
return GeohashPrefixTree.this.getNode(p, getLevel() + 1);//not performant!
|
|
||||||
}
|
|
||||||
|
|
||||||
private Shape shape;//cache
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Shape getShape() {
|
|
||||||
if (shape == null) {
|
|
||||||
shape = GeohashUtils.decodeBoundary(getGeohash(), ctx);
|
|
||||||
}
|
|
||||||
return shape;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Point getCenter() {
|
|
||||||
return GeohashUtils.decode(getGeohash(), ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getGeohash() {
|
|
||||||
return getTokenString();
|
|
||||||
}
|
|
||||||
|
|
||||||
}//class GhCell
|
|
||||||
|
|
||||||
}
|
|
@ -1,214 +0,0 @@
|
|||||||
/*
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
||||||
* contributor license agreements. See the NOTICE file distributed with
|
|
||||||
* this work for additional information regarding copyright ownership.
|
|
||||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
|
||||||
* (the "License"); you may not use this file except in compliance with
|
|
||||||
* the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.elasticsearch.common.lucene.spatial.prefix.tree;
|
|
||||||
|
|
||||||
import com.spatial4j.core.shape.Point;
|
|
||||||
import com.spatial4j.core.shape.Shape;
|
|
||||||
import com.spatial4j.core.shape.SpatialRelation;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a grid cell. These are not necessarily threadsafe, although new Cell("") (world cell) must be.
|
|
||||||
*
|
|
||||||
* @lucene.experimental
|
|
||||||
*/
|
|
||||||
public abstract class Node implements Comparable<Node> {
|
|
||||||
public static final byte LEAF_BYTE = '+';//NOTE: must sort before letters & numbers
|
|
||||||
|
|
||||||
/*
|
|
||||||
Holds a byte[] and/or String representation of the cell. Both are lazy constructed from the other.
|
|
||||||
Neither contains the trailing leaf byte.
|
|
||||||
*/
|
|
||||||
private byte[] bytes;
|
|
||||||
private int b_off;
|
|
||||||
private int b_len;
|
|
||||||
|
|
||||||
private String token;//this is the only part of equality
|
|
||||||
|
|
||||||
protected SpatialRelation shapeRel;//set in getSubCells(filter), and via setLeaf().
|
|
||||||
private SpatialPrefixTree spatialPrefixTree;
|
|
||||||
|
|
||||||
protected Node(SpatialPrefixTree spatialPrefixTree, String token) {
|
|
||||||
this.spatialPrefixTree = spatialPrefixTree;
|
|
||||||
this.token = token;
|
|
||||||
if (token.length() > 0 && token.charAt(token.length() - 1) == (char) LEAF_BYTE) {
|
|
||||||
this.token = token.substring(0, token.length() - 1);
|
|
||||||
setLeaf();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (getLevel() == 0)
|
|
||||||
getShape();//ensure any lazy instantiation completes to make this threadsafe
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Node(SpatialPrefixTree spatialPrefixTree, byte[] bytes, int off, int len) {
|
|
||||||
this.spatialPrefixTree = spatialPrefixTree;
|
|
||||||
this.bytes = bytes;
|
|
||||||
this.b_off = off;
|
|
||||||
this.b_len = len;
|
|
||||||
b_fixLeaf();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void reset(byte[] bytes, int off, int len) {
|
|
||||||
assert getLevel() != 0;
|
|
||||||
token = null;
|
|
||||||
shapeRel = null;
|
|
||||||
this.bytes = bytes;
|
|
||||||
this.b_off = off;
|
|
||||||
this.b_len = len;
|
|
||||||
b_fixLeaf();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void b_fixLeaf() {
|
|
||||||
if (bytes[b_off + b_len - 1] == LEAF_BYTE) {
|
|
||||||
b_len--;
|
|
||||||
setLeaf();
|
|
||||||
} else if (getLevel() == spatialPrefixTree.getMaxLevels()) {
|
|
||||||
setLeaf();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public SpatialRelation getShapeRel() {
|
|
||||||
return shapeRel;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isLeaf() {
|
|
||||||
return shapeRel == SpatialRelation.WITHIN;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLeaf() {
|
|
||||||
assert getLevel() != 0;
|
|
||||||
shapeRel = SpatialRelation.WITHIN;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Note: doesn't contain a trailing leaf byte.
|
|
||||||
*/
|
|
||||||
public String getTokenString() {
|
|
||||||
if (token == null) {
|
|
||||||
token = new String(bytes, b_off, b_len, SpatialPrefixTree.UTF8);
|
|
||||||
}
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Note: doesn't contain a trailing leaf byte.
|
|
||||||
*/
|
|
||||||
public byte[] getTokenBytes() {
|
|
||||||
if (bytes != null) {
|
|
||||||
if (b_off != 0 || b_len != bytes.length) {
|
|
||||||
throw new IllegalStateException("Not supported if byte[] needs to be recreated.");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
bytes = token.getBytes(SpatialPrefixTree.UTF8);
|
|
||||||
b_off = 0;
|
|
||||||
b_len = bytes.length;
|
|
||||||
}
|
|
||||||
return bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getLevel() {
|
|
||||||
return token != null ? token.length() : b_len;
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO add getParent() and update some algorithms to use this?
|
|
||||||
//public Cell getParent();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Like {@link #getSubCells()} but with the results filtered by a shape. If that shape is a {@link com.spatial4j.core.shape.Point} then it
|
|
||||||
* must call {@link #getSubCell(com.spatial4j.core.shape.Point)};
|
|
||||||
* Precondition: Never called when getLevel() == maxLevel.
|
|
||||||
*
|
|
||||||
* @param shapeFilter an optional filter for the returned cells.
|
|
||||||
* @return A set of cells (no dups), sorted. Not Modifiable.
|
|
||||||
*/
|
|
||||||
public Collection<Node> getSubCells(Shape shapeFilter) {
|
|
||||||
//Note: Higher-performing subclasses might override to consider the shape filter to generate fewer cells.
|
|
||||||
if (shapeFilter instanceof Point) {
|
|
||||||
return Collections.singleton(getSubCell((Point) shapeFilter));
|
|
||||||
}
|
|
||||||
Collection<Node> cells = getSubCells();
|
|
||||||
|
|
||||||
if (shapeFilter == null) {
|
|
||||||
return cells;
|
|
||||||
}
|
|
||||||
List<Node> copy = new ArrayList<Node>(cells.size());//copy since cells contractually isn't modifiable
|
|
||||||
for (Node cell : cells) {
|
|
||||||
SpatialRelation rel = cell.getShape().relate(shapeFilter);
|
|
||||||
if (rel == SpatialRelation.DISJOINT)
|
|
||||||
continue;
|
|
||||||
cell.shapeRel = rel;
|
|
||||||
copy.add(cell);
|
|
||||||
}
|
|
||||||
cells = copy;
|
|
||||||
return cells;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Performant implementations are expected to implement this efficiently by considering the current
|
|
||||||
* cell's boundary.
|
|
||||||
* Precondition: Never called when getLevel() == maxLevel.
|
|
||||||
* Precondition: this.getShape().relate(p) != DISJOINT.
|
|
||||||
*/
|
|
||||||
public abstract Node getSubCell(Point p);
|
|
||||||
|
|
||||||
//TODO Cell getSubCell(byte b)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the cells at the next grid cell level that cover this cell.
|
|
||||||
* Precondition: Never called when getLevel() == maxLevel.
|
|
||||||
*
|
|
||||||
* @return A set of cells (no dups), sorted. Not Modifiable.
|
|
||||||
*/
|
|
||||||
protected abstract Collection<Node> getSubCells();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@link #getSubCells()}.size() -- usually a constant. Should be >=2
|
|
||||||
*/
|
|
||||||
public abstract int getSubCellsSize();
|
|
||||||
|
|
||||||
public abstract Shape getShape();
|
|
||||||
|
|
||||||
public Point getCenter() {
|
|
||||||
return getShape().getCenter();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int compareTo(Node o) {
|
|
||||||
return getTokenString().compareTo(o.getTokenString());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
return !(obj == null || !(obj instanceof Node)) && getTokenString().equals(((Node) obj).getTokenString());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return getTokenString().hashCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return getTokenString() + (isLeaf() ? (char) LEAF_BYTE : "");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,288 +0,0 @@
|
|||||||
/*
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
||||||
* contributor license agreements. See the NOTICE file distributed with
|
|
||||||
* this work for additional information regarding copyright ownership.
|
|
||||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
|
||||||
* (the "License"); you may not use this file except in compliance with
|
|
||||||
* the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.elasticsearch.common.lucene.spatial.prefix.tree;
|
|
||||||
|
|
||||||
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 com.spatial4j.core.shape.SpatialRelation;
|
|
||||||
|
|
||||||
import java.io.PrintStream;
|
|
||||||
import java.text.NumberFormat;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @lucene.experimental
|
|
||||||
*/
|
|
||||||
public class QuadPrefixTree extends SpatialPrefixTree {
|
|
||||||
|
|
||||||
public static final int MAX_LEVELS_POSSIBLE = 50;//not really sure how big this should be
|
|
||||||
|
|
||||||
public static final int DEFAULT_MAX_LEVELS = 12;
|
|
||||||
private final double xmin;
|
|
||||||
private final double xmax;
|
|
||||||
private final double ymin;
|
|
||||||
private final double ymax;
|
|
||||||
private final double xmid;
|
|
||||||
private final double ymid;
|
|
||||||
|
|
||||||
private final double gridW;
|
|
||||||
public final double gridH;
|
|
||||||
|
|
||||||
final double[] levelW;
|
|
||||||
final double[] levelH;
|
|
||||||
final int[] levelS; // side
|
|
||||||
final int[] levelN; // number
|
|
||||||
|
|
||||||
public QuadPrefixTree(
|
|
||||||
SpatialContext ctx, Rectangle bounds, int maxLevels) {
|
|
||||||
super(ctx, maxLevels);
|
|
||||||
this.xmin = bounds.getMinX();
|
|
||||||
this.xmax = bounds.getMaxX();
|
|
||||||
this.ymin = bounds.getMinY();
|
|
||||||
this.ymax = bounds.getMaxY();
|
|
||||||
|
|
||||||
levelW = new double[maxLevels];
|
|
||||||
levelH = new double[maxLevels];
|
|
||||||
levelS = new int[maxLevels];
|
|
||||||
levelN = new int[maxLevels];
|
|
||||||
|
|
||||||
gridW = xmax - xmin;
|
|
||||||
gridH = ymax - ymin;
|
|
||||||
this.xmid = xmin + gridW / 2.0;
|
|
||||||
this.ymid = ymin + gridH / 2.0;
|
|
||||||
levelW[0] = gridW / 2.0;
|
|
||||||
levelH[0] = gridH / 2.0;
|
|
||||||
levelS[0] = 2;
|
|
||||||
levelN[0] = 4;
|
|
||||||
|
|
||||||
for (int i = 1; i < levelW.length; i++) {
|
|
||||||
levelW[i] = levelW[i - 1] / 2.0;
|
|
||||||
levelH[i] = levelH[i - 1] / 2.0;
|
|
||||||
levelS[i] = levelS[i - 1] * 2;
|
|
||||||
levelN[i] = levelN[i - 1] * 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public QuadPrefixTree(SpatialContext ctx) {
|
|
||||||
this(ctx, DEFAULT_MAX_LEVELS);
|
|
||||||
}
|
|
||||||
|
|
||||||
public QuadPrefixTree(
|
|
||||||
SpatialContext ctx, int maxLevels) {
|
|
||||||
this(ctx, ctx.getWorldBounds(), maxLevels);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void printInfo(PrintStream out) {
|
|
||||||
NumberFormat nf = NumberFormat.getNumberInstance(Locale.ROOT);
|
|
||||||
nf.setMaximumFractionDigits(5);
|
|
||||||
nf.setMinimumFractionDigits(5);
|
|
||||||
nf.setMinimumIntegerDigits(3);
|
|
||||||
|
|
||||||
for (int i = 0; i < maxLevels; i++) {
|
|
||||||
out.println(i + "]\t" + nf.format(levelW[i]) + "\t" + nf.format(levelH[i]) + "\t" +
|
|
||||||
levelS[i] + "\t" + (levelS[i] * levelS[i]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getLevelForDistance(double dist) {
|
|
||||||
if (dist == 0)//short circuit
|
|
||||||
return maxLevels;
|
|
||||||
for (int i = 0; i < maxLevels - 1; i++) {
|
|
||||||
//note: level[i] is actually a lookup for level i+1
|
|
||||||
if (dist > levelW[i] && dist > levelH[i]) {
|
|
||||||
return i + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return maxLevels;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Node getNode(Point p, int level) {
|
|
||||||
List<Node> cells = new ArrayList<Node>(1);
|
|
||||||
build(xmid, ymid, 0, cells, new StringBuilder(), ctx.makePoint(p.getX(), p.getY()), level);
|
|
||||||
return cells.get(0);//note cells could be longer if p on edge
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Node getNode(String token) {
|
|
||||||
return new QuadCell(token);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Node getNode(byte[] bytes, int offset, int len) {
|
|
||||||
return new QuadCell(bytes, offset, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override //for performance
|
|
||||||
public List<Node> getNodes(Shape shape, int detailLevel, boolean inclParents) {
|
|
||||||
if (shape instanceof Point)
|
|
||||||
return super.getNodesAltPoint((Point) shape, detailLevel, inclParents);
|
|
||||||
else
|
|
||||||
return super.getNodes(shape, detailLevel, inclParents);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void build(
|
|
||||||
double x,
|
|
||||||
double y,
|
|
||||||
int level,
|
|
||||||
List<Node> matches,
|
|
||||||
StringBuilder str,
|
|
||||||
Shape shape,
|
|
||||||
int maxLevel) {
|
|
||||||
assert str.length() == level;
|
|
||||||
double w = levelW[level] / 2;
|
|
||||||
double h = levelH[level] / 2;
|
|
||||||
|
|
||||||
// Z-Order
|
|
||||||
// http://en.wikipedia.org/wiki/Z-order_%28curve%29
|
|
||||||
checkBattenberg('A', x - w, y + h, level, matches, str, shape, maxLevel);
|
|
||||||
checkBattenberg('B', x + w, y + h, level, matches, str, shape, maxLevel);
|
|
||||||
checkBattenberg('C', x - w, y - h, level, matches, str, shape, maxLevel);
|
|
||||||
checkBattenberg('D', x + w, y - h, level, matches, str, shape, maxLevel);
|
|
||||||
|
|
||||||
// possibly consider hilbert curve
|
|
||||||
// http://en.wikipedia.org/wiki/Hilbert_curve
|
|
||||||
// http://blog.notdot.net/2009/11/Damn-Cool-Algorithms-Spatial-indexing-with-Quadtrees-and-Hilbert-Curves
|
|
||||||
// if we actually use the range property in the query, this could be useful
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkBattenberg(
|
|
||||||
char c,
|
|
||||||
double cx,
|
|
||||||
double cy,
|
|
||||||
int level,
|
|
||||||
List<Node> matches,
|
|
||||||
StringBuilder str,
|
|
||||||
Shape shape,
|
|
||||||
int maxLevel) {
|
|
||||||
assert str.length() == level;
|
|
||||||
double w = levelW[level] / 2;
|
|
||||||
double h = levelH[level] / 2;
|
|
||||||
|
|
||||||
int strlen = str.length();
|
|
||||||
Rectangle rectangle = ctx.makeRectangle(cx - w, cx + w, cy - h, cy + h);
|
|
||||||
SpatialRelation v = shape.relate(rectangle);
|
|
||||||
if (SpatialRelation.CONTAINS == v) {
|
|
||||||
str.append(c);
|
|
||||||
//str.append(SpatialPrefixGrid.COVER);
|
|
||||||
matches.add(new QuadCell(str.toString(), v.transpose()));
|
|
||||||
} else if (SpatialRelation.DISJOINT == v) {
|
|
||||||
// nothing
|
|
||||||
} else { // SpatialRelation.WITHIN, SpatialRelation.INTERSECTS
|
|
||||||
str.append(c);
|
|
||||||
|
|
||||||
int nextLevel = level + 1;
|
|
||||||
if (nextLevel >= maxLevel) {
|
|
||||||
//str.append(SpatialPrefixGrid.INTERSECTS);
|
|
||||||
matches.add(new QuadCell(str.toString(), v.transpose()));
|
|
||||||
} else {
|
|
||||||
build(cx, cy, nextLevel, matches, str, shape, maxLevel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
str.setLength(strlen);
|
|
||||||
}
|
|
||||||
|
|
||||||
class QuadCell extends Node {
|
|
||||||
|
|
||||||
public QuadCell(String token) {
|
|
||||||
super(QuadPrefixTree.this, token);
|
|
||||||
}
|
|
||||||
|
|
||||||
public QuadCell(String token, SpatialRelation shapeRel) {
|
|
||||||
super(QuadPrefixTree.this, token);
|
|
||||||
this.shapeRel = shapeRel;
|
|
||||||
}
|
|
||||||
|
|
||||||
QuadCell(byte[] bytes, int off, int len) {
|
|
||||||
super(QuadPrefixTree.this, bytes, off, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void reset(byte[] bytes, int off, int len) {
|
|
||||||
super.reset(bytes, off, len);
|
|
||||||
shape = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<Node> getSubCells() {
|
|
||||||
List<Node> cells = new ArrayList<Node>(4);
|
|
||||||
cells.add(new QuadCell(getTokenString() + "A"));
|
|
||||||
cells.add(new QuadCell(getTokenString() + "B"));
|
|
||||||
cells.add(new QuadCell(getTokenString() + "C"));
|
|
||||||
cells.add(new QuadCell(getTokenString() + "D"));
|
|
||||||
return cells;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getSubCellsSize() {
|
|
||||||
return 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Node getSubCell(Point p) {
|
|
||||||
return QuadPrefixTree.this.getNode(p, getLevel() + 1);//not performant!
|
|
||||||
}
|
|
||||||
|
|
||||||
private Shape shape;//cache
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Shape getShape() {
|
|
||||||
if (shape == null)
|
|
||||||
shape = makeShape();
|
|
||||||
return shape;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Rectangle makeShape() {
|
|
||||||
String token = getTokenString();
|
|
||||||
double xmin = QuadPrefixTree.this.xmin;
|
|
||||||
double ymin = QuadPrefixTree.this.ymin;
|
|
||||||
|
|
||||||
for (int i = 0; i < token.length(); i++) {
|
|
||||||
char c = token.charAt(i);
|
|
||||||
if ('A' == c || 'a' == c) {
|
|
||||||
ymin += levelH[i];
|
|
||||||
} else if ('B' == c || 'b' == c) {
|
|
||||||
xmin += levelW[i];
|
|
||||||
ymin += levelH[i];
|
|
||||||
} else if ('C' == c || 'c' == c) {
|
|
||||||
// nothing really
|
|
||||||
} else if ('D' == c || 'd' == c) {
|
|
||||||
xmin += levelW[i];
|
|
||||||
} else {
|
|
||||||
throw new RuntimeException("unexpected char: " + c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
int len = token.length();
|
|
||||||
double width, height;
|
|
||||||
if (len > 0) {
|
|
||||||
width = levelW[len - 1];
|
|
||||||
height = levelH[len - 1];
|
|
||||||
} else {
|
|
||||||
width = gridW;
|
|
||||||
height = gridH;
|
|
||||||
}
|
|
||||||
return ctx.makeRectangle(xmin, xmin + width, ymin, ymin + height);
|
|
||||||
}
|
|
||||||
}//QuadCell
|
|
||||||
}
|
|
@ -1,231 +0,0 @@
|
|||||||
/*
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
||||||
* contributor license agreements. See the NOTICE file distributed with
|
|
||||||
* this work for additional information regarding copyright ownership.
|
|
||||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
|
||||||
* (the "License"); you may not use this file except in compliance with
|
|
||||||
* the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.elasticsearch.common.lucene.spatial.prefix.tree;
|
|
||||||
|
|
||||||
import com.spatial4j.core.context.SpatialContext;
|
|
||||||
import com.spatial4j.core.shape.Point;
|
|
||||||
import com.spatial4j.core.shape.Shape;
|
|
||||||
|
|
||||||
import java.nio.charset.Charset;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A spatial Prefix Tree, or Trie, which decomposes shapes into prefixed strings at variable lengths corresponding to
|
|
||||||
* variable precision. Each string corresponds to a spatial region.
|
|
||||||
* <p/>
|
|
||||||
* Implementations of this class should be thread-safe and immutable once initialized.
|
|
||||||
*
|
|
||||||
* @lucene.experimental
|
|
||||||
*/
|
|
||||||
public abstract class SpatialPrefixTree {
|
|
||||||
|
|
||||||
protected static final Charset UTF8 = Charset.forName("UTF-8");
|
|
||||||
|
|
||||||
protected final int maxLevels;
|
|
||||||
|
|
||||||
protected final SpatialContext ctx;
|
|
||||||
|
|
||||||
public SpatialPrefixTree(SpatialContext ctx, int maxLevels) {
|
|
||||||
assert maxLevels > 0;
|
|
||||||
this.ctx = ctx;
|
|
||||||
this.maxLevels = maxLevels;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SpatialContext getSpatialContext() {
|
|
||||||
return ctx;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getMaxLevels() {
|
|
||||||
return maxLevels;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return getClass().getSimpleName() + "(maxLevels:" + maxLevels + ",ctx:" + ctx + ")";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the level of the largest grid in which its longest side is less
|
|
||||||
* than or equal to the provided distance (in degrees). Consequently {@code
|
|
||||||
* dist} acts as an error epsilon declaring the amount of detail needed in the
|
|
||||||
* grid, such that you can get a grid with just the right amount of
|
|
||||||
* precision.
|
|
||||||
*
|
|
||||||
* @param dist >= 0
|
|
||||||
* @return level [1 to maxLevels]
|
|
||||||
*/
|
|
||||||
public abstract int getLevelForDistance(double dist);
|
|
||||||
|
|
||||||
//TODO double getDistanceForLevel(int level)
|
|
||||||
|
|
||||||
private transient Node worldNode;//cached
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the level 0 cell which encompasses all spatial data. Equivalent to {@link #getNode(String)} with "".
|
|
||||||
* This cell is threadsafe, just like a spatial prefix grid is, although cells aren't
|
|
||||||
* generally threadsafe.
|
|
||||||
* TODO rename to getTopCell or is this fine?
|
|
||||||
*/
|
|
||||||
public Node getWorldNode() {
|
|
||||||
if (worldNode == null) {
|
|
||||||
worldNode = getNode("");
|
|
||||||
}
|
|
||||||
return worldNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The cell for the specified token. The empty string should be equal to {@link #getWorldNode()}.
|
|
||||||
* Precondition: Never called when token length > maxLevel.
|
|
||||||
*/
|
|
||||||
public abstract Node getNode(String token);
|
|
||||||
|
|
||||||
public abstract Node getNode(byte[] bytes, int offset, int len);
|
|
||||||
|
|
||||||
public final Node getNode(byte[] bytes, int offset, int len, Node target) {
|
|
||||||
if (target == null) {
|
|
||||||
return getNode(bytes, offset, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
target.reset(bytes, offset, len);
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Node getNode(Point p, int level) {
|
|
||||||
return getNodes(p, level, false).get(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the intersecting & including cells for the specified shape, without exceeding detail level.
|
|
||||||
* The result is a set of cells (no dups), sorted. Unmodifiable.
|
|
||||||
* <p/>
|
|
||||||
* This implementation checks if shape is a Point and if so uses an implementation that
|
|
||||||
* recursively calls {@link Node#getSubCell(com.spatial4j.core.shape.Point)}. Cell subclasses
|
|
||||||
* ideally implement that method with a quick implementation, otherwise, subclasses should
|
|
||||||
* override this method to invoke {@link #getNodesAltPoint(com.spatial4j.core.shape.Point, int, boolean)}.
|
|
||||||
* TODO consider another approach returning an iterator -- won't build up all cells in memory.
|
|
||||||
*/
|
|
||||||
public List<Node> getNodes(Shape shape, int detailLevel, boolean inclParents) {
|
|
||||||
if (detailLevel > maxLevels) {
|
|
||||||
throw new IllegalArgumentException("detailLevel > maxLevels");
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Node> cells;
|
|
||||||
if (shape instanceof Point) {
|
|
||||||
//optimized point algorithm
|
|
||||||
final int initialCapacity = inclParents ? 1 + detailLevel : 1;
|
|
||||||
cells = new ArrayList<Node>(initialCapacity);
|
|
||||||
recursiveGetNodes(getWorldNode(), (Point) shape, detailLevel, true, cells);
|
|
||||||
assert cells.size() == initialCapacity;
|
|
||||||
} else {
|
|
||||||
cells = new ArrayList<Node>(inclParents ? 1024 : 512);
|
|
||||||
recursiveGetNodes(getWorldNode(), shape, detailLevel, inclParents, cells);
|
|
||||||
}
|
|
||||||
if (inclParents) {
|
|
||||||
Node c = cells.remove(0);//remove getWorldNode()
|
|
||||||
assert c.getLevel() == 0;
|
|
||||||
}
|
|
||||||
return cells;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void recursiveGetNodes(Node node, Shape shape, int detailLevel, boolean inclParents,
|
|
||||||
Collection<Node> result) {
|
|
||||||
if (node.isLeaf()) {//cell is within shape
|
|
||||||
result.add(node);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final Collection<Node> subCells = node.getSubCells(shape);
|
|
||||||
if (node.getLevel() == detailLevel - 1) {
|
|
||||||
if (subCells.size() == node.getSubCellsSize() && !inclParents) {
|
|
||||||
// A bottom level (i.e. detail level) optimization where all boxes intersect, so use parent cell.
|
|
||||||
// Can optimize at only one of index time or query/filter time; the !inclParents
|
|
||||||
// condition above means we do not optimize at index time.
|
|
||||||
node.setLeaf();
|
|
||||||
result.add(node);
|
|
||||||
} else {
|
|
||||||
if (inclParents)
|
|
||||||
result.add(node);
|
|
||||||
for (Node subCell : subCells) {
|
|
||||||
subCell.setLeaf();
|
|
||||||
}
|
|
||||||
result.addAll(subCells);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (inclParents) {
|
|
||||||
result.add(node);
|
|
||||||
}
|
|
||||||
for (Node subCell : subCells) {
|
|
||||||
recursiveGetNodes(subCell, shape, detailLevel, inclParents, result);//tail call
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void recursiveGetNodes(Node node, Point point, int detailLevel, boolean inclParents,
|
|
||||||
Collection<Node> result) {
|
|
||||||
if (inclParents) {
|
|
||||||
result.add(node);
|
|
||||||
}
|
|
||||||
final Node pCell = node.getSubCell(point);
|
|
||||||
if (node.getLevel() == detailLevel - 1) {
|
|
||||||
pCell.setLeaf();
|
|
||||||
result.add(pCell);
|
|
||||||
} else {
|
|
||||||
recursiveGetNodes(pCell, point, detailLevel, inclParents, result);//tail call
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Subclasses might override {@link #getNodes(com.spatial4j.core.shape.Shape, int, boolean)}
|
|
||||||
* and check if the argument is a shape and if so, delegate
|
|
||||||
* to this implementation, which calls {@link #getNode(com.spatial4j.core.shape.Point, int)} and
|
|
||||||
* then calls {@link #getNode(String)} repeatedly if inclParents is true.
|
|
||||||
*/
|
|
||||||
protected final List<Node> getNodesAltPoint(Point p, int detailLevel, boolean inclParents) {
|
|
||||||
Node cell = getNode(p, detailLevel);
|
|
||||||
if (!inclParents) {
|
|
||||||
return Collections.singletonList(cell);
|
|
||||||
}
|
|
||||||
|
|
||||||
String endToken = cell.getTokenString();
|
|
||||||
assert endToken.length() == detailLevel;
|
|
||||||
List<Node> cells = new ArrayList<Node>(detailLevel);
|
|
||||||
for (int i = 1; i < detailLevel; i++) {
|
|
||||||
cells.add(getNode(endToken.substring(0, i)));
|
|
||||||
}
|
|
||||||
cells.add(cell);
|
|
||||||
return cells;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Will add the trailing leaf byte for leaves. This isn't particularly efficient.
|
|
||||||
*/
|
|
||||||
public static List<String> nodesToTokenStrings(Collection<Node> nodes) {
|
|
||||||
List<String> tokens = new ArrayList<String>((nodes.size()));
|
|
||||||
for (Node node : nodes) {
|
|
||||||
final String token = node.getTokenString();
|
|
||||||
if (node.isLeaf()) {
|
|
||||||
tokens.add(token + (char) Node.LEAF_BYTE);
|
|
||||||
} else {
|
|
||||||
tokens.add(token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return tokens;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,17 +1,22 @@
|
|||||||
package org.elasticsearch.index.mapper.geo;
|
package org.elasticsearch.index.mapper.geo;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import org.apache.lucene.document.Field;
|
import org.apache.lucene.document.Field;
|
||||||
import org.apache.lucene.document.FieldType;
|
import org.apache.lucene.document.FieldType;
|
||||||
import org.apache.lucene.index.FieldInfo;
|
import org.apache.lucene.index.FieldInfo;
|
||||||
|
import org.apache.lucene.spatial.SpatialStrategy;
|
||||||
|
import org.apache.lucene.spatial.prefix.PrefixTreeStrategy;
|
||||||
|
import org.apache.lucene.spatial.prefix.TermQueryPrefixTreeStrategy;
|
||||||
|
import org.apache.lucene.spatial.prefix.tree.GeohashPrefixTree;
|
||||||
|
import org.apache.lucene.spatial.prefix.tree.QuadPrefixTree;
|
||||||
|
import org.apache.lucene.spatial.prefix.tree.SpatialPrefixTree;
|
||||||
import org.elasticsearch.ElasticSearchIllegalArgumentException;
|
import org.elasticsearch.ElasticSearchIllegalArgumentException;
|
||||||
import org.elasticsearch.common.Strings;
|
import org.elasticsearch.common.Strings;
|
||||||
import org.elasticsearch.common.geo.GeoJSONShapeParser;
|
import org.elasticsearch.common.geo.GeoJSONShapeParser;
|
||||||
import org.elasticsearch.common.geo.GeoShapeConstants;
|
import org.elasticsearch.common.geo.GeoShapeConstants;
|
||||||
import org.elasticsearch.common.lucene.spatial.SpatialStrategy;
|
import org.elasticsearch.common.lucene.spatial.XTermQueryPrefixTreeStategy;
|
||||||
import org.elasticsearch.common.lucene.spatial.prefix.TermQueryPrefixTreeStrategy;
|
|
||||||
import org.elasticsearch.common.lucene.spatial.prefix.tree.GeohashPrefixTree;
|
|
||||||
import org.elasticsearch.common.lucene.spatial.prefix.tree.QuadPrefixTree;
|
|
||||||
import org.elasticsearch.common.lucene.spatial.prefix.tree.SpatialPrefixTree;
|
|
||||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||||
import org.elasticsearch.index.codec.postingsformat.PostingsFormatProvider;
|
import org.elasticsearch.index.codec.postingsformat.PostingsFormatProvider;
|
||||||
import org.elasticsearch.index.fielddata.FieldDataType;
|
import org.elasticsearch.index.fielddata.FieldDataType;
|
||||||
@ -21,9 +26,6 @@ import org.elasticsearch.index.mapper.MapperParsingException;
|
|||||||
import org.elasticsearch.index.mapper.ParseContext;
|
import org.elasticsearch.index.mapper.ParseContext;
|
||||||
import org.elasticsearch.index.mapper.core.AbstractFieldMapper;
|
import org.elasticsearch.index.mapper.core.AbstractFieldMapper;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FieldMapper for indexing {@link com.spatial4j.core.shape.Shape}s.
|
* FieldMapper for indexing {@link com.spatial4j.core.shape.Shape}s.
|
||||||
* <p/>
|
* <p/>
|
||||||
@ -135,12 +137,13 @@ public class GeoShapeFieldMapper extends AbstractFieldMapper<String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final SpatialStrategy spatialStrategy;
|
private final PrefixTreeStrategy spatialStrategy;
|
||||||
|
|
||||||
public GeoShapeFieldMapper(FieldMapper.Names names, SpatialPrefixTree prefixTree, double distanceErrorPct,
|
public GeoShapeFieldMapper(FieldMapper.Names names, SpatialPrefixTree prefixTree, double distanceErrorPct,
|
||||||
FieldType fieldType, PostingsFormatProvider provider) {
|
FieldType fieldType, PostingsFormatProvider provider) {
|
||||||
super(names, 1, fieldType, null, null, provider, null, null);
|
super(names, 1, fieldType, null, null, provider, null, null);
|
||||||
this.spatialStrategy = new TermQueryPrefixTreeStrategy(names, prefixTree, distanceErrorPct);
|
this.spatialStrategy = new XTermQueryPrefixTreeStategy(prefixTree, names);
|
||||||
|
this.spatialStrategy.setDistErrPct(distanceErrorPct);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -152,10 +155,30 @@ public class GeoShapeFieldMapper extends AbstractFieldMapper<String> {
|
|||||||
public FieldDataType defaultFieldDataType() {
|
public FieldDataType defaultFieldDataType() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void parse(ParseContext context) throws IOException {
|
||||||
|
try {
|
||||||
|
Field[] fields = spatialStrategy.createIndexableFields(GeoJSONShapeParser.parse(context.parser()));
|
||||||
|
if (fields == null || fields.length == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (Field field : fields) {
|
||||||
|
if (!customBoost()) {
|
||||||
|
field.setBoost(boost);
|
||||||
|
}
|
||||||
|
if (context.listener().beforeFieldAdded(this, field, context)) {
|
||||||
|
context.doc().add(field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new MapperParsingException("failed to parse [" + names.fullName() + "]", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Field parseCreateField(ParseContext context) throws IOException {
|
protected Field parseCreateField(ParseContext context) throws IOException {
|
||||||
return spatialStrategy.createField(GeoJSONShapeParser.parse(context.parser()));
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -163,21 +186,21 @@ public class GeoShapeFieldMapper extends AbstractFieldMapper<String> {
|
|||||||
builder.field("type", contentType());
|
builder.field("type", contentType());
|
||||||
|
|
||||||
// TODO: Come up with a better way to get the name, maybe pass it from builder
|
// TODO: Come up with a better way to get the name, maybe pass it from builder
|
||||||
if (spatialStrategy.getPrefixTree() instanceof GeohashPrefixTree) {
|
if (spatialStrategy.getGrid() instanceof GeohashPrefixTree) {
|
||||||
// Don't emit the tree name since GeohashPrefixTree is the default
|
// Don't emit the tree name since GeohashPrefixTree is the default
|
||||||
// Only emit the tree levels if it isn't the default value
|
// Only emit the tree levels if it isn't the default value
|
||||||
if (spatialStrategy.getPrefixTree().getMaxLevels() != Defaults.GEOHASH_LEVELS) {
|
if (spatialStrategy.getGrid().getMaxLevels() != Defaults.GEOHASH_LEVELS) {
|
||||||
builder.field(Names.TREE_LEVELS, spatialStrategy.getPrefixTree().getMaxLevels());
|
builder.field(Names.TREE_LEVELS, spatialStrategy.getGrid().getMaxLevels());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
builder.field(Names.TREE, Names.QUADTREE);
|
builder.field(Names.TREE, Names.QUADTREE);
|
||||||
if (spatialStrategy.getPrefixTree().getMaxLevels() != Defaults.QUADTREE_LEVELS) {
|
if (spatialStrategy.getGrid().getMaxLevels() != Defaults.QUADTREE_LEVELS) {
|
||||||
builder.field(Names.TREE_LEVELS, spatialStrategy.getPrefixTree().getMaxLevels());
|
builder.field(Names.TREE_LEVELS, spatialStrategy.getGrid().getMaxLevels());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (spatialStrategy.getDistanceErrorPct() != Defaults.DISTANCE_ERROR_PCT) {
|
if (spatialStrategy.getDistErrPct() != Defaults.DISTANCE_ERROR_PCT) {
|
||||||
builder.field(Names.DISTANCE_ERROR_PCT, spatialStrategy.getDistanceErrorPct());
|
builder.field(Names.DISTANCE_ERROR_PCT, spatialStrategy.getDistErrPct());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -191,7 +214,7 @@ public class GeoShapeFieldMapper extends AbstractFieldMapper<String> {
|
|||||||
throw new UnsupportedOperationException("GeoShape fields cannot be converted to String values");
|
throw new UnsupportedOperationException("GeoShape fields cannot be converted to String values");
|
||||||
}
|
}
|
||||||
|
|
||||||
public SpatialStrategy spatialStrategy() {
|
public PrefixTreeStrategy spatialStrategy() {
|
||||||
return this.spatialStrategy;
|
return this.spatialStrategy;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,8 @@ package org.elasticsearch.index.query;
|
|||||||
|
|
||||||
import com.spatial4j.core.shape.Shape;
|
import com.spatial4j.core.shape.Shape;
|
||||||
import org.apache.lucene.search.Filter;
|
import org.apache.lucene.search.Filter;
|
||||||
|
import org.apache.lucene.spatial.query.SpatialArgs;
|
||||||
|
import org.apache.lucene.spatial.query.SpatialOperation;
|
||||||
import org.elasticsearch.common.geo.GeoJSONShapeParser;
|
import org.elasticsearch.common.geo.GeoJSONShapeParser;
|
||||||
import org.elasticsearch.common.geo.ShapeRelation;
|
import org.elasticsearch.common.geo.ShapeRelation;
|
||||||
import org.elasticsearch.common.inject.Inject;
|
import org.elasticsearch.common.inject.Inject;
|
||||||
@ -163,7 +165,7 @@ public class GeoShapeFilterParser implements FilterParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
GeoShapeFieldMapper shapeFieldMapper = (GeoShapeFieldMapper) fieldMapper;
|
GeoShapeFieldMapper shapeFieldMapper = (GeoShapeFieldMapper) fieldMapper;
|
||||||
Filter filter = shapeFieldMapper.spatialStrategy().createFilter(shape, shapeRelation);
|
Filter filter = shapeFieldMapper.spatialStrategy().makeFilter(GeoShapeQueryParser.getArgs(shape, shapeRelation));
|
||||||
|
|
||||||
if (cache) {
|
if (cache) {
|
||||||
filter = parseContext.cacheFilter(filter, cacheKey);
|
filter = parseContext.cacheFilter(filter, cacheKey);
|
||||||
|
@ -21,6 +21,9 @@ package org.elasticsearch.index.query;
|
|||||||
|
|
||||||
import com.spatial4j.core.shape.Shape;
|
import com.spatial4j.core.shape.Shape;
|
||||||
import org.apache.lucene.search.Query;
|
import org.apache.lucene.search.Query;
|
||||||
|
import org.apache.lucene.spatial.query.SpatialArgs;
|
||||||
|
import org.apache.lucene.spatial.query.SpatialOperation;
|
||||||
|
import org.elasticsearch.ElasticSearchIllegalArgumentException;
|
||||||
import org.elasticsearch.common.Nullable;
|
import org.elasticsearch.common.Nullable;
|
||||||
import org.elasticsearch.common.Strings;
|
import org.elasticsearch.common.Strings;
|
||||||
import org.elasticsearch.common.geo.GeoJSONShapeParser;
|
import org.elasticsearch.common.geo.GeoJSONShapeParser;
|
||||||
@ -135,7 +138,7 @@ public class GeoShapeQueryParser implements QueryParser {
|
|||||||
|
|
||||||
GeoShapeFieldMapper shapeFieldMapper = (GeoShapeFieldMapper) fieldMapper;
|
GeoShapeFieldMapper shapeFieldMapper = (GeoShapeFieldMapper) fieldMapper;
|
||||||
|
|
||||||
Query query = shapeFieldMapper.spatialStrategy().createQuery(shape, shapeRelation);
|
Query query = shapeFieldMapper.spatialStrategy().makeQuery(getArgs(shape, shapeRelation));
|
||||||
query.setBoost(boost);
|
query.setBoost(boost);
|
||||||
return query;
|
return query;
|
||||||
}
|
}
|
||||||
@ -144,4 +147,18 @@ public class GeoShapeQueryParser implements QueryParser {
|
|||||||
public void setFetchService(@Nullable ShapeFetchService fetchService) {
|
public void setFetchService(@Nullable ShapeFetchService fetchService) {
|
||||||
this.fetchService = fetchService;
|
this.fetchService = fetchService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static SpatialArgs getArgs(Shape shape, ShapeRelation relation) {
|
||||||
|
switch(relation) {
|
||||||
|
case DISJOINT:
|
||||||
|
return new SpatialArgs(SpatialOperation.IsDisjointTo, shape);
|
||||||
|
case INTERSECTS:
|
||||||
|
return new SpatialArgs(SpatialOperation.Intersects, shape);
|
||||||
|
case WITHIN:
|
||||||
|
return new SpatialArgs(SpatialOperation.IsWithin, shape);
|
||||||
|
default:
|
||||||
|
throw new ElasticSearchIllegalArgumentException("");
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -110,7 +110,7 @@ public class GeoShapeIntegrationTests extends AbstractNodesTests {
|
|||||||
assertThat(searchResponse.getHits().getAt(0).id(), equalTo("1"));
|
assertThat(searchResponse.getHits().getAt(0).id(), equalTo("1"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test(enabled=false) // LUCENE MONITIR enable this test again once Lucene4.2 is out. This bug is fixed in Lucene 4.2
|
||||||
public void testEdgeCases() throws Exception {
|
public void testEdgeCases() throws Exception {
|
||||||
client.admin().indices().prepareDelete().execute().actionGet();
|
client.admin().indices().prepareDelete().execute().actionGet();
|
||||||
|
|
||||||
@ -147,9 +147,9 @@ public class GeoShapeIntegrationTests extends AbstractNodesTests {
|
|||||||
geoShapeFilter("location", query).relation(ShapeRelation.INTERSECTS)))
|
geoShapeFilter("location", query).relation(ShapeRelation.INTERSECTS)))
|
||||||
.execute().actionGet();
|
.execute().actionGet();
|
||||||
|
|
||||||
assertThat(searchResponse.hits().getTotalHits(), equalTo(1l));
|
assertThat(searchResponse.getHits().getTotalHits(), equalTo(1l));
|
||||||
assertThat(searchResponse.hits().hits().length, equalTo(1));
|
assertThat(searchResponse.getHits().hits().length, equalTo(1));
|
||||||
assertThat(searchResponse.hits().getAt(0).id(), equalTo("blakely"));
|
assertThat(searchResponse.getHits().getAt(0).id(), equalTo("blakely"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -1,7 +1,14 @@
|
|||||||
package org.elasticsearch.test.unit.common.lucene.spatial.prefix;
|
package org.elasticsearch.test.unit.common.lucene.spatial.prefix;
|
||||||
|
|
||||||
import com.spatial4j.core.shape.Rectangle;
|
import static org.elasticsearch.common.geo.ShapeBuilder.newPoint;
|
||||||
import com.spatial4j.core.shape.Shape;
|
import static org.elasticsearch.common.geo.ShapeBuilder.newPolygon;
|
||||||
|
import static org.elasticsearch.common.geo.ShapeBuilder.newRectangle;
|
||||||
|
import static org.testng.Assert.assertTrue;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import org.apache.lucene.analysis.core.KeywordAnalyzer;
|
import org.apache.lucene.analysis.core.KeywordAnalyzer;
|
||||||
import org.apache.lucene.document.Document;
|
import org.apache.lucene.document.Document;
|
||||||
import org.apache.lucene.document.Field;
|
import org.apache.lucene.document.Field;
|
||||||
@ -9,27 +16,29 @@ import org.apache.lucene.document.StringField;
|
|||||||
import org.apache.lucene.index.IndexReader;
|
import org.apache.lucene.index.IndexReader;
|
||||||
import org.apache.lucene.index.IndexWriter;
|
import org.apache.lucene.index.IndexWriter;
|
||||||
import org.apache.lucene.index.IndexWriterConfig;
|
import org.apache.lucene.index.IndexWriterConfig;
|
||||||
import org.apache.lucene.search.*;
|
import org.apache.lucene.search.Filter;
|
||||||
|
import org.apache.lucene.search.IndexSearcher;
|
||||||
|
import org.apache.lucene.search.MatchAllDocsQuery;
|
||||||
|
import org.apache.lucene.search.Query;
|
||||||
|
import org.apache.lucene.search.ScoreDoc;
|
||||||
|
import org.apache.lucene.search.TopDocs;
|
||||||
|
import org.apache.lucene.spatial.prefix.TermQueryPrefixTreeStrategy;
|
||||||
|
import org.apache.lucene.spatial.prefix.tree.GeohashPrefixTree;
|
||||||
|
import org.apache.lucene.spatial.prefix.tree.QuadPrefixTree;
|
||||||
|
import org.apache.lucene.spatial.prefix.tree.SpatialPrefixTree;
|
||||||
import org.apache.lucene.store.Directory;
|
import org.apache.lucene.store.Directory;
|
||||||
import org.apache.lucene.store.RAMDirectory;
|
import org.apache.lucene.store.RAMDirectory;
|
||||||
import org.apache.lucene.util.IOUtils;
|
import org.apache.lucene.util.IOUtils;
|
||||||
import org.apache.lucene.util.Version;
|
import org.apache.lucene.util.Version;
|
||||||
import org.elasticsearch.common.geo.GeoShapeConstants;
|
import org.elasticsearch.common.geo.GeoShapeConstants;
|
||||||
import org.elasticsearch.common.lucene.spatial.prefix.TermQueryPrefixTreeStrategy;
|
import org.elasticsearch.common.lucene.spatial.XTermQueryPrefixTreeStategy;
|
||||||
import org.elasticsearch.common.lucene.spatial.prefix.tree.GeohashPrefixTree;
|
|
||||||
import org.elasticsearch.common.lucene.spatial.prefix.tree.QuadPrefixTree;
|
|
||||||
import org.elasticsearch.common.lucene.spatial.prefix.tree.SpatialPrefixTree;
|
|
||||||
import org.elasticsearch.index.mapper.FieldMapper;
|
import org.elasticsearch.index.mapper.FieldMapper;
|
||||||
import org.testng.annotations.AfterTest;
|
import org.testng.annotations.AfterTest;
|
||||||
import org.testng.annotations.BeforeTest;
|
import org.testng.annotations.BeforeTest;
|
||||||
import org.testng.annotations.Test;
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
import java.io.IOException;
|
import com.spatial4j.core.shape.Rectangle;
|
||||||
import java.util.HashSet;
|
import com.spatial4j.core.shape.Shape;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import static org.elasticsearch.common.geo.ShapeBuilder.*;
|
|
||||||
import static org.testng.Assert.assertTrue;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link TermQueryPrefixTreeStrategy}
|
* Tests for {@link TermQueryPrefixTreeStrategy}
|
||||||
@ -43,8 +52,7 @@ public class TermQueryPrefixTreeStrategyTests {
|
|||||||
private static final SpatialPrefixTree GEOHASH_PREFIX_TREE
|
private static final SpatialPrefixTree GEOHASH_PREFIX_TREE
|
||||||
= new GeohashPrefixTree(GeoShapeConstants.SPATIAL_CONTEXT, GeohashPrefixTree.getMaxLevelsPossible());
|
= new GeohashPrefixTree(GeoShapeConstants.SPATIAL_CONTEXT, GeohashPrefixTree.getMaxLevelsPossible());
|
||||||
|
|
||||||
private static final TermQueryPrefixTreeStrategy STRATEGY = new TermQueryPrefixTreeStrategy(new FieldMapper.Names("shape"), GEOHASH_PREFIX_TREE, 0.025);
|
private static final XTermQueryPrefixTreeStategy STRATEGY = new XTermQueryPrefixTreeStategy(GEOHASH_PREFIX_TREE, new FieldMapper.Names("shape"));
|
||||||
|
|
||||||
private Directory directory;
|
private Directory directory;
|
||||||
private IndexReader indexReader;
|
private IndexReader indexReader;
|
||||||
private IndexSearcher indexSearcher;
|
private IndexSearcher indexSearcher;
|
||||||
@ -66,7 +74,10 @@ public class TermQueryPrefixTreeStrategyTests {
|
|||||||
private Document newDocument(String id, Shape shape) {
|
private Document newDocument(String id, Shape shape) {
|
||||||
Document document = new Document();
|
Document document = new Document();
|
||||||
document.add(new Field("id", id, StringField.TYPE_STORED));
|
document.add(new Field("id", id, StringField.TYPE_STORED));
|
||||||
document.add(STRATEGY.createField(shape));
|
Field[] createIndexableFields = STRATEGY.createIndexableFields(shape);
|
||||||
|
for (Field field : createIndexableFields) {
|
||||||
|
document.add(field);
|
||||||
|
}
|
||||||
return document;
|
return document;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,15 @@
|
|||||||
package org.elasticsearch.test.unit.index.mapper.geo;
|
package org.elasticsearch.test.unit.index.mapper.geo;
|
||||||
|
|
||||||
import org.elasticsearch.common.lucene.spatial.SpatialStrategy;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import org.elasticsearch.common.lucene.spatial.prefix.tree.GeohashPrefixTree;
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
import org.elasticsearch.common.lucene.spatial.prefix.tree.QuadPrefixTree;
|
import static org.hamcrest.Matchers.instanceOf;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.apache.lucene.spatial.SpatialStrategy;
|
||||||
|
import org.apache.lucene.spatial.prefix.PrefixTreeStrategy;
|
||||||
|
import org.apache.lucene.spatial.prefix.tree.GeohashPrefixTree;
|
||||||
|
import org.apache.lucene.spatial.prefix.tree.QuadPrefixTree;
|
||||||
import org.elasticsearch.common.xcontent.XContentFactory;
|
import org.elasticsearch.common.xcontent.XContentFactory;
|
||||||
import org.elasticsearch.index.mapper.DocumentMapper;
|
import org.elasticsearch.index.mapper.DocumentMapper;
|
||||||
import org.elasticsearch.index.mapper.FieldMapper;
|
import org.elasticsearch.index.mapper.FieldMapper;
|
||||||
@ -10,12 +17,6 @@ import org.elasticsearch.index.mapper.geo.GeoShapeFieldMapper;
|
|||||||
import org.elasticsearch.test.unit.index.mapper.MapperTests;
|
import org.elasticsearch.test.unit.index.mapper.MapperTests;
|
||||||
import org.testng.annotations.Test;
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
|
||||||
import static org.hamcrest.Matchers.instanceOf;
|
|
||||||
|
|
||||||
public class GeoShapeFieldMapperTests {
|
public class GeoShapeFieldMapperTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -31,11 +32,11 @@ public class GeoShapeFieldMapperTests {
|
|||||||
assertThat(fieldMapper, instanceOf(GeoShapeFieldMapper.class));
|
assertThat(fieldMapper, instanceOf(GeoShapeFieldMapper.class));
|
||||||
|
|
||||||
GeoShapeFieldMapper geoShapeFieldMapper = (GeoShapeFieldMapper) fieldMapper;
|
GeoShapeFieldMapper geoShapeFieldMapper = (GeoShapeFieldMapper) fieldMapper;
|
||||||
SpatialStrategy strategy = geoShapeFieldMapper.spatialStrategy();
|
PrefixTreeStrategy strategy = geoShapeFieldMapper.spatialStrategy();
|
||||||
|
|
||||||
assertThat(strategy.getDistanceErrorPct(), equalTo(GeoShapeFieldMapper.Defaults.DISTANCE_ERROR_PCT));
|
assertThat(strategy.getDistErrPct(), equalTo(GeoShapeFieldMapper.Defaults.DISTANCE_ERROR_PCT));
|
||||||
assertThat(strategy.getPrefixTree(), instanceOf(GeohashPrefixTree.class));
|
assertThat(strategy.getGrid(), instanceOf(GeohashPrefixTree.class));
|
||||||
assertThat(strategy.getPrefixTree().getMaxLevels(), equalTo(GeoShapeFieldMapper.Defaults.GEOHASH_LEVELS));
|
assertThat(strategy.getGrid().getMaxLevels(), equalTo(GeoShapeFieldMapper.Defaults.GEOHASH_LEVELS));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -54,11 +55,11 @@ public class GeoShapeFieldMapperTests {
|
|||||||
assertThat(fieldMapper, instanceOf(GeoShapeFieldMapper.class));
|
assertThat(fieldMapper, instanceOf(GeoShapeFieldMapper.class));
|
||||||
|
|
||||||
GeoShapeFieldMapper geoShapeFieldMapper = (GeoShapeFieldMapper) fieldMapper;
|
GeoShapeFieldMapper geoShapeFieldMapper = (GeoShapeFieldMapper) fieldMapper;
|
||||||
SpatialStrategy strategy = geoShapeFieldMapper.spatialStrategy();
|
PrefixTreeStrategy strategy = geoShapeFieldMapper.spatialStrategy();
|
||||||
|
|
||||||
assertThat(strategy.getDistanceErrorPct(), equalTo(0.1));
|
assertThat(strategy.getDistErrPct(), equalTo(0.1));
|
||||||
assertThat(strategy.getPrefixTree(), instanceOf(GeohashPrefixTree.class));
|
assertThat(strategy.getGrid(), instanceOf(GeohashPrefixTree.class));
|
||||||
assertThat(strategy.getPrefixTree().getMaxLevels(), equalTo(4));
|
assertThat(strategy.getGrid().getMaxLevels(), equalTo(4));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -77,10 +78,10 @@ public class GeoShapeFieldMapperTests {
|
|||||||
assertThat(fieldMapper, instanceOf(GeoShapeFieldMapper.class));
|
assertThat(fieldMapper, instanceOf(GeoShapeFieldMapper.class));
|
||||||
|
|
||||||
GeoShapeFieldMapper geoShapeFieldMapper = (GeoShapeFieldMapper) fieldMapper;
|
GeoShapeFieldMapper geoShapeFieldMapper = (GeoShapeFieldMapper) fieldMapper;
|
||||||
SpatialStrategy strategy = geoShapeFieldMapper.spatialStrategy();
|
PrefixTreeStrategy strategy = geoShapeFieldMapper.spatialStrategy();
|
||||||
|
|
||||||
assertThat(strategy.getDistanceErrorPct(), equalTo(0.5));
|
assertThat(strategy.getDistErrPct(), equalTo(0.5));
|
||||||
assertThat(strategy.getPrefixTree(), instanceOf(QuadPrefixTree.class));
|
assertThat(strategy.getGrid(), instanceOf(QuadPrefixTree.class));
|
||||||
assertThat(strategy.getPrefixTree().getMaxLevels(), equalTo(6));
|
assertThat(strategy.getGrid().getMaxLevels(), equalTo(6));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user