blob: e6fc63601285b6b0aabece65a55be53690172b7d [file] [log] [blame]
diff --git a/lucene/benchmark/conf/spatial2.alg b/lucene/benchmark/conf/spatial2.alg
new file mode 100644
index 0000000..6e968f1
--- /dev/null
+++ b/lucene/benchmark/conf/spatial2.alg
@@ -0,0 +1,97 @@
+#/**
+# * 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.
+# */
+# -------------------------------------------------------------------------------------
+# Spatial search benchmark
+# In order to use this, you'll need to first run 'ant geonames-files'.
+# You may need more memory when running this: -Dtask.mem=1000M (5000M for direct)
+# For docs on what options are available, see the javadocs.
+
+# Benchmark existing geohash, and quad implementations
+# -------------------------------------------------------------------------------------
+### Spatial Context, Grid, Strategy config
+#work.dir=work/geohash
+#work.dir=work/quad
+work.dir=work/flex
+doc.maker=org.apache.lucene.benchmark.byTask.feeds.SpatialDocMaker
+#spatial.prefixTree=geohash
+#spatial.prefixTree=quad
+spatial.prefixTree=flex
+spatial.levelPattern=3,1*,3
+# 1m or better
+spatial.maxDistErr = 0.000009
+spatial.docPointsOnly=true
+spatial.pruneLeafyBranches=false
+#spatial.distErrPct=.25
+codec.prostingsFormat=Direct
+
+### Source & Doc
+content.source=org.apache.lucene.benchmark.byTask.feeds.LineDocSource
+line.parser=org.apache.lucene.benchmark.byTask.feeds.GeonamesLineParser
+docs.file=work/geonames/allCountries.txt
+doc.tokenized=false
+
+### Directory
+directory=FSDirectory
+compound=false
+merge.factor=10
+ram.flush.mb=64
+concurrent.merge.scheduler.max.thread.count=2
+
+### Query
+query.maker=org.apache.lucene.benchmark.byTask.feeds.SpatialFileQueryMaker
+query.file=work/geonames/allCountries.txt
+query.file.line.parser=org.apache.lucene.benchmark.byTask.feeds.GeonamesLineParser
+#query.file.maxQueries=1000
+
+# Next 3 props convert query points to circles with a random radius and then optionally bbox'es
+query.spatial.radiusDegrees=0
+query.spatial.radiusDegreesRandPlusMinus=3
+query.spatial.bbox=true
+query.spatial.prefixGridScanLevel=pgsl:-1:-2:-3:-4:-5:-6:-7:-8:-9:-10:1
+query.spatial.score=false
+
+### Misc
+
+log.step.AddDoc = 100000
+
+{ "Populate"
+ ResetSystemErase
+ CreateIndex
+ #1 million docs, to speed up add, do it parallely
+ [{ "MAddDocs" AddDoc} : 500000] : 4
+ ForceMerge(1)
+ CommitIndex
+ CloseIndex
+} : 1
+
+#set above round to 0 on subsequent runs if not changing indexing but experimenting with search
+
+OpenReader
+{"WarmJIT" Search > : 4000
+CloseReader
+
+{ "Rounds"
+ ResetSystemSoft
+
+ OpenReader
+ Search
+ {"Queries" Search > : 4000
+ CloseReader
+
+ NewRound
+} : 11
+RepSumByPrefRound Queries
diff --git a/lucene/spatial/NOTE b/lucene/spatial/NOTE
new file mode 100644
index 0000000..8cbd068
--- /dev/null
+++ b/lucene/spatial/NOTE
@@ -0,0 +1,2 @@
+'[junit4]' is not recognized as an internal or external command,
+operable program or batch file.
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/prefix/tree/FlexPrefixTree2D.java b/lucene/spatial/src/java/org/apache/lucene/spatial/prefix/tree/FlexPrefixTree2D.java
new file mode 100644
index 0000000..c653694
--- /dev/null
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/prefix/tree/FlexPrefixTree2D.java
@@ -0,0 +1,631 @@
+package org.apache.lucene.spatial.prefix.tree;
+
+/*
+ * 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.
+ */
+
+import java.util.Map;
+
+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 org.apache.lucene.util.BytesRef;
+import org.apache.lucene.util.StringHelper;
+
+
+public class FlexPrefixTree2D extends SpatialPrefixTree {
+
+ /**
+ * Factory for creating {@link FlexPrefixTree2D} instances with useful defaults
+ */
+ public static class Factory extends SpatialPrefixTreeFactory {
+
+ public static final String LEVEL_PATTERN = "levelPattern";
+ public static final String LEVEL_PATTERN_REGEX = "(\\d,)*(\\d\\*)(,\\d)*"; //only the number of cells to be repeated must be specified
+ @Override
+ protected void init(Map<String, String> args, SpatialContext ctx) {
+ this.args = args;
+ this.ctx = ctx;
+ initNumberOfCellsPerLevel();
+ initMaxLevels();
+ }
+
+ private void initNumberOfCellsPerLevel() {
+
+ }
+
+ @Override
+ protected int getLevelForDistance(double degrees) {
+ FlexPrefixTree2D grid = new FlexPrefixTree2D(ctx, MAX_LEVELS_POSSIBLE);
+ return grid.getLevelForDistance(degrees);
+ }
+
+ @Override
+ protected SpatialPrefixTree newSPT() {
+ String levelPattern = args.get(LEVEL_PATTERN);
+ if (levelPattern != null && levelPattern.matches(LEVEL_PATTERN_REGEX)) {
+ int maxLevels=this.maxLevels != null ? this.maxLevels : MAX_LEVELS_POSSIBLE;
+ int []numberOfSubCells=null;
+ String []levels = levelPattern.split(",");
+ numberOfSubCells = new int[MAX_LEVELS_POSSIBLE];
+ int []temp = new int[levels.length];
+ int repeatLevel=levelPatternToIntArray(levels,temp);
+ int numberOfTimesTobeRepeated=maxLevels-(sum(temp)-temp[repeatLevel]);
+ System.arraycopy(temp,0,numberOfSubCells,0,repeatLevel);
+ for(int i=0;i<numberOfTimesTobeRepeated;++i){
+ numberOfSubCells[repeatLevel+i] = temp[repeatLevel];
+ }
+ System.arraycopy(temp,repeatLevel+1,numberOfSubCells,(repeatLevel+numberOfTimesTobeRepeated),(temp.length-(repeatLevel+1)));
+ maxLevels=(temp.length-temp[repeatLevel])+numberOfTimesTobeRepeated;
+ return new FlexPrefixTree2D(ctx, ctx.getWorldBounds(), maxLevels, numberOfSubCells);
+ }
+ return new FlexPrefixTree2D(ctx,
+ this.maxLevels != null ? maxLevels : MAX_LEVELS_POSSIBLE);
+ }
+
+ private int sum(int []toBeSummed) {
+ int sum=0;
+ for(int i:toBeSummed) {
+ sum+=i;
+ }
+ return sum;
+ }
+
+ private int levelPatternToIntArray(String []numberOfCellsString, int []numberOfCellsInt) {
+ int repeatLevel = 0;
+ for(int i=0;i<numberOfCellsString.length;++i) {
+ if(numberOfCellsString[i].contains("*")) {
+ numberOfCellsString[i] = numberOfCellsString[i].replace("*","");
+ repeatLevel = i;
+ }
+ numberOfCellsInt[i]=Integer.parseInt(numberOfCellsString[i]);
+ }
+ return repeatLevel;
+ }
+ }
+
+
+
+ public static final int MAX_LEVELS_POSSIBLE = 30; //32 bits, 31 bits for +ve and 30 shifts to attain max power of 2
+
+ public static final int DEFAULT_MAX_LEVELS = 12;
+
+ private final int[] numberOfSubCellsAsExponentOfFour;
+ private final int[] gridSizes;
+
+
+ //The world bounds for the grid
+ private final int xMin;
+ private final int xMax;
+ private final int yMin;
+ private final int yMax;
+
+ private final double intToDouble;
+ private final double doubleToInt;
+ private final double newOriginX;
+ private final double newOriginY;
+ private final Rectangle bounds;
+
+ private final byte LEAF_BYTE = 0x01;
+ private final byte START_CELL_NUMBER = 0x02;
+
+
+ //The API will change- This is temporary for tests to pass
+ public FlexPrefixTree2D(SpatialContext ctx, Rectangle bounds, int maxLevels) {
+ this(ctx, bounds, maxLevels, null);
+ }
+
+ //Do we need maxlevels? SubcellsPerLevel should be enough
+ //Can we provide a default cell division of 4
+ public FlexPrefixTree2D(SpatialContext ctx, Rectangle bounds, int maxLevels, int[] numberOfSubCellsAsExponentOfTwo) {
+ super(ctx, maxLevels);
+ if (numberOfSubCellsAsExponentOfTwo == null) {
+ numberOfSubCellsAsExponentOfTwo = new int[maxLevels];
+ for (int i = 0; i < maxLevels; ++i) {
+ numberOfSubCellsAsExponentOfTwo[i] = 1;
+ }
+ }
+ this.bounds = bounds;
+ this.numberOfSubCellsAsExponentOfFour = numberOfSubCellsAsExponentOfTwo;
+
+ this.xMin = 0;
+ this.yMin = 0;
+ this.gridSizes = new int[maxLevels + 1];
+
+ //Now we will create a lookup for height and width for levels
+ int division;
+ maxLevels = getMaxLevelsFromPowersOfFour(numberOfSubCellsAsExponentOfFour);
+ gridSizes[0] = (1 << maxLevels);
+
+ //Compute the rest
+ for (int i = 1; i < gridSizes.length; i++) {
+ division = this.numberOfSubCellsAsExponentOfFour[i - 1];
+ gridSizes[i] = (gridSizes[i - 1] >> division);
+ }
+
+ doubleToInt = Math.min((1 << maxLevels) / (bounds.getMaxY() - bounds.getMinY()), (1 << maxLevels) / (bounds.getMaxX() - bounds.getMinX()));
+
+ intToDouble = Math.max((bounds.getMaxX() - bounds.getMinX()) / (1 << maxLevels), (bounds.getMaxY() - bounds.getMinY()) / (1 << maxLevels));
+
+ newOriginX = bounds.getMinX();
+ newOriginY = bounds.getMinY();
+ xMax = (int) Math.ceil(bounds.getMaxX() * doubleToInt);
+ yMax = (int) Math.ceil(bounds.getMaxX() * doubleToInt);
+ }
+
+ private int getMaxLevelsFromPowersOfFour(int[] numberOfCellsAsExponentOfFour) {
+ int sum = 0;
+ for (int i=0;i <maxLevels;++i) {
+ sum += numberOfCellsAsExponentOfFour[i];
+ }
+ return sum;
+ }
+
+ //The API will change- This is temporary for tests to pass
+ public FlexPrefixTree2D(SpatialContext ctx) {
+ this(ctx, DEFAULT_MAX_LEVELS);
+ }
+
+ //The API will change- This is temporary for tests to pass
+ public FlexPrefixTree2D(SpatialContext ctx, int maxLevels) {
+ this(ctx, ctx.getWorldBounds(), maxLevels, null);
+ }
+
+ @Override
+ public int getLevelForDistance(double dist) {
+ if (dist == 0)//short circuit
+ return maxLevels;
+ for (int i = 0; i < maxLevels - 1; i++) {
+ if (dist > (gridSizes[i]*intToDouble)) {
+ return i;
+ }
+ }
+ return maxLevels;
+ }
+
+ @Override
+ public double getDistanceForLevel(int level) {
+ if (level < 1 || level > getMaxLevels())
+ throw new IllegalArgumentException("Level must be in 1 to maxLevels range");
+ //get the grid width and height for that level
+ double width = gridSizes[level] * intToDouble;
+ double height = gridSizes[level] * intToDouble;
+ //Use standard cartesian hypotenuse. For geospatial, this answer is larger
+ // than the correct one but it's okay to over-estimate.
+ return Math.sqrt(width * width + height * height);
+
+ }
+
+ @Override
+ public FlexCell getWorldCell() {
+ return new CellStack(maxLevels, xMin, yMin).cells[0];
+ }
+
+ @Override
+ public Cell readCell(BytesRef term, Cell scratch) {
+ FlexCell cell = (FlexCell) scratch;
+ if (scratch == null) {
+ cell = getWorldCell();
+ }
+ //First get the length of the term
+ int termLength = term.length;
+
+ //We store at cell a cellstack len + leaf bytes
+ termLength -= term.bytes[term.offset + term.length - 1] == LEAF_BYTE ? 1 : 0;
+
+ //Now from the cellstack obtain the correct numbered cell
+ FlexCell cells[] = cell.getCellStack().cells;
+ cells[termLength].reuseWithFreshTerm(term);
+ cell.getCellStack().invalidate(0);
+ return cells[termLength];
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder subcells = new StringBuilder();
+ for(int i=0;i<maxLevels;++i) {
+ subcells.append(numberOfSubCellsAsExponentOfFour[i]);
+ subcells.append("-");
+ }
+ return getClass().getSimpleName() + "(maxLevels:" + maxLevels + ",ctx:" + ctx +" ,NumberOfSubCells:"+subcells.toString()+")";
+ }
+
+ /**
+ * Cell here store the term without the leaf, so at any point we can remove
+ * cellLevel and instead use term.length
+ */
+ private class FlexCell implements Cell {
+
+ protected final CellStack cellStack;
+ private final int cellLevel;
+ private final FlexPrefixTreeIterator cellIterator;
+ private final Rectangle gridRectangle;
+ private boolean isLeaf;
+ private boolean isShapeSet = false;
+ private SpatialRelation shapeRel;
+
+ private int xMin;
+ private int yMin;
+
+ @Override
+ public String toString() {
+ return getShape() + "";
+ }
+
+ protected void setMinCornerCoordinates(int xmin, int ymin) {
+ //Set the coordinates of bottom most corner
+ this.xMin = xmin;
+ this.yMin = ymin;
+ }
+
+ protected FlexCell(FlexCell cell, CellStack cells, int level) {
+ super();
+ this.gridRectangle = ctx.makeRectangle(0, 0, 0, 0);
+ this.cellStack = cells;
+ this.cellLevel = level;
+ this.cellIterator = new FlexPrefixTreeIterator(cell, cellStack.term, cellLevel);
+ }
+
+ protected CellStack getCellStack() {
+ return cellStack;
+ }
+
+ @Override
+ public SpatialRelation getShapeRel() {
+ return shapeRel;
+ }
+
+ @Override
+ public void setShapeRel(SpatialRelation rel) {
+ this.shapeRel = rel;
+ }
+
+ @Override
+ public boolean isLeaf() {
+ return isLeaf;
+ }
+
+ @Override
+ public void setLeaf() {
+ this.isLeaf = true;
+ }
+
+ @Override
+ public BytesRef getTokenBytesWithLeaf(BytesRef result) {
+ result = getTokenBytesNoLeaf(result);
+ if (!isLeaf)
+ return result;
+ result.bytes[result.offset + result.length++] = LEAF_BYTE;
+ return result;
+ }
+
+ @Override
+ public BytesRef getTokenBytesNoLeaf(BytesRef result) {
+ cellStack.term.length = cellLevel;
+ if (result == null)
+ return BytesRef.deepCopyOf(cellStack.term);
+ result.bytes = cellStack.term.bytes;
+ result.length = cellStack.term.length;
+ result.offset = cellStack.term.offset;
+ return result;
+ }
+
+ @Override
+ public int getLevel() {
+ return cellLevel;
+ }
+
+ @Override
+ public CellIterator getNextLevelCells(Shape shapeFilter) {
+ assert getLevel() < FlexPrefixTree2D.this.getMaxLevels();
+ return cellIterator.init(shapeFilter, START_CELL_NUMBER);
+ }
+
+ @Override
+ public Shape getShape() {
+ if (!isShapeSet) {
+ isShapeSet = true;
+ makeShape();
+ }
+ return gridRectangle;
+ }
+
+ private void makeShape() {
+ BytesRef token = cellStack.term;
+ cellStack.decode(cellLevel);//Decode the terms
+ double xMax = ((xMin + gridSizes[token.length]) * intToDouble) + newOriginX;
+ double yMax = ((yMin + gridSizes[token.length]) * intToDouble) + newOriginY;
+ double xMin = (this.xMin * intToDouble) + newOriginX;
+ double yMin = (this.yMin * intToDouble) + newOriginY;
+ if (xMax < bounds.getMaxX()) {
+ xMax = Math.nextAfter(xMax, Double.NEGATIVE_INFINITY);
+ }
+
+ if (yMax < bounds.getMaxY()) {
+ yMax = Math.nextAfter(yMax, Double.NEGATIVE_INFINITY);
+ }
+ gridRectangle.reset(xMin, xMax, yMin, yMax);
+ }
+
+ @Override
+ public boolean isPrefixOf(Cell c) {
+ //Note: this only works when each level uses a whole number of bytes.
+ FlexCell cell = (FlexCell) c;
+ boolean result = StringHelper.startsWith(cell.cellStack.term, cellStack.term);
+ assert result == StringHelper.startsWith(c.getTokenBytesNoLeaf(null), getTokenBytesNoLeaf(null));
+ return result;
+ }
+
+ @Override
+ public int compareToNoLeaf(Cell fromCell) {
+ FlexCell b = (FlexCell) fromCell;
+ return b.cellStack.term.compareTo(cellStack.term);
+ }
+
+ private Cell reuseWithFreshTerm(BytesRef term) {
+ cellStack.invalidate(cellLevel); //Force re-decoding of the invalidated cell
+ this.isShapeSet = false;
+ this.isLeaf = false;
+ this.shapeRel = null;
+ this.cellStack.term.length = term.length;
+ this.cellStack.term.bytes = term.bytes;
+ this.cellStack.term.offset = term.offset;
+ // Now the term placed here may have a leaf
+ isLeaf = (cellStack.term.length > 0 && cellStack.term.bytes[cellStack.term.offset + cellStack.term.length - 1] == LEAF_BYTE);
+ if (isLeaf)
+ cellStack.term.length--;
+ return this;
+ }
+
+ private Cell reuse() {
+ cellStack.invalidate(cellLevel);
+ this.isShapeSet = false;
+ this.isLeaf = false;
+ this.shapeRel = null;
+ return this;
+ }
+
+
+ private SpatialRelation relateIntegerCoordinate(int int_min, int int_max, int ext_min, int ext_max) {
+ if (ext_min > int_max || ext_max < int_min) {
+ return SpatialRelation.DISJOINT;
+ }
+
+ if (ext_min >= int_min && ext_max <= int_max) {
+ return SpatialRelation.CONTAINS;
+ }
+
+ if (ext_min <= int_min && ext_max >= int_max) {
+ return SpatialRelation.WITHIN;
+ }
+ return SpatialRelation.INTERSECTS;
+ }
+
+ protected SpatialRelation relateIntegerRectangle() {
+ int xMax = this.xMin + gridSizes[this.cellLevel];
+ if (xMax < FlexPrefixTree2D.this.xMax) {
+ xMax -= 1;
+ }
+ int ymax = this.yMin + gridSizes[this.cellLevel];
+ if (ymax < FlexPrefixTree2D.this.yMax) {
+ ymax -= 1;
+ }
+ SpatialRelation yIntersect = relateIntegerCoordinate(this.yMin, ymax, this.cellStack.shapeFilterYMin, this.cellStack.shapeFilterYMax);
+ if (yIntersect == SpatialRelation.DISJOINT)
+ return SpatialRelation.DISJOINT;
+ SpatialRelation xIntersect = relateIntegerCoordinate(this.xMin, xMax, this.cellStack.shapeFilterXMin, this.cellStack.shapeFilterXMax);
+ if (xIntersect == SpatialRelation.DISJOINT)
+ return SpatialRelation.DISJOINT;
+ if (xIntersect == yIntersect)//in agreement
+ return xIntersect;
+ if (this.cellStack.shapeFilterXMin == this.xMin && this.cellStack.shapeFilterXMax == xMax)
+ return yIntersect;
+ if (this.cellStack.shapeFilterYMin == this.yMin && this.cellStack.shapeFilterYMax == ymax)
+ return xIntersect;
+ return SpatialRelation.INTERSECTS;
+ }
+
+ }
+
+
+ /**
+ * An Iterator for FlexCells. This iterator reuses cells at a level and iterates over the siblings
+ * initIter can be used to reuse the cell Iterator
+ */
+ private class FlexPrefixTreeIterator extends CellIterator {
+
+ private final BytesRef term;
+ private final int bytePos;
+ private final int endCellNumber;
+ private final FlexCell cell;
+ private int nextCellNumber;
+ private Shape shapeFilter;
+
+
+ protected FlexPrefixTreeIterator(FlexCell cell, BytesRef sharedTerm, int level) {
+ this.term = sharedTerm;
+ this.cell = cell;
+ if (level < maxLevels) {
+ this.endCellNumber = (1 <<( numberOfSubCellsAsExponentOfFour[level] + numberOfSubCellsAsExponentOfFour[level])) + 1;
+ } else {
+ this.endCellNumber = 0;
+ }
+ this.bytePos = level;
+ }
+
+ //Inititalizes the Iterator, so that we can reuse the iterator
+ protected CellIterator init(Shape shapeFilter, int start) {
+ this.nextCell = null;
+ this.thisCell = null;
+ //Level 0 does not store a byte its byte pos is -1, but, in makeshape this is handled
+ //Level 1 stores its byte at index 0
+ //this.bytePos = this.scratch.cellLevel-1;
+ this.shapeFilter = shapeFilter;
+ this.cell.cellStack.findIntegerBoundingBox(shapeFilter);
+ this.nextCellNumber = start;
+ return this;
+ }
+
+ //Concatenates to the source BytesRef the given byte and places into te target
+ private void changeTailByte(byte b) {
+ term.bytes[term.offset + bytePos] = b;
+ }
+
+ @Override
+ public boolean hasNext() {
+ thisCell = null;
+ if (nextCell != null)//calling hasNext twice in a row
+ return true;
+ while (levelHasUntraversedCell()) {
+ SpatialRelation rel = null;
+ nextCell = cell;
+ if (shapeFilter == null) {
+ return true;
+ } else {
+ FlexCell nextFlexCell = (FlexCell) nextCell;
+ nextFlexCell.cellStack.decode(nextFlexCell.cellLevel);
+ rel = getSpatialRelation(nextFlexCell);
+ if (rel.intersects()) {
+ nextCell.setShapeRel(rel);
+ if (rel == SpatialRelation.WITHIN)
+ nextCell.setLeaf(); // Since the relation is a within no further decomposition will be required
+ if (rel == SpatialRelation.CONTAINS) {
+ stopLevelIteration();
+ }
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private SpatialRelation getSpatialRelation(FlexCell nextFlexCell) {
+ SpatialRelation rel = null;
+ if (!nextFlexCell.cellStack.shapeFilterBoundingBox.getCrossesDateLine()) {
+ rel = nextFlexCell.relateIntegerRectangle();
+ if (!(shapeFilter instanceof Rectangle || shapeFilter instanceof Point) || (nextFlexCell.cellStack.shapeFilterBoundingBox.getCrossesDateLine()) || rel == SpatialRelation.WITHIN)
+ rel = null;
+ }
+ if (rel == null) {
+ rel = nextCell.getShape().relate(shapeFilter);
+ }
+ return rel;
+ }
+
+
+ private void stopLevelIteration() {
+ nextCellNumber = endCellNumber + 1;
+ }
+
+ //Populates into scratch the next cell in z-order TODO Hilbert ordering
+ private boolean levelHasUntraversedCell() {
+ if (nextCellNumber > endCellNumber) {
+ nextCell = null;
+ return false;
+ }
+ this.cell.cellStack.term.length = this.cell.cellLevel;
+ //We must call this as we want the cell to invalidate its ShapeCache
+ changeTailByte((byte) nextCellNumber);
+ cell.reuse();
+ ++nextCellNumber;
+ return true;
+ }
+ }
+
+ /**
+ * A stack of flexCells with the following characteristics
+ * - Lazy decoding of cells
+ * - Cells from the same CellStack share BytesRef
+ */
+ private class CellStack {
+
+ protected final FlexCell cells[];
+ protected int lastDecodedLevel = 0;
+ protected BytesRef term;
+
+ //ShapeFilter bounding box and calculations
+ private int shapeFilterXMin;
+ private int shapeFilterXMax;
+ private int shapeFilterYMin;
+ private int shapeFilterYMax;
+ private Rectangle shapeFilterBoundingBox;
+ private Shape shapeFilter;
+
+ public CellStack(int maxLevels, int xmin, int ymin) {
+ this.cells = new FlexCell[maxLevels + 1];
+ term = new BytesRef(maxLevels + 1); //+1 For leaf and this byteRef will be shared within the stack
+ for (int level = maxLevels; level >= 0; --level) {
+ if (level != maxLevels) {
+ cells[level] = new FlexCell(cells[level + 1], this, level);
+ } else {
+ cells[level] = new FlexCell(null, this, level);
+ }
+ }
+ //? The xmin,ymin needs to be set for the top cell. From there its decoded lazily a level at a time
+ cells[0].setMinCornerCoordinates(xmin, ymin);
+ }
+
+ private void findIntegerBoundingBox(Shape shapeFilter) {
+ if (shapeFilter != null && this.shapeFilter != shapeFilter) { // object equivalence?
+ //TODO this remains same for a given FPT and given shape
+ shapeFilterBoundingBox = shapeFilter.getBoundingBox();
+ this.shapeFilterXMax = (int) ((shapeFilterBoundingBox.getMaxX() - bounds.getMinX()) * doubleToInt);
+ this.shapeFilterXMin = (int) ((shapeFilterBoundingBox.getMinX() - bounds.getMinX()) * doubleToInt);
+ this.shapeFilterYMax = (int) ((shapeFilterBoundingBox.getMaxY() - bounds.getMinY()) * doubleToInt);
+ this.shapeFilterYMin = (int) ((shapeFilterBoundingBox.getMinY() - bounds.getMinY()) * doubleToInt);
+ this.shapeFilter = shapeFilter;
+ }
+
+ }
+
+ protected void decode(int cellLevel) {
+ int xmin;
+ int ymin;
+ int row;
+ int col;
+ int c;
+ int division;
+ //decode all cells from the last decoded cell to the desired cell
+ for (int i = lastDecodedLevel; i < cellLevel; i++) {
+ xmin = cells[i].xMin;
+ ymin = cells[i].yMin;
+ c = term.bytes[term.offset + i] - 2;
+ division = numberOfSubCellsAsExponentOfFour[i];
+ col = (c >> division);
+ row = (c - (1 << division) * col); // Is this worthwhile?
+ xmin += gridSizes[i + 1] * col;
+ ymin += gridSizes[i + 1] * row;
+ cells[i + 1].setMinCornerCoordinates(xmin, ymin);
+ }
+ if (lastDecodedLevel < cellLevel) {
+ lastDecodedLevel = cellLevel;
+ }
+ }
+
+
+ /**
+ * Invalidates the decoding of a cell forcing decoding to happen again
+ *
+ * @param cellLevel the Cell whose decoding is to be done
+ */
+ protected void invalidate(int cellLevel) {
+ lastDecodedLevel = Math.max(cellLevel - 1, 0); //Note: Cell at level 0 is always decoded.
+ }
+
+ }
+}
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/prefix/tree/SpatialPrefixTreeFactory.java b/lucene/spatial/src/java/org/apache/lucene/spatial/prefix/tree/SpatialPrefixTreeFactory.java
index f5a4bc4..f4992ed 100644
--- a/lucene/spatial/src/java/org/apache/lucene/spatial/prefix/tree/SpatialPrefixTreeFactory.java
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/prefix/tree/SpatialPrefixTreeFactory.java
@@ -52,6 +52,9 @@ public abstract class SpatialPrefixTreeFactory {
instance = new GeohashPrefixTree.Factory();
else if ("quad".equalsIgnoreCase(cname))
instance = new QuadPrefixTree.Factory();
+ else if("flex".equalsIgnoreCase(cname)){
+ instance = new FlexPrefixTree2D.Factory();
+ }
else {
try {
Class c = classLoader.loadClass(cname);
diff --git a/lucene/spatial/src/test/org/apache/lucene/spatial/DistanceStrategyTest.java b/lucene/spatial/src/test/org/apache/lucene/spatial/DistanceStrategyTest.java
index f260502..3173357 100644
--- a/lucene/spatial/src/test/org/apache/lucene/spatial/DistanceStrategyTest.java
+++ b/lucene/spatial/src/test/org/apache/lucene/spatial/DistanceStrategyTest.java
@@ -26,6 +26,7 @@ import org.apache.lucene.document.FieldType;
import org.apache.lucene.spatial.bbox.BBoxStrategy;
import org.apache.lucene.spatial.prefix.RecursivePrefixTreeStrategy;
import org.apache.lucene.spatial.prefix.TermQueryPrefixTreeStrategy;
+import org.apache.lucene.spatial.prefix.tree.FlexPrefixTree2D;
import org.apache.lucene.spatial.prefix.tree.GeohashPrefixTree;
import org.apache.lucene.spatial.prefix.tree.QuadPrefixTree;
import org.apache.lucene.spatial.prefix.tree.SpatialPrefixTree;
@@ -48,10 +49,16 @@ public class DistanceStrategyTest extends StrategyTestCase {
SpatialPrefixTree grid;
SpatialStrategy strategy;
- grid = new QuadPrefixTree(ctx,25);
+ grid = new FlexPrefixTree2D(ctx,25);
strategy = new RecursivePrefixTreeStrategy(grid, "recursive_quad");
ctorArgs.add(new Object[]{new Param(strategy)});
+ grid = new FlexPrefixTree2D(ctx,25);
+ RecursivePrefixTreeStrategy rec_strategy = new RecursivePrefixTreeStrategy(grid, "recursive_flex");
+ rec_strategy.setPruneLeafyBranches(false);
+ strategy = rec_strategy;
+ ctorArgs.add(new Object[]{new Param(strategy)});
+
grid = new GeohashPrefixTree(ctx,12);
strategy = new TermQueryPrefixTreeStrategy(grid, "termquery_geohash");
ctorArgs.add(new Object[]{new Param(strategy)});
diff --git a/lucene/spatial/src/test/org/apache/lucene/spatial/PortedSolr3Test.java b/lucene/spatial/src/test/org/apache/lucene/spatial/PortedSolr3Test.java
index cc3fb02..cf7cab4 100644
--- a/lucene/spatial/src/test/org/apache/lucene/spatial/PortedSolr3Test.java
+++ b/lucene/spatial/src/test/org/apache/lucene/spatial/PortedSolr3Test.java
@@ -28,6 +28,7 @@ import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.spatial.prefix.RecursivePrefixTreeStrategy;
import org.apache.lucene.spatial.prefix.TermQueryPrefixTreeStrategy;
+import org.apache.lucene.spatial.prefix.tree.FlexPrefixTree2D;
import org.apache.lucene.spatial.prefix.tree.GeohashPrefixTree;
import org.apache.lucene.spatial.prefix.tree.QuadPrefixTree;
import org.apache.lucene.spatial.prefix.tree.SpatialPrefixTree;
@@ -62,6 +63,12 @@ public class PortedSolr3Test extends StrategyTestCase {
strategy = new RecursivePrefixTreeStrategy(grid, "recursive_quad");
ctorArgs.add(new Object[]{new Param(strategy)});
+ grid = new FlexPrefixTree2D(ctx,25);
+ RecursivePrefixTreeStrategy rec_strategy = new RecursivePrefixTreeStrategy(grid, "recursive_flex");
+ rec_strategy.setPruneLeafyBranches(false);
+ strategy = rec_strategy;
+ ctorArgs.add(new Object[]{new Param(strategy)});
+
grid = new GeohashPrefixTree(ctx,12);
strategy = new TermQueryPrefixTreeStrategy(grid, "termquery_geohash");
ctorArgs.add(new Object[]{new Param(strategy)});
diff --git a/lucene/spatial/src/test/org/apache/lucene/spatial/QueryEqualsHashCodeTest.java b/lucene/spatial/src/test/org/apache/lucene/spatial/QueryEqualsHashCodeTest.java
index 9eed512..aea2ba5 100644
--- a/lucene/spatial/src/test/org/apache/lucene/spatial/QueryEqualsHashCodeTest.java
+++ b/lucene/spatial/src/test/org/apache/lucene/spatial/QueryEqualsHashCodeTest.java
@@ -22,6 +22,7 @@ import com.spatial4j.core.shape.Shape;
import org.apache.lucene.spatial.bbox.BBoxStrategy;
import org.apache.lucene.spatial.prefix.RecursivePrefixTreeStrategy;
import org.apache.lucene.spatial.prefix.TermQueryPrefixTreeStrategy;
+import org.apache.lucene.spatial.prefix.tree.FlexPrefixTree2D;
import org.apache.lucene.spatial.prefix.tree.GeohashPrefixTree;
import org.apache.lucene.spatial.prefix.tree.QuadPrefixTree;
import org.apache.lucene.spatial.prefix.tree.SpatialPrefixTree;
@@ -44,10 +45,12 @@ public class QueryEqualsHashCodeTest extends LuceneTestCase {
final SpatialPrefixTree gridQuad = new QuadPrefixTree(ctx,10);
final SpatialPrefixTree gridGeohash = new GeohashPrefixTree(ctx,10);
+ final SpatialPrefixTree gridFlex = new FlexPrefixTree2D(ctx,10);
Collection<SpatialStrategy> strategies = new ArrayList<>();
strategies.add(new RecursivePrefixTreeStrategy(gridGeohash, "recursive_geohash"));
strategies.add(new TermQueryPrefixTreeStrategy(gridQuad, "termquery_quad"));
+ strategies.add(new TermQueryPrefixTreeStrategy(gridFlex, "termquery_flex"));
strategies.add(new PointVectorStrategy(ctx, "pointvector"));
strategies.add(new BBoxStrategy(ctx, "bbox"));
strategies.add(new SerializedDVStrategy(ctx, "serialized"));
diff --git a/lucene/spatial/src/test/org/apache/lucene/spatial/StrategyTestCase.java b/lucene/spatial/src/test/org/apache/lucene/spatial/StrategyTestCase.java
index 61a45b4..2a396ea 100644
--- a/lucene/spatial/src/test/org/apache/lucene/spatial/StrategyTestCase.java
+++ b/lucene/spatial/src/test/org/apache/lucene/spatial/StrategyTestCase.java
@@ -250,4 +250,8 @@ public abstract class StrategyTestCase extends SpatialTestCase {
deleteAll();//clean up after ourselves
}
+ public void testSomething(){
+
+ }
+
}
diff --git a/lucene/spatial/src/test/org/apache/lucene/spatial/prefix/JtsPolygonTest.java b/lucene/spatial/src/test/org/apache/lucene/spatial/prefix/JtsPolygonTest.java
index 6dbaa9b..b28e2f7 100644
--- a/lucene/spatial/src/test/org/apache/lucene/spatial/prefix/JtsPolygonTest.java
+++ b/lucene/spatial/src/test/org/apache/lucene/spatial/prefix/JtsPolygonTest.java
@@ -28,6 +28,7 @@ import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.spatial.StrategyTestCase;
+import org.apache.lucene.spatial.prefix.tree.FlexPrefixTree2D;
import org.apache.lucene.spatial.prefix.tree.GeohashPrefixTree;
import org.apache.lucene.spatial.prefix.tree.QuadPrefixTree;
import org.apache.lucene.spatial.prefix.tree.SpatialPrefixTree;
diff --git a/lucene/spatial/src/test/org/apache/lucene/spatial/prefix/RandomSpatialOpFuzzyPrefixTreeTest.java b/lucene/spatial/src/test/org/apache/lucene/spatial/prefix/RandomSpatialOpFuzzyPrefixTreeTest.java
index e302554..7215446 100644
--- a/lucene/spatial/src/test/org/apache/lucene/spatial/prefix/RandomSpatialOpFuzzyPrefixTreeTest.java
+++ b/lucene/spatial/src/test/org/apache/lucene/spatial/prefix/RandomSpatialOpFuzzyPrefixTreeTest.java
@@ -34,7 +34,9 @@ import org.apache.lucene.search.Query;
import org.apache.lucene.spatial.StrategyTestCase;
import org.apache.lucene.spatial.prefix.tree.Cell;
import org.apache.lucene.spatial.prefix.tree.CellIterator;
+import org.apache.lucene.spatial.prefix.tree.FlexPrefixTree2D;
import org.apache.lucene.spatial.prefix.tree.GeohashPrefixTree;
+import org.apache.lucene.spatial.prefix.tree.LegacyCell;
import org.apache.lucene.spatial.prefix.tree.QuadPrefixTree;
import org.apache.lucene.spatial.prefix.tree.SpatialPrefixTree;
import org.apache.lucene.spatial.query.SpatialArgs;
@@ -61,8 +63,10 @@ import static com.spatial4j.core.shape.SpatialRelation.DISJOINT;
import static com.spatial4j.core.shape.SpatialRelation.INTERSECTS;
import static com.spatial4j.core.shape.SpatialRelation.WITHIN;
-/** Randomized PrefixTree test that considers the fuzziness of the
- * results introduced by grid approximation. */
+/**
+ * Randomized PrefixTree test that considers the fuzziness of the
+ * results introduced by grid approximation.
+ */
public class RandomSpatialOpFuzzyPrefixTreeTest extends StrategyTestCase {
static final int ITERATIONS = 10;
@@ -71,16 +75,22 @@ public class RandomSpatialOpFuzzyPrefixTreeTest extends StrategyTestCase {
private SpatialContext ctx2D;
public void setupGrid(int maxLevels) throws IOException {
- if (randomBoolean())
- setupQuadGrid(maxLevels);
- else
- setupGeohashGrid(maxLevels);
+ switch (1) {
+ case 1:
+ setupFlexGrid(maxLevels);
+ ((RecursivePrefixTreeStrategy) strategy).setPruneLeafyBranches(false);
+ break;
+ case 2:
+ setupQuadGrid(maxLevels);
+ ((RecursivePrefixTreeStrategy) strategy).setPruneLeafyBranches(randomBoolean());
+ break;
+ case 3:
+ setupGeohashGrid(maxLevels);
+ ((RecursivePrefixTreeStrategy) strategy).setPruneLeafyBranches(randomBoolean());
+ break;
+ }
setupCtx2D(ctx);
-
//((PrefixTreeStrategy) strategy).setDistErrPct(0);//fully precise to grid
-
- ((RecursivePrefixTreeStrategy)strategy).setPruneLeafyBranches(randomBoolean());
-
System.out.println("Strategy: " + strategy.toString());
}
@@ -108,6 +118,24 @@ public class RandomSpatialOpFuzzyPrefixTreeTest extends StrategyTestCase {
this.strategy = new RecursivePrefixTreeStrategy(grid, getClass().getSimpleName());
}
+ private void setupFlexGrid(int maxLevels) {
+ //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).
+ SpatialContextFactory factory = new SpatialContextFactory();
+ factory.geo = false;
+ factory.worldBounds = new RectangleImpl(0, 256, -128, 128, null);
+ this.ctx = factory.newSpatialContext();
+ //A fairly shallow grid, and default 2.5% distErrPct
+ if (maxLevels == -1)
+ maxLevels = randomIntBetween(1, 8);//max 64k cells (4^8), also 256*256
+ int[] numberOfCells = new int[maxLevels + 1];
+ for (int i = 0; i < maxLevels; ++i) {
+ numberOfCells[i] = randomIntBetween(1, 3);
+ }
+ this.grid = new FlexPrefixTree2D(ctx, maxLevels);
+ this.strategy = new RecursivePrefixTreeStrategy(grid, getClass().getSimpleName());
+ }
+
public void setupGeohashGrid(int maxLevels) {
this.ctx = SpatialContext.GEO;
//A fairly shallow grid, and default 2.5% distErrPct
@@ -138,7 +166,9 @@ public class RandomSpatialOpFuzzyPrefixTreeTest extends StrategyTestCase {
doTest(SpatialOperation.Contains);
}
- /** See LUCENE-5062, {@link ContainsPrefixTreeFilter#multiOverlappingIndexedShapes}. */
+ /**
+ * See LUCENE-5062, {@link ContainsPrefixTreeFilter#multiOverlappingIndexedShapes}.
+ */
@Test
public void testContainsPairOverlap() throws IOException {
setupQuadGrid(3);
@@ -164,7 +194,8 @@ public class RandomSpatialOpFuzzyPrefixTreeTest extends StrategyTestCase {
assertTrue(searchResults.numFound == 0);
}
- @Test /** LUCENE-4916 */
+ @Test
+ /** LUCENE-4916 */
public void testWithinLeafApproxRule() throws IOException {
setupQuadGrid(2);//4x4 grid
//indexed shape will simplify to entire right half (2 top cells)
@@ -178,13 +209,13 @@ public class RandomSpatialOpFuzzyPrefixTreeTest extends StrategyTestCase {
// 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
+ ), 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
+ ), 1).numFound == 1);//match
}
@Test
@@ -207,8 +238,8 @@ public class RandomSpatialOpFuzzyPrefixTreeTest extends StrategyTestCase {
Collection<Shape> shapes;
if (shape instanceof ShapePair) {
shapes = new ArrayList<>(2);
- shapes.add(((ShapePair)shape).shape1);
- shapes.add(((ShapePair)shape).shape2);
+ shapes.add(((ShapePair) shape).shape1);
+ shapes.add(((ShapePair) shape).shape2);
} else {
shapes = Collections.singleton(shape);
}
@@ -284,7 +315,9 @@ public class RandomSpatialOpFuzzyPrefixTreeTest extends StrategyTestCase {
final Shape queryShape;
switch (randomInt(10)) {
- case 0: queryShape = randomPoint(); break;
+ case 0:
+ queryShape = randomPoint();
+ break;
// LUCENE-5549
//TODO debug: -Dtests.method=testWithin -Dtests.multiplier=3 -Dtests.seed=5F5294CE2E075A3E:AAD2F0F79288CA64
// case 1:case 2:case 3:
@@ -292,11 +325,12 @@ public class RandomSpatialOpFuzzyPrefixTreeTest extends StrategyTestCase {
// queryShape = randomShapePairRect(!biasContains);//invert biasContains for query side
// break;
// }
- default: queryShape = randomRectangle();
+ default:
+ queryShape = randomRectangle();
}
final Shape queryShapeGS = gridSnap(queryShape);
- final boolean opIsDisjoint = operation == SpatialOperation.IsDisjointTo;
+ final boolean opIsDisjoint = (operation == SpatialOperation.IsDisjointTo);
//Generate truth via brute force:
// We ensure true-positive matches (if the predicate on the raw shapes match
@@ -349,6 +383,7 @@ public class RandomSpatialOpFuzzyPrefixTreeTest extends StrategyTestCase {
String id = result.getId();
boolean removed = remainingExpectedIds.remove(id);
if (!removed && (!opIsDisjoint && !secondaryIds.contains(id))) {
+ System.out.println(indexedShapes.get(id));
fail("Shouldn't match", id, indexedShapes, indexedShapesGS, queryShape);
}
}
@@ -400,7 +435,12 @@ public class RandomSpatialOpFuzzyPrefixTreeTest extends StrategyTestCase {
Cell cell = cells.next();
if (!cell.isLeaf())
continue;
- cellShapes.add(cell.getShape());
+ if (cell instanceof LegacyCell) {
+ cellShapes.add(cell.getShape());
+ } else {
+ Rectangle rect = (Rectangle) cell.getShape();
+ cellShapes.add(ctx.makeRectangle(rect.getMinX(), rect.getMaxX(), rect.getMinY(), rect.getMaxY()));
+ }
}
return new ShapeCollection<>(cellShapes, ctx).getBoundingBox();
}
@@ -458,21 +498,35 @@ public class RandomSpatialOpFuzzyPrefixTreeTest extends StrategyTestCase {
//See if the correct answer is actually Contains, when the indexed shapes are adjacent,
// creating a larger shape that contains the input shape.
boolean pairTouches = shape1.relate(shape2).intersects();
- if (!pairTouches)
- return r;
+ if (!pairTouches) {
+ if (isAdjacent(shape1, shape2)) {
+ return CONTAINS;
+ } else return r;
+ }
//test all 4 corners
// Note: awkwardly, we use a non-geo context for this because in geo, -180 & +180 are the same place, which means
- // that "other" might wrap the world horizontally and yet all it's corners could be in shape1 (or shape2) even
- // though shape1 is only adjacent to the dateline. I couldn't think of a better way to handle this.
- Rectangle oRect = (Rectangle)other;
+ // that "other" might wrap the world horizontally and yet all it's corners could be in shape1 (or shape2) even
+ // though shape1 is only adjacent to the dateline. I couldn't think of a better way to handle this.
+ Rectangle oRect = (Rectangle) other;
if (cornerContainsNonGeo(oRect.getMinX(), oRect.getMinY())
&& cornerContainsNonGeo(oRect.getMinX(), oRect.getMaxY())
&& cornerContainsNonGeo(oRect.getMaxX(), oRect.getMinY())
- && cornerContainsNonGeo(oRect.getMaxX(), oRect.getMaxY()) )
+ && cornerContainsNonGeo(oRect.getMaxX(), oRect.getMaxY()))
return CONTAINS;
return r;
}
+ private boolean isAdjacent(Shape shape1, Shape shape2) {
+ if (Math.nextAfter(shape1.getBoundingBox().getMaxX(), Double.POSITIVE_INFINITY) == shape2.getBoundingBox().getMinX() || Math.nextAfter(shape2.getBoundingBox().getMaxX(), Double.POSITIVE_INFINITY) == shape1.getBoundingBox().getMinX()) {
+ return true;
+ }
+ if (Math.nextAfter(shape1.getBoundingBox().getMaxY(), Double.POSITIVE_INFINITY) == shape2.getBoundingBox().getMinY() || Math.nextAfter(shape2.getBoundingBox().getMaxY(), Double.POSITIVE_INFINITY) == shape1.getBoundingBox().getMinY()) {
+ return true;
+ }
+
+ return false;
+ }
+
private boolean cornerContainsNonGeo(double x, double y) {
Shape pt = ctx2D.makePoint(x, y);
return shape1_2D.relate(pt).intersects() || shape2_2D.relate(pt).intersects();
diff --git a/lucene/spatial/src/test/org/apache/lucene/spatial/prefix/TestTermQueryPrefixGridStrategy.java b/lucene/spatial/src/test/org/apache/lucene/spatial/prefix/TestTermQueryPrefixGridStrategy.java
index 97c2690..bd79316 100644
--- a/lucene/spatial/src/test/org/apache/lucene/spatial/prefix/TestTermQueryPrefixGridStrategy.java
+++ b/lucene/spatial/src/test/org/apache/lucene/spatial/prefix/TestTermQueryPrefixGridStrategy.java
@@ -24,8 +24,12 @@ import org.apache.lucene.document.Field;
import org.apache.lucene.document.StoredField;
import org.apache.lucene.document.StringField;
import org.apache.lucene.spatial.SpatialTestCase;
+import org.apache.lucene.spatial.prefix.tree.FlexPrefixTree2D;
import org.apache.lucene.spatial.prefix.tree.QuadPrefixTree;
+import org.apache.lucene.spatial.prefix.tree.SpatialPrefixTree;
import org.apache.lucene.spatial.query.SpatialArgsParser;
+import org.junit.Before;
+import org.junit.BeforeClass;
import org.junit.Test;
import java.io.IOException;
@@ -34,10 +38,25 @@ import java.util.Arrays;
public class TestTermQueryPrefixGridStrategy extends SpatialTestCase {
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ this.ctx = SpatialContext.GEO;
+ }
+
@Test
- public void testNGramPrefixGridLosAngeles() throws IOException {
- SpatialContext ctx = SpatialContext.GEO;
- TermQueryPrefixTreeStrategy prefixGridStrategy = new TermQueryPrefixTreeStrategy(new QuadPrefixTree(ctx), "geo");
+ public void testNGramPrefixGridLosAngeles_flex() throws IOException {
+ testNGramPrefixGridLosAngelesWithTrie(new FlexPrefixTree2D(ctx));
+ }
+
+ @Test
+ public void testNGramPrefixGridLosAngeles_quad() throws IOException {
+ testNGramPrefixGridLosAngelesWithTrie(new QuadPrefixTree(ctx));
+ }
+
+ private void testNGramPrefixGridLosAngelesWithTrie(SpatialPrefixTree trie) throws IOException {
+ TermQueryPrefixTreeStrategy prefixGridStrategy = new TermQueryPrefixTreeStrategy(trie, "geo");
Shape point = ctx.makePoint(-118.243680, 34.052230);
diff --git a/lucene/spatial/src/test/org/apache/lucene/spatial/prefix/tree/SpatialPrefixTreeTest.java b/lucene/spatial/src/test/org/apache/lucene/spatial/prefix/tree/SpatialPrefixTreeTest.java
index 9f53546..50aad6a 100644
--- a/lucene/spatial/src/test/org/apache/lucene/spatial/prefix/tree/SpatialPrefixTreeTest.java
+++ b/lucene/spatial/src/test/org/apache/lucene/spatial/prefix/tree/SpatialPrefixTreeTest.java
@@ -17,10 +17,13 @@ package org.apache.lucene.spatial.prefix.tree;
* limitations under the License.
*/
+import com.carrotsearch.randomizedtesting.annotations.Repeat;
import com.spatial4j.core.context.SpatialContext;
+import com.spatial4j.core.context.SpatialContextFactory;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
import com.spatial4j.core.shape.Shape;
+import com.spatial4j.core.shape.ShapeCollection;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.Field.Store;
@@ -38,11 +41,14 @@ import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
+import static com.carrotsearch.randomizedtesting.RandomizedTest.randomIntBetween;
+
public class SpatialPrefixTreeTest extends SpatialTestCase {
//TODO plug in others and test them
private SpatialContext ctx;
private SpatialPrefixTree trie;
+ final int ITERATIONS = 10;
@Override
@Before
@@ -53,7 +59,7 @@ public class SpatialPrefixTreeTest extends SpatialTestCase {
@Test
public void testCellTraverse() {
- trie = new GeohashPrefixTree(ctx,4);
+ trie = new GeohashPrefixTree(ctx, 4);
Cell prevC = null;
Cell c = trie.getWorldCell();
@@ -66,9 +72,9 @@ public class SpatialPrefixTreeTest extends SpatialTestCase {
while (subCellsIter.hasNext()) {
subCells.add(subCellsIter.next());
}
- c = subCells.get(random().nextInt(subCells.size()-1));
-
- assertEquals(prevC.getLevel()+1,c.getLevel());
+ c = subCells.get(random().nextInt(subCells.size() - 1));
+
+ assertEquals(prevC.getLevel() + 1, c.getLevel());
Rectangle prevNShape = (Rectangle) prevC.getShape();
Shape s = c.getShape();
Rectangle sbox = s.getBoundingBox();
@@ -76,6 +82,7 @@ public class SpatialPrefixTreeTest extends SpatialTestCase {
assertTrue(prevNShape.getHeight() > sbox.getHeight());
}
}
+
/**
* A PrefixTree pruning optimization gone bad, applicable when optimize=true.
* See <a href="https://issues.apache.org/jira/browse/LUCENE-4770>LUCENE-4770</a>.
@@ -112,4 +119,89 @@ public class SpatialPrefixTreeTest extends SpatialTestCase {
assertEquals(1, search.totalHits);
}
+ @Test
+ @Repeat(iterations = ITERATIONS)
+ public void testRandomCellRelationship() {
+
+ int maxLevels = randomIntBetween(3, 20);
+ SpatialContextFactory ctxFactory = new SpatialContextFactory();
+ ctxFactory.geo = false;
+ ctxFactory.worldBounds = ctx.getWorldBounds();
+ SpatialContext ctx = ctxFactory.newSpatialContext();
+ assert ctx != null;
+ trie = new FlexPrefixTree2D(ctx, maxLevels);
+ Rectangle WB = ctx.getWorldBounds();
+ Point p = ctx.makePoint(randomIntBetween((int) WB.getMinX(), (int) WB.getMaxX()), randomIntBetween((int) WB.getMinY(), (int) WB.getMaxY()));
+ //Get the world Cell
+ Cell cell = trie.getWorldCell();
+ CellIterator itr = cell.getNextLevelCells(p);
+
+ if (itr.hasNext()) {
+ cell = itr.next();
+ itr = cell.getNextLevelCells(null);
+ while (true) {
+ //Get the cell that contains the point
+ ArrayList<Shape> cells = new ArrayList<Shape>();
+ Shape parent = cell.getShape();
+ while (itr.hasNext()) {
+ Cell c = itr.next();
+ Rectangle s = (Rectangle) c.getShape();
+ cells.add(ctx.makeRectangle(s.getMinX(), s.getMaxX(), s.getMinY(), s.getMaxY()));
+ }
+ Shape children = (new ShapeCollection<>(cells, ctx)).getBoundingBox();
+ assertTrue(children.equals(parent));
+ itr = cell.getNextLevelCells(p);
+ itr.hasNext();
+ cell = itr.next();
+ if (cell.getLevel() >= (maxLevels - 2)) {
+ break;
+ }
+ itr = cell.getNextLevelCells(null);
+ }
+ }
+ }
+/*
+ @Test
+ @Repeat(iterations = ITERATIONS)
+ public void testRandomEdgeCellIntersectionsCount(){
+
+ int maxLevels = randomIntBetween(1,12);
+ SpatialContextFactory ctxFactory = new SpatialContextFactory();
+ ctxFactory.geo = false;
+ ctxFactory.worldBounds = ctx.getWorldBounds();
+ SpatialContext ctx = ctxFactory.newSpatialContext();
+ assert ctx!= null;
+ trie = new FlexPrefixTree2D(ctx,maxLevels);
+ Rectangle WB = ctx.getWorldBounds();
+ double x = WB.getMaxX();
+ double y = WB.getMinY();
+ for(int i=1;i<maxLevels;++i){
+ if(randomBoolean()){
+ x /= 2; //Since FPT takes power of two
+ }
+ if(randomBoolean()){
+ y /= 2;
+ }
+ }
+
+ Point p= ctx.makePoint(x,y);
+ Cell c = trie.getWorldCell();
+ if(maxLevels>1) {
+ checkNoOfMatches(p, c);
+ //Check the centre of the grid TODO make it more points on the edge of the grid
+ p = ctx.makePoint((WB.getMaxX() + WB.getMinX()) / 2, (WB.getMaxY() + WB.getMinY()) / 2);
+ checkNoOfMatches(p, c);
+ }
+ }
+
+ private void checkNoOfMatches(Point p,Cell c){
+ CellIterator itr = c.getNextLevelCells(p);
+ int count=0;
+ while(itr.hasNext()){
+ itr.next();
+ ++count;
+ }
+ assertEquals(1,count);
+
+ }*/
}
\ No newline at end of file