LUCENE-8968: Improve performance of WITHIN and DISJOINT queries for Shape queries (#857)

This commit is contained in:
Ignacio Vera 2019-09-10 14:53:56 +02:00 committed by GitHub
parent f09aafd624
commit de423aec1f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 314 additions and 226 deletions

View File

@ -147,6 +147,9 @@ the total hits is not requested.
* LUCENE-8755: spatial-extras quad and packed quad prefix trees now index points faster. * LUCENE-8755: spatial-extras quad and packed quad prefix trees now index points faster.
(Chongchen Chen, David Smiley) (Chongchen Chen, David Smiley)
* LUCENE-8968: Improve performance of WITHIN and DISJOINT queries for Shape queries by
doing just one pass whenever possible. (Ignacio Vera)
Bug Fixes Bug Fixes
* LUCENE-8755: spatial-extras quad and packed quad prefix trees could throw a * LUCENE-8755: spatial-extras quad and packed quad prefix trees could throw a

View File

@ -61,7 +61,12 @@ final class LatLonShapeBoundingBoxQuery extends ShapeQuery {
if (queryRelation == QueryRelation.WITHIN) { if (queryRelation == QueryRelation.WITHIN) {
return rectangle2D.containsTriangle(aX, aY, bX, bY, cX, cY); return rectangle2D.containsTriangle(aX, aY, bX, bY, cX, cY);
} }
return rectangle2D.intersectsTriangle(aX, aY, bX, bY, cX, cY); switch (queryRelation) {
case INTERSECTS: return rectangle2D.intersectsTriangle(aX, aY, bX, bY, cX, cY);
case WITHIN: return rectangle2D.containsTriangle(aX, aY, bX, bY, cX, cY);
case DISJOINT: return rectangle2D.intersectsTriangle(aX, aY, bX, bY, cX, cY) == false;
default: throw new IllegalArgumentException("Unsupported query type :[" + queryRelation + "]");
}
} }
@Override @Override

View File

@ -94,11 +94,12 @@ final class LatLonShapeLineQuery extends ShapeQuery {
double clat = GeoEncodingUtils.decodeLatitude(scratchTriangle.cY); double clat = GeoEncodingUtils.decodeLatitude(scratchTriangle.cY);
double clon = GeoEncodingUtils.decodeLongitude(scratchTriangle.cX); double clon = GeoEncodingUtils.decodeLongitude(scratchTriangle.cX);
if (queryRelation == QueryRelation.WITHIN) { switch (queryRelation) {
return line2D.relateTriangle(alon, alat, blon, blat, clon, clat) == Relation.CELL_INSIDE_QUERY; case INTERSECTS: return line2D.relateTriangle(alon, alat, blon, blat, clon, clat) != Relation.CELL_OUTSIDE_QUERY;
case WITHIN: return line2D.relateTriangle(alon, alat, blon, blat, clon, clat) == Relation.CELL_INSIDE_QUERY;
case DISJOINT: return line2D.relateTriangle(alon, alat, blon, blat, clon, clat) == Relation.CELL_OUTSIDE_QUERY;
default: throw new IllegalArgumentException("Unsupported query type :[" + queryRelation + "]");
} }
// INTERSECTS
return line2D.relateTriangle(alon, alat, blon, blat, clon, clat) != Relation.CELL_OUTSIDE_QUERY;
} }
@Override @Override

View File

