LUCENE-7291: Fix spatial HeatmapFacetCounter bug with dateline and large non-point shapes

This commit is contained in:
David Smiley 2016-06-10 13:36:07 -04:00
parent e4db256d3f
commit b33d7176aa
3 changed files with 40 additions and 25 deletions

View File

@ -143,6 +143,10 @@ Bug Fixes
* LUCENE-7286: Added support for highlighting SynonymQuery. (Adrien Grand)
* LUCENE-7291: Spatial heatmap faceting could mis-count when the heatmap crosses the
dateline and indexed non-point shapes are much bigger than the heatmap region.
(David Smiley)
Other
* LUCENE-7295: TermAutomatonQuery.hashCode calculates Automaton.toDot().hash,

View File

@ -20,17 +20,17 @@ import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.shape.Point;
import org.locationtech.spatial4j.shape.Rectangle;
import org.locationtech.spatial4j.shape.Shape;
import org.locationtech.spatial4j.shape.SpatialRelation;
import org.apache.lucene.index.IndexReaderContext;
import org.apache.lucene.spatial.prefix.tree.Cell;
import org.apache.lucene.spatial.prefix.tree.CellIterator;
import org.apache.lucene.spatial.prefix.tree.SpatialPrefixTree;
import org.apache.lucene.util.ArrayUtil;
import org.apache.lucene.util.Bits;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.shape.Point;
import org.locationtech.spatial4j.shape.Rectangle;
import org.locationtech.spatial4j.shape.Shape;
import org.locationtech.spatial4j.shape.SpatialRelation;
/**
* Computes spatial facets in two dimensions as a grid of numbers. The data is often visualized as a so-called
@ -204,8 +204,9 @@ public class HeatmapFacetCounter {
int[] pair = new int[2];//output of intersectInterval
for (Map.Entry<Rectangle, Integer> entry : ancestors.entrySet()) {
Rectangle rect = entry.getKey();
Rectangle rect = entry.getKey(); // from a cell (thus doesn't cross DL)
final int count = entry.getValue();
//note: we approach this in a way that eliminates int overflow/underflow (think huge cell, tiny heatmap)
intersectInterval(heatMinY, heatMaxY, cellHeight, rows, rect.getMinY(), rect.getMaxY(), pair);
final int startRow = pair[0];
@ -218,32 +219,33 @@ public class HeatmapFacetCounter {
incrementRange(heatmap, startCol, endCol, startRow, endRow, count);
} else {
// note: the cell rect might intersect 2 disjoint parts of the heatmap, so we do the left & right separately
final int leftColumns = (int) Math.round((180 - heatMinX) / cellWidth);
final int rightColumns = heatmap.columns - leftColumns;
//left half of dateline:
if (rect.getMaxX() >= heatMinX) {
final int leftColumns = (int) Math.round((180 - heatMinX) / cellWidth) + 1;
if (rect.getMaxX() > heatMinX) {
intersectInterval(heatMinX, 180, cellWidth, leftColumns, rect.getMinX(), rect.getMaxX(), pair);
final int startCol = pair[0];
final int endCol = pair[1];
incrementRange(heatmap, startCol, endCol, startRow, endRow, count);
}
//right half of dateline
if (rect.getMinY() <= heatMaxX) {
final int rightColumns = (int) Math.round(heatMaxX / cellWidth) + 1;
intersectInterval(0, heatMaxX, cellWidth, rightColumns, rect.getMinX(), rect.getMaxX(), pair);
final int startCol = pair[0];
final int endCol = pair[1];
if (rect.getMinX() < heatMaxX) {
intersectInterval(-180, heatMaxX, cellWidth, rightColumns, rect.getMinX(), rect.getMaxX(), pair);
final int startCol = pair[0] + leftColumns;
final int endCol = pair[1] + leftColumns;
incrementRange(heatmap, startCol, endCol, startRow, endRow, count);
}
}
}
return heatmap;
}
private static void intersectInterval(double heatMin, double heatMax, double heatCellLen, int heatLen,
private static void intersectInterval(double heatMin, double heatMax, double heatCellLen, int numCells,
double cellMin, double cellMax,
int[] out) {
assert heatMin < heatMax && cellMin < cellMax;
//precondition: we know there's an intersection
if (heatMin >= cellMin) {
out[0] = 0;
@ -251,7 +253,7 @@ public class HeatmapFacetCounter {
out[0] = (int) Math.round((cellMin - heatMin) / heatCellLen);
}
if (heatMax <= cellMax) {
out[1] = heatLen - 1;
out[1] = numCells - 1;
} else {
out[1] = (int) Math.round((cellMax - heatMin) / heatCellLen) - 1;
}

View File

@ -21,15 +21,6 @@ import java.util.ArrayList;
import java.util.List;
import com.carrotsearch.randomizedtesting.annotations.Repeat;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.context.SpatialContextFactory;
import org.locationtech.spatial4j.distance.DistanceUtils;
import org.locationtech.spatial4j.shape.Circle;
import org.locationtech.spatial4j.shape.Point;
import org.locationtech.spatial4j.shape.Rectangle;
import org.locationtech.spatial4j.shape.Shape;
import org.locationtech.spatial4j.shape.SpatialRelation;
import org.locationtech.spatial4j.shape.impl.RectangleImpl;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TotalHitCountCollector;
import org.apache.lucene.spatial.StrategyTestCase;
@ -39,6 +30,15 @@ import org.apache.lucene.util.Bits;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.context.SpatialContextFactory;
import org.locationtech.spatial4j.distance.DistanceUtils;
import org.locationtech.spatial4j.shape.Circle;
import org.locationtech.spatial4j.shape.Point;
import org.locationtech.spatial4j.shape.Rectangle;
import org.locationtech.spatial4j.shape.Shape;
import org.locationtech.spatial4j.shape.SpatialRelation;
import org.locationtech.spatial4j.shape.impl.RectangleImpl;
import static com.carrotsearch.randomizedtesting.RandomizedTest.atMost;
import static com.carrotsearch.randomizedtesting.RandomizedTest.randomIntBetween;
@ -82,6 +82,15 @@ public class HeatmapFacetCounterTest extends StrategyTestCase {
// add specific tests if we find a bug.
}
@Test
public void testLucene7291Dateline() throws IOException {
grid = new QuadPrefixTree(ctx, 2); // only 2, and we wind up with some big leaf cells
strategy = new RecursivePrefixTreeStrategy(grid, getTestClass().getSimpleName());
adoc("0", ctx.makeRectangle(-102, -83, 43, 52));
commit();
validateHeatmapResultLoop(ctx.makeRectangle(179, -179, 62, 63), 2, 100);// HM crosses dateline
}
@Test
public void testQueryCircle() throws IOException {
//overwrite setUp; non-geo bounds is more straight-forward; otherwise 88,88 would actually be practically north,