LUCENE-4916: Spatial Within RPT and shape simplification bug

git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1465679 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
David Wayne Smiley 2013-04-08 16:49:01 +00:00
parent 873b2308b6
commit 9dc0040467
3 changed files with 61 additions and 15 deletions

View File

@ -52,7 +52,7 @@ public abstract class AbstractVisitingPrefixTreeFilter extends AbstractPrefixTre
public AbstractVisitingPrefixTreeFilter(Shape queryShape, String fieldName, SpatialPrefixTree grid, public AbstractVisitingPrefixTreeFilter(Shape queryShape, String fieldName, SpatialPrefixTree grid,
int detailLevel, int prefixGridScanLevel) { int detailLevel, int prefixGridScanLevel) {
super(queryShape, fieldName, grid, detailLevel); super(queryShape, fieldName, grid, detailLevel);
this.prefixGridScanLevel = Math.max(1, Math.min(prefixGridScanLevel, grid.getMaxLevels() - 1)); this.prefixGridScanLevel = Math.max(0, Math.min(prefixGridScanLevel, grid.getMaxLevels() - 1));
assert detailLevel <= grid.getMaxLevels(); assert detailLevel <= grid.getMaxLevels();
} }

View File

@ -32,6 +32,7 @@ import org.apache.lucene.util.Bits;
import org.apache.lucene.util.FixedBitSet; import org.apache.lucene.util.FixedBitSet;
import java.io.IOException; import java.io.IOException;
import java.util.Collection;
import java.util.Iterator; import java.util.Iterator;
/** /**
@ -160,18 +161,41 @@ public class WithinPrefixTreeFilter extends AbstractVisitingPrefixTreeFilter {
@Override @Override
protected void visitLeaf(Cell cell) throws IOException { protected void visitLeaf(Cell cell) throws IOException {
SpatialRelation relation = visitRelation; //visitRelation is declared as a field, populated by visit() so we don't recompute it
assert detailLevel != cell.getLevel();
assert visitRelation == cell.getShape().relate(queryShape); assert visitRelation == cell.getShape().relate(queryShape);
if (relation.intersects()) { if (allCellsIntersectQuery(cell, visitRelation))
collectDocs(inside); collectDocs(inside);
} else { else
collectDocs(outside); collectDocs(outside);
} }
/** Returns true if the provided cell, and all its sub-cells down to
* detailLevel all intersect the queryShape.
*/
private boolean allCellsIntersectQuery(Cell cell, SpatialRelation relate/*cell to query*/) {
if (relate == null)
relate = cell.getShape().relate(queryShape);
if (cell.getLevel() == detailLevel)
return relate.intersects();
if (relate == SpatialRelation.WITHIN)
return true;
if (relate == SpatialRelation.DISJOINT)
return false;
// Note: Generating all these cells just to determine intersection is not ideal.
// It was easy to implement but could be optimized. For example if the docs
// in question are already marked in the 'outside' bitset then it can be avoided.
Collection<Cell> subCells = cell.getSubCells(null);
for (Cell subCell : subCells) {
if (!allCellsIntersectQuery(subCell, null))//recursion
return false;
}
return true;
} }
@Override @Override
protected void visitScanned(Cell cell) throws IOException { protected void visitScanned(Cell cell) throws IOException {
if (queryShape.relate(cell.getShape()).intersects()) { if (allCellsIntersectQuery(cell, null)) {
collectDocs(inside); collectDocs(inside);
} else { } else {
collectDocs(outside); collectDocs(outside);

View File

@ -55,12 +55,14 @@ public class SpatialOpRecursivePrefixTreeTest extends StrategyTestCase {
deleteAll(); deleteAll();
} }
public void mySetup() throws IOException { public void mySetup(int maxLevels) throws IOException {
//non-geospatial makes this test a little easier (in gridSnap), and using boundary values 2^X raises //non-geospatial makes this test a little easier (in gridSnap), and using boundary values 2^X raises
// the prospect of edge conditions we want to test, plus makes for simpler numbers (no decimals). // the prospect of edge conditions we want to test, plus makes for simpler numbers (no decimals).
this.ctx = new SpatialContext(false, null, new RectangleImpl(0, 256, -128, 128, null)); this.ctx = new SpatialContext(false, null, new RectangleImpl(0, 256, -128, 128, null));
//A fairly shallow grid, and default 2.5% distErrPct //A fairly shallow grid, and default 2.5% distErrPct
this.grid = new QuadPrefixTree(ctx, randomIntBetween(1, 8)); if (maxLevels == -1)
maxLevels = randomIntBetween(1, 8);
this.grid = new QuadPrefixTree(ctx, maxLevels);
this.strategy = new RecursivePrefixTreeStrategy(grid, getClass().getSimpleName()); this.strategy = new RecursivePrefixTreeStrategy(grid, getClass().getSimpleName());
//((PrefixTreeStrategy) strategy).setDistErrPct(0);//fully precise to grid //((PrefixTreeStrategy) strategy).setDistErrPct(0);//fully precise to grid
@ -70,30 +72,27 @@ public class SpatialOpRecursivePrefixTreeTest extends StrategyTestCase {
@Test @Test
@Repeat(iterations = 10) @Repeat(iterations = 10)
public void testIntersects() throws IOException { public void testIntersects() throws IOException {
mySetup(); mySetup(-1);
doTest(SpatialOperation.Intersects); doTest(SpatialOperation.Intersects);
} }
@Test @Test
@Repeat(iterations = 10) @Repeat(iterations = 10)
public void testWithin() throws IOException { public void testWithin() throws IOException {
mySetup(); mySetup(-1);
doTest(SpatialOperation.IsWithin); doTest(SpatialOperation.IsWithin);
} }
@Test @Test
@Repeat(iterations = 10) @Repeat(iterations = 10)
public void testContains() throws IOException { public void testContains() throws IOException {
mySetup(); mySetup(-1);
doTest(SpatialOperation.Contains); doTest(SpatialOperation.Contains);
} }
@Test @Test
public void testWithinDisjointParts() throws IOException { public void testWithinDisjointParts() throws IOException {
this.ctx = new SpatialContext(false, null, new RectangleImpl(0, 256, -128, 128, null)); mySetup(7);
//A fairly shallow grid, and default 2.5% distErrPct
this.grid = new QuadPrefixTree(ctx, 7);
this.strategy = new RecursivePrefixTreeStrategy(grid, getClass().getSimpleName());
//one shape comprised of two parts, quite separated apart //one shape comprised of two parts, quite separated apart
adoc("0", new ShapePair(ctx.makeRectangle(0, 10, -120, -100), ctx.makeRectangle(220, 240, 110, 125))); adoc("0", new ShapePair(ctx.makeRectangle(0, 10, -120, -100), ctx.makeRectangle(220, 240, 110, 125)));
@ -102,7 +101,30 @@ public class SpatialOpRecursivePrefixTreeTest extends StrategyTestCase {
Query query = strategy.makeQuery(new SpatialArgs(SpatialOperation.IsWithin, ctx.makeRectangle(210, 245, 105, 128))); Query query = strategy.makeQuery(new SpatialArgs(SpatialOperation.IsWithin, ctx.makeRectangle(210, 245, 105, 128)));
SearchResults searchResults = executeQuery(query, 1); SearchResults searchResults = executeQuery(query, 1);
//we shouldn't find it because it's not completely within //we shouldn't find it because it's not completely within
assertTrue(searchResults.numFound==0); assertTrue(searchResults.numFound == 0);
}
@Test /** LUCENE-4916 */
public void testWithinLeafApproxRule() throws IOException {
mySetup(2);//4x4 grid
//indexed shape will simplify to entire right half (2 top cells)
adoc("0", ctx.makeRectangle(192, 204, -128, 128));
commit();
((RecursivePrefixTreeStrategy) strategy).setPrefixGridScanLevel(randomInt(2));
//query does NOT contain it; both indexed cells are leaves to the query, and
// when expanded to the full grid cells, the top one's top row is disjoint
// from the query and thus not a match.
assertTrue(executeQuery(strategy.makeQuery(
new SpatialArgs(SpatialOperation.IsWithin, ctx.makeRectangle(38, 192, -72, 56))
), 1).numFound==0);//no-match
//this time the rect is a little bigger and is considered a match. It's a
// an acceptable false-positive because of the grid approximation.
assertTrue(executeQuery(strategy.makeQuery(
new SpatialArgs(SpatialOperation.IsWithin, ctx.makeRectangle(38, 192, -72, 80))
), 1).numFound==1);//match
} }
private void doTest(final SpatialOperation operation) throws IOException { private void doTest(final SpatialOperation operation) throws IOException {