@ -88,11 +88,12 @@ final class LatLonShapePolygonQuery extends ShapeQuery {
double clat = GeoEncodingUtils.decodeLatitude(scratchTriangle.cY); double clat = GeoEncodingUtils.decodeLatitude(scratchTriangle.cY);
double clon = GeoEncodingUtils.decodeLongitude(scratchTriangle.cX); double clon = GeoEncodingUtils.decodeLongitude(scratchTriangle.cX);
if (queryRelation == QueryRelation.WITHIN) { switch (queryRelation) {
return poly2D.relateTriangle(alon, alat, blon, blat, clon, clat) == Relation.CELL_INSIDE_QUERY; case INTERSECTS: return poly2D.relateTriangle(alon, alat, blon, blat, clon, clat) != Relation.CELL_OUTSIDE_QUERY;
case WITHIN: return poly2D.relateTriangle(alon, alat, blon, blat, clon, clat) == Relation.CELL_INSIDE_QUERY;
case DISJOINT: return poly2D.relateTriangle(alon, alat, blon, blat, clon, clat) == Relation.CELL_OUTSIDE_QUERY;
default: throw new IllegalArgumentException("Unsupported query type :[" + queryRelation + "]");
} }
// INTERSECTS
return poly2D.relateTriangle(alon, alat, blon, blat, clon, clat) != Relation.CELL_OUTSIDE_QUERY;
} }
@Override @Override

View File

