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