@ -26,6 +26,7 @@ import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.PointValues; import org.apache.lucene.index.PointValues;
import org.apache.lucene.index.PointValues.IntersectVisitor; import org.apache.lucene.index.PointValues.IntersectVisitor;
import org.apache.lucene.index.PointValues.Relation; import org.apache.lucene.index.PointValues.Relation;
import org.apache.lucene.search.CollectionTerminatedException;
import org.apache.lucene.search.ConstantScoreScorer; import org.apache.lucene.search.ConstantScoreScorer;
import org.apache.lucene.search.ConstantScoreWeight; import org.apache.lucene.search.ConstantScoreWeight;
import org.apache.lucene.search.DocIdSetIterator; import org.apache.lucene.search.DocIdSetIterator;
@ -88,7 +89,7 @@ abstract class ShapeQuery extends Query {
/** relates a range of triangles (internal node) to the query */ /** relates a range of triangles (internal node) to the query */
protected Relation relateRangeToQuery(byte[] minTriangle, byte[] maxTriangle, QueryRelation queryRelation) { protected Relation relateRangeToQuery(byte[] minTriangle, byte[] maxTriangle, QueryRelation queryRelation) {
// compute bounding box of internal node // compute bounding box of internal node
Relation r = relateRangeBBoxToQuery(ShapeField.BYTES, 0, minTriangle, 3 * ShapeField.BYTES, 2 * ShapeField.BYTES, maxTriangle); final Relation r = relateRangeBBoxToQuery(ShapeField.BYTES, 0, minTriangle, 3 * ShapeField.BYTES, 2 * ShapeField.BYTES, maxTriangle);
if (queryRelation == QueryRelation.DISJOINT) { if (queryRelation == QueryRelation.DISJOINT) {
return transposeRelation(r); return transposeRelation(r);
} }
@ -103,149 +104,44 @@ abstract class ShapeQuery extends Query {
} }
@Override @Override
public final Weight createWeight(IndexSearcher searcher, ScoreMode scoreMode, float boost) throws IOException { public final Weight createWeight(IndexSearcher searcher, ScoreMode scoreMode, float boost) {
final ShapeQuery query = this;
return new ConstantScoreWeight(query, boost) {
return new ConstantScoreWeight(this, boost) { @Override
public Scorer scorer(LeafReaderContext context) throws IOException {
/** create a visitor that adds documents that match the query using a sparse bitset. (Used by INTERSECT) */ final ScorerSupplier scorerSupplier = scorerSupplier(context);
protected IntersectVisitor getSparseIntersectVisitor(DocIdSetBuilder result) { if (scorerSupplier == null) {
return new IntersectVisitor() { return null;
final ShapeField.DecodedTriangle scratchTriangle = new ShapeField.DecodedTriangle();
DocIdSetBuilder.BulkAdder adder;
@Override
public void grow(int count) {
adder = result.grow(count);
}
@Override
public void visit(int docID) throws IOException {
adder.add(docID);
}
@Override
public void visit(int docID, byte[] t) throws IOException {
if (queryMatches(t, scratchTriangle, QueryRelation.INTERSECTS)) {
visit(docID);
}
}
@Override
public void visit(DocIdSetIterator iterator, byte[] t) throws IOException {
if (queryMatches(t, scratchTriangle, QueryRelation.INTERSECTS)) {
int docID;
while ((docID = iterator.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) {
visit(docID);
}
}
}
@Override
public Relation compare(byte[] minTriangle, byte[] maxTriangle) {
return relateRangeToQuery(minTriangle, maxTriangle, ShapeField.QueryRelation.INTERSECTS);
}
};
}
/** create a visitor that adds documents that match the query using a dense bitset. (Used by WITHIN, DISJOINT) */
protected IntersectVisitor getDenseIntersectVisitor(FixedBitSet intersect, FixedBitSet disjoint, ShapeField.QueryRelation queryRelation) {
return new IntersectVisitor() {
final ShapeField.DecodedTriangle scratchTriangle = new ShapeField.DecodedTriangle();
@Override
public void visit(int docID) throws IOException {
if (queryRelation == ShapeField.QueryRelation.DISJOINT) {
// if DISJOINT query set the doc in the disjoint bitset
disjoint.set(docID);
} else {
// for INTERSECT, and WITHIN queries we set the intersect bitset
intersect.set(docID);
}
}
@Override
public void visit(int docID, byte[] t) throws IOException {
if (queryMatches(t, scratchTriangle, queryRelation)) {
intersect.set(docID);
} else {
disjoint.set(docID);
}
}
@Override
public void visit(DocIdSetIterator iterator, byte[] t) throws IOException {
boolean queryMatches = queryMatches(t, scratchTriangle, queryRelation);
int docID;
while ((docID = iterator.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) {
if (queryMatches) {
intersect.set(docID);
} else {
disjoint.set(docID);
}
}
}
@Override
public Relation compare(byte[] minTriangle, byte[] maxTriangle) {
return relateRangeToQuery(minTriangle, maxTriangle, queryRelation);
}
};
}
/** get a scorer supplier for INTERSECT queries */
protected ScorerSupplier getIntersectScorerSupplier(LeafReader reader, PointValues values, Weight weight, ScoreMode scoreMode) throws IOException {
DocIdSetBuilder result = new DocIdSetBuilder(reader.maxDoc(), values, field);
IntersectVisitor visitor = getSparseIntersectVisitor(result);
return new RelationScorerSupplier(values, visitor, null, queryRelation) {
@Override
public Scorer get(long leadCost) throws IOException {
return getIntersectsScorer(ShapeQuery.this, reader, weight, result, score(), scoreMode);
}
};
}
/** get a scorer supplier for all other queries (DISJOINT, WITHIN) */
protected ScorerSupplier getScorerSupplier(LeafReader reader, PointValues values, Weight weight, ScoreMode scoreMode) throws IOException {
if (queryRelation == ShapeField.QueryRelation.INTERSECTS) {
return getIntersectScorerSupplier(reader, values, weight, scoreMode);
} }
//For within and disjoint we need two passes to remove false positives in case of multi-shapes. return scorerSupplier.get(Long.MAX_VALUE);
FixedBitSet within = new FixedBitSet(reader.maxDoc());
FixedBitSet disjoint = new FixedBitSet(reader.maxDoc());
IntersectVisitor withinVisitor = getDenseIntersectVisitor(within, disjoint, ShapeField.QueryRelation.WITHIN);
IntersectVisitor disjointVisitor = getDenseIntersectVisitor(within, disjoint, ShapeField.QueryRelation.DISJOINT);
return new RelationScorerSupplier(values, withinVisitor, disjointVisitor, queryRelation) {
@Override
public Scorer get(long leadCost) throws IOException {
return getScorer(ShapeQuery.this, weight, within, disjoint, score(), scoreMode);
}
};
} }
@Override @Override
public ScorerSupplier scorerSupplier(LeafReaderContext context) throws IOException { public ScorerSupplier scorerSupplier(LeafReaderContext context) throws IOException {
LeafReader reader = context.reader(); final LeafReader reader = context.reader();
PointValues values = reader.getPointValues(field); final PointValues values = reader.getPointValues(field);
if (values == null) { if (values == null) {
// No docs in this segment had any points fields // No docs in this segment had any points fields
return null; return null;
} }
FieldInfo fieldInfo = reader.getFieldInfos().fieldInfo(field); final FieldInfo fieldInfo = reader.getFieldInfos().fieldInfo(field);
if (fieldInfo == null) { if (fieldInfo == null) {
// No docs in this segment indexed this field at all // No docs in this segment indexed this field at all
return null; return null;
} }
boolean allDocsMatch = true;
if (values.getDocCount() != reader.maxDoc() ||
relateRangeToQuery(values.getMinPackedValue(), values.getMaxPackedValue(), queryRelation) != Relation.CELL_INSIDE_QUERY) {
allDocsMatch = false;
}
final Weight weight = this; final Weight weight = this;
if (allDocsMatch) { final Relation rel = relateRangeToQuery(values.getMinPackedValue(), values.getMaxPackedValue(), queryRelation);
if (rel == Relation.CELL_OUTSIDE_QUERY) {
// no documents match the query
return null;
}
else if (values.getDocCount() == reader.maxDoc() && rel == Relation.CELL_INSIDE_QUERY) {
// all documents match the query
return new ScorerSupplier() { return new ScorerSupplier() {
@Override @Override
public Scorer get(long leadCost) throws IOException { public Scorer get(long leadCost) {
return new ConstantScoreScorer(weight, score(), scoreMode, DocIdSetIterator.all(reader.maxDoc())); return new ConstantScoreScorer(weight, score(), scoreMode, DocIdSetIterator.all(reader.maxDoc()));
} }
@ -255,19 +151,22 @@ abstract class ShapeQuery extends Query {
} }
}; };
} else { } else {
return getScorerSupplier(reader, values, weight, scoreMode); if (queryRelation != QueryRelation.INTERSECTS
&& hasAnyHits(query, values) == false) {
// First we check if we have any hits so we are fast in the adversarial case where
// the shape does not match any documents and we are in the dense case
return null;
}
// walk the tree to get matching documents
return new RelationScorerSupplier(values, ShapeQuery.this) {
@Override
public Scorer get(long leadCost) throws IOException {
return getScorer(reader, weight, score(), scoreMode);
}
};
} }
} }
@Override
public Scorer scorer(LeafReaderContext context) throws IOException {
ScorerSupplier scorerSupplier = scorerSupplier(context);
if (scorerSupplier == null) {
return null;
}
return scorerSupplier.get(Long.MAX_VALUE);
}
@Override @Override
public boolean isCacheable(LeafReaderContext ctx) { public boolean isCacheable(LeafReaderContext ctx) {
return true; return true;
@ -314,57 +213,26 @@ abstract class ShapeQuery extends Query {
/** utility class for implementing constant score logic specific to INTERSECT, WITHIN, and DISJOINT */ /** utility class for implementing constant score logic specific to INTERSECT, WITHIN, and DISJOINT */
private static abstract class RelationScorerSupplier extends ScorerSupplier { private static abstract class RelationScorerSupplier extends ScorerSupplier {
PointValues values; final private PointValues values;
IntersectVisitor visitor; final private ShapeQuery query;
IntersectVisitor disjointVisitor;//it can be null private long cost = -1;
ShapeField.QueryRelation queryRelation;
long cost = -1;
RelationScorerSupplier(PointValues values, IntersectVisitor visitor, IntersectVisitor disjointVisitor, QueryRelation queryRelation) { RelationScorerSupplier(final PointValues values, final ShapeQuery query) {
this.values = values; this.values = values;
this.visitor = visitor; this.query = query;
this.disjointVisitor = disjointVisitor;
this.queryRelation = queryRelation;
} }
/** create a visitor that clears documents that do NOT match the polygon query; used with INTERSECTS */ protected Scorer getScorer(final LeafReader reader, final Weight weight, final float boost, final ScoreMode scoreMode) throws IOException {
private IntersectVisitor getInverseIntersectVisitor(ShapeQuery query, FixedBitSet result, int[] cost) { switch (query.getQueryRelation()) {
return new IntersectVisitor() { case INTERSECTS: return getSparseScorer(reader, weight, boost, scoreMode);
final ShapeField.DecodedTriangle scratchTriangle = new ShapeField.DecodedTriangle(); case WITHIN:
@Override case DISJOINT: return getDenseScorer(reader, weight, boost, scoreMode);
public void visit(int docID) { default: throw new IllegalArgumentException("Unsupported query type :[" + query.getQueryRelation() + "]");
result.clear(docID); }
cost[0]--;
}
@Override
public void visit(int docID, byte[] packedTriangle) {
if (query.queryMatches(packedTriangle, scratchTriangle, QueryRelation.INTERSECTS) == false) {
visit(docID);
}
}
@Override
public void visit(DocIdSetIterator iterator, byte[] t) throws IOException {
if (query.queryMatches(t, scratchTriangle, QueryRelation.INTERSECTS) == false) {
int docID;
while ((docID = iterator.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) {
visit(docID);
}
}
}
@Override
public Relation compare(byte[] minPackedValue, byte[] maxPackedValue) {
return transposeRelation(query.relateRangeToQuery(minPackedValue, maxPackedValue, QueryRelation.INTERSECTS));
}
};
} }
/** returns a Scorer for INTERSECT queries that uses a sparse bitset */ /** Scorer used for INTERSECTS **/
protected Scorer getIntersectsScorer(ShapeQuery query, LeafReader reader, Weight weight, private Scorer getSparseScorer(final LeafReader reader, final Weight weight, final float boost, final ScoreMode scoreMode) throws IOException {
DocIdSetBuilder docIdSetBuilder, final float boost, ScoreMode scoreMode) throws IOException {
if (values.getDocCount() == reader.maxDoc() if (values.getDocCount() == reader.maxDoc()
&& values.getDocCount() == values.size() && values.getDocCount() == values.size()
&& cost() > reader.maxDoc() / 2) { && cost() > reader.maxDoc() / 2) {
@ -373,34 +241,41 @@ abstract class ShapeQuery extends Query {
// by computing the set of documents that do NOT match the query // by computing the set of documents that do NOT match the query
final FixedBitSet result = new FixedBitSet(reader.maxDoc()); final FixedBitSet result = new FixedBitSet(reader.maxDoc());
result.set(0, reader.maxDoc()); result.set(0, reader.maxDoc());
int[] cost = new int[]{reader.maxDoc()}; final long[] cost = new long[]{reader.maxDoc()};
values.intersect(getInverseIntersectVisitor(query, result, cost)); values.intersect(getInverseDenseVisitor(query, result, cost));
final DocIdSetIterator iterator = new BitSetIterator(result, cost[0]); final DocIdSetIterator iterator = new BitSetIterator(result, cost[0]);
return new ConstantScoreScorer(weight, boost, scoreMode, iterator); return new ConstantScoreScorer(weight, boost, scoreMode, iterator);
} }
final DocIdSetBuilder docIdSetBuilder = new DocIdSetBuilder(reader.maxDoc(), values, query.getField());
values.intersect(visitor); values.intersect(getSparseVisitor(query, docIdSetBuilder));
DocIdSetIterator iterator = docIdSetBuilder.build().iterator(); final DocIdSetIterator iterator = docIdSetBuilder.build().iterator();
return new ConstantScoreScorer(weight, boost, scoreMode, iterator); return new ConstantScoreScorer(weight, boost, scoreMode, iterator);
} }
/** returns a Scorer for all other (non INTERSECT) queries */ /** Scorer used for WITHIN and DISJOINT **/
protected Scorer getScorer(ShapeQuery query, Weight weight, private Scorer getDenseScorer(LeafReader reader, Weight weight, final float boost, ScoreMode scoreMode) throws IOException {
FixedBitSet intersect, FixedBitSet disjoint, final float boost, ScoreMode scoreMode) throws IOException { final FixedBitSet result = new FixedBitSet(reader.maxDoc());
values.intersect(visitor); final long[] cost;
if (disjointVisitor != null) { if (values.getDocCount() == reader.maxDoc()) {
values.intersect(disjointVisitor); cost = new long[]{values.size()};
} // In this case we can spare one visit to the tree, all documents
DocIdSetIterator iterator; // are potential matches
if (query.queryRelation == ShapeField.QueryRelation.DISJOINT) { result.set(0, reader.maxDoc());
disjoint.andNot(intersect); // Remove false positives
iterator = new BitSetIterator(disjoint, cost()); values.intersect(getInverseDenseVisitor(query, result, cost));
} else if (query.queryRelation == ShapeField.QueryRelation.WITHIN) {
intersect.andNot(disjoint);
iterator = new BitSetIterator(intersect, cost());
} else { } else {
iterator = new BitSetIterator(intersect, cost()); cost = new long[]{0};
// Get potential documents.
final FixedBitSet excluded = new FixedBitSet(reader.maxDoc());
values.intersect(getDenseVisitor(query, result, excluded, cost));
result.andNot(excluded);
// Remove false positives, we only care about the inner nodes as intersecting
// leaf nodes have been already taken into account. Unfortunately this
// process still reads the leaf nodes.
values.intersect(getShallowInverseDenseVisitor(query, result));
} }
assert cost[0] > 0;
final DocIdSetIterator iterator = new BitSetIterator(result, cost[0]);
return new ConstantScoreScorer(weight, boost, scoreMode, iterator); return new ConstantScoreScorer(weight, boost, scoreMode, iterator);
} }
@ -408,14 +283,213 @@ abstract class ShapeQuery extends Query {
public long cost() { public long cost() {
if (cost == -1) { if (cost == -1) {
// Computing the cost may be expensive, so only do it if necessary // Computing the cost may be expensive, so only do it if necessary
if (queryRelation == ShapeField.QueryRelation.DISJOINT) { cost = values.estimatePointCount(getEstimateVisitor(query));
cost = values.estimatePointCount(disjointVisitor);
} else {
cost = values.estimatePointCount(visitor);
}
assert cost >= 0; assert cost >= 0;
} }
return cost; return cost;
} }
} }
/** create a visitor for calculating point count estimates for the provided relation */
private static IntersectVisitor getEstimateVisitor(final ShapeQuery query) {
return new IntersectVisitor() {
@Override
public void visit(int docID) {
throw new UnsupportedOperationException();
}
@Override
public void visit(int docID, byte[] t) {
throw new UnsupportedOperationException();
}
@Override
public Relation compare(byte[] minTriangle, byte[] maxTriangle) {
return query.relateRangeToQuery(minTriangle, maxTriangle, query.getQueryRelation());
}
};
}
/** create a visitor that adds documents that match the query using a sparse bitset. (Used by INTERSECT) */
private static IntersectVisitor getSparseVisitor(final ShapeQuery query, final DocIdSetBuilder result) {
return new IntersectVisitor() {
final ShapeField.DecodedTriangle scratchTriangle = new ShapeField.DecodedTriangle();
DocIdSetBuilder.BulkAdder adder;
@Override
public void grow(int count) {
adder = result.grow(count);
}
@Override
public void visit(int docID) {
adder.add(docID);
}
@Override
public void visit(int docID, byte[] t) {
if (query.queryMatches(t, scratchTriangle, query.getQueryRelation())) {
visit(docID);
}
}
@Override
public void visit(DocIdSetIterator iterator, byte[] t) throws IOException {
if (query.queryMatches(t, scratchTriangle, query.getQueryRelation())) {
int docID;
while ((docID = iterator.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) {
visit(docID);
}
}
}
@Override
public Relation compare(byte[] minTriangle, byte[] maxTriangle) {
return query.relateRangeToQuery(minTriangle, maxTriangle, query.getQueryRelation());
}
};
}
/** create a visitor that adds documents that match the query using a dense bitset; used with WITHIN & DISJOINT */
private static IntersectVisitor getDenseVisitor(final ShapeQuery query, final FixedBitSet result, final FixedBitSet excluded, final long[] cost) {
return new IntersectVisitor() {
final ShapeField.DecodedTriangle scratchTriangle = new ShapeField.DecodedTriangle();
@Override
public void visit(int docID) {
result.set(docID);
cost[0]++;
}
@Override
public void visit(int docID, byte[] t) {
if (query.queryMatches(t, scratchTriangle, query.getQueryRelation())) {
visit(docID);
} else {
excluded.set(docID);
}
}
@Override
public void visit(DocIdSetIterator iterator, byte[] t) throws IOException {
boolean matches = query.queryMatches(t, scratchTriangle, query.getQueryRelation());
int docID;
while ((docID = iterator.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) {
if (matches) {
visit(docID);
} else {
excluded.set(docID);
}
}
}
@Override
public Relation compare(byte[] minTriangle, byte[] maxTriangle) {
return query.relateRangeToQuery(minTriangle, maxTriangle, query.getQueryRelation());
}
};
}
/** create a visitor that clears documents that do not match the polygon query using a dense bitset; used with WITHIN & DISJOINT */
private static IntersectVisitor getInverseDenseVisitor(final ShapeQuery query, final FixedBitSet result, final long[] cost) {
return new IntersectVisitor() {
final ShapeField.DecodedTriangle scratchTriangle = new ShapeField.DecodedTriangle();
@Override
public void visit(int docID) {
result.clear(docID);
cost[0]--;
}
@Override
public void visit(int docID, byte[] packedTriangle) {
if (query.queryMatches(packedTriangle, scratchTriangle, query.getQueryRelation()) == false) {
visit(docID);
}
}
@Override
public void visit(DocIdSetIterator iterator, byte[] t) throws IOException {
if (query.queryMatches(t, scratchTriangle, query.getQueryRelation()) == false) {
int docID;
while ((docID = iterator.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) {
visit(docID);
}
}
}
@Override
public Relation compare(byte[] minPackedValue, byte[] maxPackedValue) {
return transposeRelation(query.relateRangeToQuery(minPackedValue, maxPackedValue, query.getQueryRelation()));
}
};
}
/** create a visitor that clears documents that do not match the polygon query using a dense bitset; used with WITHIN & DISJOINT.
* This visitor only takes into account inner nodes */
private static IntersectVisitor getShallowInverseDenseVisitor(final ShapeQuery query, final FixedBitSet result) {
return new IntersectVisitor() {
@Override
public void visit(int docID) {
result.clear(docID);
}
@Override
public void visit(int docID, byte[] packedTriangle) {
//NO-OP
}
@Override
public void visit(DocIdSetIterator iterator, byte[] t) {
//NO-OP
}
@Override
public Relation compare(byte[] minPackedValue, byte[] maxPackedValue) {
return transposeRelation(query.relateRangeToQuery(minPackedValue, maxPackedValue, query.getQueryRelation()));
}
};
}
/** Return true if the query matches at least one document. It creates a visitor that terminates as soon as one or more docs
* are matched. */
private static boolean hasAnyHits(final ShapeQuery query, final PointValues values) throws IOException {
try {
values.intersect(new IntersectVisitor() {
final ShapeField.DecodedTriangle scratchTriangle = new ShapeField.DecodedTriangle();
@Override
public void visit(int docID) {
throw new CollectionTerminatedException();
}
@Override
public void visit(int docID, byte[] t) {
if (query.queryMatches(t, scratchTriangle, query.getQueryRelation())) {
throw new CollectionTerminatedException();
}
}
@Override
public void visit(DocIdSetIterator iterator, byte[] t) {
if (query.queryMatches(t, scratchTriangle, query.getQueryRelation())) {
throw new CollectionTerminatedException();
}
}
@Override
public Relation compare(byte[] minPackedValue, byte[] maxPackedValue) {
Relation rel = query.relateRangeToQuery(minPackedValue, maxPackedValue, query.getQueryRelation());
if (rel == Relation.CELL_INSIDE_QUERY) {
throw new CollectionTerminatedException();
}
return rel;
}
});
} catch (CollectionTerminatedException e) {
return true;
}
return false;
}
} }

View File

@ -57,10 +57,12 @@ public class XYShapeBoundingBoxQuery extends ShapeQuery {
int cY = scratchTriangle.cY; int cY = scratchTriangle.cY;
int cX = scratchTriangle.cX; int cX = scratchTriangle.cX;
if (queryRelation == QueryRelation.WITHIN) { switch (queryRelation) {
return rectangle2D.containsTriangle(aX, aY, bX, bY, cX, cY); case INTERSECTS: return rectangle2D.intersectsTriangle(aX, aY, bX, bY, cX, cY);
case WITHIN: return rectangle2D.containsTriangle(aX, aY, bX, bY, cX, cY);
case DISJOINT: return rectangle2D.intersectsTriangle(aX, aY, bX, bY, cX, cY) == false;
default: throw new IllegalArgumentException("Unsupported query type :[" + queryRelation + "]");
} }
return rectangle2D.intersectsTriangle(aX, aY, bX, bY, cX, cY);
} }
@Override @Override

View File

@ -96,11 +96,12 @@ final class XYShapeLineQuery extends ShapeQuery {
double clat = decode(scratchTriangle.cY); double clat = decode(scratchTriangle.cY);
double clon = decode(scratchTriangle.cX); double clon = decode(scratchTriangle.cX);
if (queryRelation == QueryRelation.WITHIN) { switch (queryRelation) {
return line2D.relateTriangle(alon, alat, blon, blat, clon, clat) == Relation.CELL_INSIDE_QUERY; case INTERSECTS: return line2D.relateTriangle(alon, alat, blon, blat, clon, clat) != Relation.CELL_OUTSIDE_QUERY;
case WITHIN: return line2D.relateTriangle(alon, alat, blon, blat, clon, clat) == Relation.CELL_INSIDE_QUERY;
case DISJOINT: return line2D.relateTriangle(alon, alat, blon, blat, clon, clat) == Relation.CELL_OUTSIDE_QUERY;
default: throw new IllegalArgumentException("Unsupported query type :[" + queryRelation + "]");
} }
// INTERSECTS
return line2D.relateTriangle(alon, alat, blon, blat, clon, clat) != Relation.CELL_OUTSIDE_QUERY;
} }
@Override @Override

View File

@ -88,11 +88,12 @@ final class XYShapePolygonQuery extends ShapeQuery {
double clat = decode(scratchTriangle.cY); double clat = decode(scratchTriangle.cY);
double clon = decode(scratchTriangle.cX); double clon = decode(scratchTriangle.cX);
if (queryRelation == QueryRelation.WITHIN) { switch (queryRelation) {
return poly2D.relateTriangle(alon, alat, blon, blat, clon, clat) == Relation.CELL_INSIDE_QUERY; case INTERSECTS: return poly2D.relateTriangle(alon, alat, blon, blat, clon, clat) != Relation.CELL_OUTSIDE_QUERY;
case WITHIN: return poly2D.relateTriangle(alon, alat, blon, blat, clon, clat) == Relation.CELL_INSIDE_QUERY;
case DISJOINT: return poly2D.relateTriangle(alon, alat, blon, blat, clon, clat) == Relation.CELL_OUTSIDE_QUERY;
default: throw new IllegalArgumentException("Unsupported query type :[" + queryRelation + "]");
} }
// INTERSECTS
return poly2D.relateTriangle(alon, alat, blon, blat, clon, clat) != Relation.CELL_OUTSIDE_QUERY;
} }
@Override @Override