You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by ds...@apache.org on 2016/03/08 02:19:59 UTC

[18/32] lucene-solr git commit: LUCENE-7056: Geo3D package re-org (cherry picked from commit 3a31a8c)

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/0a1951be/lucene/spatial3d/src/test/org/apache/lucene/geo3d/PlaneTest.java
----------------------------------------------------------------------
diff --git a/lucene/spatial3d/src/test/org/apache/lucene/geo3d/PlaneTest.java b/lucene/spatial3d/src/test/org/apache/lucene/geo3d/PlaneTest.java
deleted file mode 100644
index 2ac3856..0000000
--- a/lucene/spatial3d/src/test/org/apache/lucene/geo3d/PlaneTest.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * 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.
- */
-package org.apache.lucene.geo3d;
-
-import org.junit.Test;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-/**
- * Test basic plane functionality.
- */
-public class PlaneTest {
-
-
-  @Test
-  public void testIdenticalPlanes() {
-    final GeoPoint p = new GeoPoint(PlanetModel.SPHERE, 0.123, -0.456);
-    final Plane plane1 = new Plane(p, 0.0);
-    final Plane plane2 = new Plane(p, 0.0);
-    assertTrue(plane1.isNumericallyIdentical(plane2));
-    final Plane plane3 = new Plane(p, 0.1);
-    assertFalse(plane1.isNumericallyIdentical(plane3));
-    final Vector v1 = new Vector(0.1, -0.732, 0.9);
-    final double constant = 0.432;
-    final Vector v2 = new Vector(v1.x * constant, v1.y * constant, v1.z * constant);
-    final Plane p1 = new Plane(v1, 0.2);
-    final Plane p2 = new Plane(v2, 0.2 * constant);
-    assertTrue(p1.isNumericallyIdentical(p2));
-  }
-
-  @Test
-  public void testInterpolation() {
-    // [X=0.35168818443386646, Y=-0.19637966197066342, Z=0.9152870857244183],
-    // [X=0.5003343189532654, Y=0.522128543226148, Z=0.6906861469771293],
-
-    final GeoPoint start = new GeoPoint(0.35168818443386646, -0.19637966197066342, 0.9152870857244183);
-    final GeoPoint end = new GeoPoint(0.5003343189532654, 0.522128543226148, 0.6906861469771293);
-
-    // [A=-0.6135342247741855, B=0.21504338363863665, C=0.28188192383666794, D=0.0, side=-1.0] internal? false;
-    final Plane p = new Plane(-0.6135342247741855, 0.21504338363863665, 0.28188192383666794, 0.0);
-
-    final GeoPoint[] points = p.interpolate(start, end, new double[]{0.25, 0.50, 0.75});
-
-    for (GeoPoint point : points) {
-      assertTrue(p.evaluateIsZero(point));
-    }
-  }
-}
-

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/0a1951be/lucene/spatial3d/src/test/org/apache/lucene/geo3d/TestGeo3DPoint.java
----------------------------------------------------------------------
diff --git a/lucene/spatial3d/src/test/org/apache/lucene/geo3d/TestGeo3DPoint.java b/lucene/spatial3d/src/test/org/apache/lucene/geo3d/TestGeo3DPoint.java
deleted file mode 100644
index 17a4075..0000000
--- a/lucene/spatial3d/src/test/org/apache/lucene/geo3d/TestGeo3DPoint.java
+++ /dev/null
@@ -1,801 +0,0 @@
-/*
- * 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.
- */
-package org.apache.lucene.geo3d;
-
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import org.apache.lucene.codecs.Codec;
-import org.apache.lucene.codecs.FilterCodec;
-import org.apache.lucene.codecs.PointsFormat;
-import org.apache.lucene.codecs.PointsReader;
-import org.apache.lucene.codecs.PointsWriter;
-import org.apache.lucene.codecs.lucene60.Lucene60PointsReader;
-import org.apache.lucene.codecs.lucene60.Lucene60PointsWriter;
-import org.apache.lucene.document.Document;
-import org.apache.lucene.document.Field;
-import org.apache.lucene.document.NumericDocValuesField;
-import org.apache.lucene.index.DirectoryReader;
-import org.apache.lucene.index.IndexReader;
-import org.apache.lucene.index.IndexWriter;
-import org.apache.lucene.index.IndexWriterConfig;
-import org.apache.lucene.index.LeafReaderContext;
-import org.apache.lucene.index.MultiDocValues;
-import org.apache.lucene.index.NumericDocValues;
-import org.apache.lucene.index.SegmentReadState;
-import org.apache.lucene.index.SegmentWriteState;
-import org.apache.lucene.index.Term;
-import org.apache.lucene.search.IndexSearcher;
-import org.apache.lucene.search.Query;
-import org.apache.lucene.search.SimpleCollector;
-import org.apache.lucene.store.Directory;
-import org.apache.lucene.store.MockDirectoryWrapper;
-import org.apache.lucene.util.FixedBitSet;
-import org.apache.lucene.util.IOUtils;
-import org.apache.lucene.util.LuceneTestCase;
-import org.apache.lucene.util.TestUtil;
-import org.junit.BeforeClass;
-
-import com.carrotsearch.randomizedtesting.generators.RandomInts;
-
-public class TestGeo3DPoint extends LuceneTestCase {
-
-  private static boolean smallBBox;
-  
-  @BeforeClass
-  public static void beforeClass() {
-    smallBBox = random().nextBoolean();
-    if (VERBOSE) {
-      System.err.println("TEST: smallBBox=" + smallBBox);
-    }
-  }
-
-  private static Codec getCodec() {
-    if (Codec.getDefault().getName().equals("Lucene60")) {
-      int maxPointsInLeafNode = TestUtil.nextInt(random(), 16, 2048);
-      double maxMBSortInHeap = 3.0 + (3*random().nextDouble());
-      if (VERBOSE) {
-        System.out.println("TEST: using Lucene60PointsFormat with maxPointsInLeafNode=" + maxPointsInLeafNode + " and maxMBSortInHeap=" + maxMBSortInHeap);
-      }
-
-      return new FilterCodec("Lucene60", Codec.getDefault()) {
-        @Override
-        public PointsFormat pointsFormat() {
-          return new PointsFormat() {
-            @Override
-            public PointsWriter fieldsWriter(SegmentWriteState writeState) throws IOException {
-              return new Lucene60PointsWriter(writeState, maxPointsInLeafNode, maxMBSortInHeap);
-            }
-
-            @Override
-            public PointsReader fieldsReader(SegmentReadState readState) throws IOException {
-              return new Lucene60PointsReader(readState);
-            }
-          };
-        }
-      };
-    } else {
-      return Codec.getDefault();
-    }
-  }
-
-  public void testBasic() throws Exception {
-    Directory dir = getDirectory();
-    IndexWriterConfig iwc = newIndexWriterConfig();
-    iwc.setCodec(getCodec());
-    IndexWriter w = new IndexWriter(dir, iwc);
-    Document doc = new Document();
-    doc.add(new Geo3DPoint("field", toRadians(50.7345267), toRadians(-97.5303555)));
-    w.addDocument(doc);
-    IndexReader r = DirectoryReader.open(w);
-    // We can't wrap with "exotic" readers because the query must see the BKD3DDVFormat:
-    IndexSearcher s = newSearcher(r, false);
-    assertEquals(1, s.search(Geo3DPoint.newShapeQuery("field",
-                                                      GeoCircleFactory.makeGeoCircle(PlanetModel.WGS84, toRadians(50), toRadians(-97), Math.PI/180.)), 1).totalHits);
-    w.close();
-    r.close();
-    dir.close();
-  }
-
-  private static double toRadians(double degrees) {
-    return Math.PI*(degrees/360.0);
-  }
-
-  private static PlanetModel getPlanetModel() {
-    if (random().nextBoolean()) {
-      // Use one of the earth models:
-      if (random().nextBoolean()) {
-        return PlanetModel.WGS84;
-      } else {
-        return PlanetModel.SPHERE;
-      }
-    } else {
-      // Make a randomly squashed planet:
-      double oblateness = random().nextDouble() * 0.5 - 0.25;
-      return new PlanetModel(1.0 + oblateness, 1.0 - oblateness);
-    }
-  }
-
-  private static class Cell {
-    static int nextCellID;
-
-    final Cell parent;
-    final int cellID;
-    final int xMinEnc, xMaxEnc;
-    final int yMinEnc, yMaxEnc;
-    final int zMinEnc, zMaxEnc;
-    final int splitCount;
-
-    public Cell(Cell parent,
-                int xMinEnc, int xMaxEnc,
-                int yMinEnc, int yMaxEnc,
-                int zMinEnc, int zMaxEnc,
-                int splitCount) {
-      this.parent = parent;
-      this.xMinEnc = xMinEnc;
-      this.xMaxEnc = xMaxEnc;
-      this.yMinEnc = yMinEnc;
-      this.yMaxEnc = yMaxEnc;
-      this.zMinEnc = zMinEnc;
-      this.zMaxEnc = zMaxEnc;
-      this.cellID = nextCellID++;
-      this.splitCount = splitCount;
-    }
-
-    /** Returns true if the quantized point lies within this cell, inclusive on all bounds. */
-    public boolean contains(double planetMax, GeoPoint point) {
-      int docX = Geo3DUtil.encodeValue(planetMax, point.x);
-      int docY = Geo3DUtil.encodeValue(planetMax, point.y);
-      int docZ = Geo3DUtil.encodeValue(planetMax, point.z);
-
-      return docX >= xMinEnc && docX <= xMaxEnc &&
-        docY >= yMinEnc && docY <= yMaxEnc && 
-        docZ >= zMinEnc && docZ <= zMaxEnc;
-    }
-
-    @Override
-    public String toString() {
-      return "cell=" + cellID + (parent == null ? "" : " parentCellID=" + parent.cellID) + " x: " + xMinEnc + " TO " + xMaxEnc + ", y: " + yMinEnc + " TO " + yMaxEnc + ", z: " + zMinEnc + " TO " + zMaxEnc + ", splits: " + splitCount;
-    }
-  }
-
-  private static GeoPoint quantize(double planetMax, GeoPoint point) {
-    return new GeoPoint(Geo3DUtil.decodeValueCenter(planetMax, Geo3DUtil.encodeValue(planetMax, point.x)),
-                        Geo3DUtil.decodeValueCenter(planetMax, Geo3DUtil.encodeValue(planetMax, point.y)),
-                        Geo3DUtil.decodeValueCenter(planetMax, Geo3DUtil.encodeValue(planetMax, point.z)));
-  }
-
-  /** Tests consistency of GeoArea.getRelationship vs GeoShape.isWithin */
-  public void testGeo3DRelations() throws Exception {
-
-    PlanetModel planetModel = getPlanetModel();
-
-    int numDocs = atLeast(1000);
-    if (VERBOSE) {
-      System.out.println("TEST: " + numDocs + " docs");
-    }
-
-    GeoPoint[] docs = new GeoPoint[numDocs];
-    for(int docID=0;docID<numDocs;docID++) {
-      docs[docID] = new GeoPoint(planetModel, toRadians(randomLat()), toRadians(randomLon()));
-      if (VERBOSE) {
-        System.out.println("  doc=" + docID + ": " + docs[docID]);
-      }
-    }
-
-    double planetMax = planetModel.getMaximumMagnitude();
-
-    int iters = atLeast(10);
-
-    int recurseDepth = RandomInts.randomIntBetween(random(), 5, 15);
-
-    iters = atLeast(50);
-    
-    for(int iter=0;iter<iters;iter++) {
-      GeoShape shape = randomShape(planetModel);
-
-      StringWriter sw = new StringWriter();
-      PrintWriter log = new PrintWriter(sw, true);
-
-      if (VERBOSE) {
-        log.println("TEST: iter=" + iter + " shape=" + shape);
-      }
-
-      XYZBounds bounds = new XYZBounds();
-      shape.getBounds(bounds);
-
-      // Start with the root cell that fully contains the shape:
-      Cell root = new Cell(null,
-                           Geo3DUtil.encodeValueLenient(planetMax, bounds.getMinimumX()),
-                           Geo3DUtil.encodeValueLenient(planetMax, bounds.getMaximumX()),
-                           Geo3DUtil.encodeValueLenient(planetMax, bounds.getMinimumY()),
-                           Geo3DUtil.encodeValueLenient(planetMax, bounds.getMaximumY()),
-                           Geo3DUtil.encodeValueLenient(planetMax, bounds.getMinimumZ()),
-                           Geo3DUtil.encodeValueLenient(planetMax, bounds.getMaximumZ()),
-                           0);
-
-      if (VERBOSE) {
-        log.println("  root cell: " + root);
-      }
-
-      List<Cell> queue = new ArrayList<>();
-      queue.add(root);
-      Set<Integer> hits = new HashSet<>();
-
-      while (queue.size() > 0) {
-        Cell cell = queue.get(queue.size()-1);
-        queue.remove(queue.size()-1);
-        if (VERBOSE) {
-          log.println("  cycle: " + cell + " queue.size()=" + queue.size());
-        }
-
-        if (random().nextInt(10) == 7 || cell.splitCount > recurseDepth) {
-          if (VERBOSE) {
-            log.println("    leaf");
-          }
-          // Leaf cell: brute force check all docs that fall within this cell:
-          for(int docID=0;docID<numDocs;docID++) {
-            GeoPoint point = docs[docID];
-            if (cell.contains(planetMax, point)) {
-              if (shape.isWithin(quantize(planetMax, point))) {
-                if (VERBOSE) {
-                  log.println("    check doc=" + docID + ": match!");
-                }
-                hits.add(docID);
-              } else {
-                if (VERBOSE) {
-                  log.println("    check doc=" + docID + ": no match");
-                }
-              }
-            }
-          }
-        } else {
-          
-          GeoArea xyzSolid = GeoAreaFactory.makeGeoArea(planetModel,
-                                                        Geo3DUtil.decodeValueMin(planetMax, cell.xMinEnc), Geo3DUtil.decodeValueMax(planetMax, cell.xMaxEnc),
-                                                        Geo3DUtil.decodeValueMin(planetMax, cell.yMinEnc), Geo3DUtil.decodeValueMax(planetMax, cell.yMaxEnc),
-                                                        Geo3DUtil.decodeValueMin(planetMax, cell.zMinEnc), Geo3DUtil.decodeValueMax(planetMax, cell.zMaxEnc));
-
-          if (VERBOSE) {
-            log.println("    minx="+Geo3DUtil.decodeValueMin(planetMax, cell.xMinEnc)+" maxx="+Geo3DUtil.decodeValueMax(planetMax, cell.xMaxEnc)+
-              " miny="+Geo3DUtil.decodeValueMin(planetMax, cell.yMinEnc)+" maxy="+Geo3DUtil.decodeValueMax(planetMax, cell.yMaxEnc)+
-              " minz="+Geo3DUtil.decodeValueMin(planetMax, cell.zMinEnc)+" maxz="+Geo3DUtil.decodeValueMax(planetMax, cell.zMaxEnc));
-          }
-
-          switch (xyzSolid.getRelationship(shape)) {          
-          case GeoArea.CONTAINS:
-            // Shape fully contains the cell: blindly add all docs in this cell:
-            if (VERBOSE) {
-              log.println("    GeoArea.CONTAINS: now addAll");
-            }
-            for(int docID=0;docID<numDocs;docID++) {
-              if (cell.contains(planetMax, docs[docID])) {
-                if (VERBOSE) {
-                  log.println("    addAll doc=" + docID);
-                }
-                hits.add(docID);
-              }
-            }
-            continue;
-          case GeoArea.OVERLAPS:
-            if (VERBOSE) {
-              log.println("    GeoArea.OVERLAPS: keep splitting");
-            }
-            // They do overlap but neither contains the other:
-            //log.println("    crosses1");
-            break;
-          case GeoArea.WITHIN:
-            if (VERBOSE) {
-              log.println("    GeoArea.WITHIN: keep splitting");
-            }
-            // Cell fully contains the shape:
-            //log.println("    crosses2");
-            break;
-          case GeoArea.DISJOINT:
-            // They do not overlap at all: don't recurse on this cell
-            //log.println("    outside");
-            if (VERBOSE) {
-              log.println("    GeoArea.DISJOINT: drop this cell");
-              for(int docID=0;docID<numDocs;docID++) {
-                if (cell.contains(planetMax, docs[docID])) {
-                  if (VERBOSE) {
-                    log.println("    skip doc=" + docID);
-                  }
-                }
-              }
-            }
-            continue;
-          default:
-            assert false;
-          }
-
-          // Randomly split:
-          switch(random().nextInt(3)) {
-
-          case 0:
-            // Split on X:
-            {
-              int splitValue = RandomInts.randomIntBetween(random(), cell.xMinEnc, cell.xMaxEnc);
-              if (VERBOSE) {
-                log.println("    now split on x=" + splitValue);
-              }
-              Cell cell1 = new Cell(cell,
-                                 cell.xMinEnc, splitValue,
-                                 cell.yMinEnc, cell.yMaxEnc,
-                                 cell.zMinEnc, cell.zMaxEnc,
-                                 cell.splitCount+1);
-              Cell cell2 = new Cell(cell,
-                                 splitValue, cell.xMaxEnc,
-                                 cell.yMinEnc, cell.yMaxEnc,
-                                 cell.zMinEnc, cell.zMaxEnc,
-                                 cell.splitCount+1);
-              if (VERBOSE) {
-                log.println("    split cell1: " + cell1);
-                log.println("    split cell2: " + cell2);
-              }
-              queue.add(cell1);
-              queue.add(cell2);
-            }
-            break;
-
-          case 1:
-            // Split on Y:
-            {
-              int splitValue = RandomInts.randomIntBetween(random(), cell.yMinEnc, cell.yMaxEnc);
-              if (VERBOSE) {
-                log.println("    now split on y=" + splitValue);
-              }
-              Cell cell1 = new Cell(cell,
-                                 cell.xMinEnc, cell.xMaxEnc,
-                                 cell.yMinEnc, splitValue,
-                                 cell.zMinEnc, cell.zMaxEnc,
-                                 cell.splitCount+1);
-              Cell cell2 = new Cell(cell,
-                                 cell.xMinEnc, cell.xMaxEnc,
-                                 splitValue, cell.yMaxEnc,
-                                 cell.zMinEnc, cell.zMaxEnc,
-                                 cell.splitCount+1);
-              if (VERBOSE) {
-                log.println("    split cell1: " + cell1);
-                log.println("    split cell2: " + cell2);
-              }
-              queue.add(cell1);
-              queue.add(cell2);
-            }
-            break;
-
-          case 2:
-            // Split on Z:
-            {
-              int splitValue = RandomInts.randomIntBetween(random(), cell.zMinEnc, cell.zMaxEnc);
-              if (VERBOSE) {
-                log.println("    now split on z=" + splitValue);
-              }
-              Cell cell1 = new Cell(cell,
-                                 cell.xMinEnc, cell.xMaxEnc,
-                                 cell.yMinEnc, cell.yMaxEnc,
-                                 cell.zMinEnc, splitValue,
-                                 cell.splitCount+1);
-              Cell cell2 = new Cell(cell,
-                                 cell.xMinEnc, cell.xMaxEnc,
-                                 cell.yMinEnc, cell.yMaxEnc,
-                                 splitValue, cell.zMaxEnc,
-                                 cell.splitCount+1);
-              if (VERBOSE) {
-                log.println("    split cell1: " + cell1);
-                log.println("    split cell2: " + cell2);
-              }
-              queue.add(cell1);
-              queue.add(cell2);
-            }
-            break;
-          }
-        }
-      }
-
-      if (VERBOSE) {
-        log.println("  " + hits.size() + " hits");
-      }
-
-      // Done matching, now verify:
-      boolean fail = false;
-      for(int docID=0;docID<numDocs;docID++) {
-        GeoPoint point = docs[docID];
-        GeoPoint quantized = quantize(planetMax, point);
-        boolean expected = shape.isWithin(quantized);
-
-        if (expected != shape.isWithin(point)) {
-          // Quantization changed the result; skip testing this doc:
-          continue;
-        }
-
-        boolean actual = hits.contains(docID);
-        if (actual != expected) {
-          if (actual) {
-            log.println("doc=" + docID + " matched but should not");
-          } else {
-            log.println("doc=" + docID + " did not match but should");
-          }
-          log.println("  point=" + docs[docID]);
-          log.println("  quantized=" + quantize(planetMax, docs[docID]));
-          fail = true;
-        }
-      }
-
-      if (fail) {
-        System.out.print(sw.toString());
-        fail("invalid hits for shape=" + shape);
-      }
-    }
-  }
-
-  public void testRandomTiny() throws Exception {
-    // Make sure single-leaf-node case is OK:
-    doTestRandom(10);
-  }
-
-  public void testRandomMedium() throws Exception {
-    doTestRandom(10000);
-  }
-
-  @Nightly
-  public void testRandomBig() throws Exception {
-    doTestRandom(200000);
-  }
-
-  private void doTestRandom(int count) throws Exception {
-    int numPoints = atLeast(count);
-
-    if (VERBOSE) {
-      System.err.println("TEST: numPoints=" + numPoints);
-    }
-
-    double[] lats = new double[numPoints];
-    double[] lons = new double[numPoints];
-
-    boolean haveRealDoc = false;
-
-    for (int docID=0;docID<numPoints;docID++) {
-      int x = random().nextInt(20);
-      if (x == 17) {
-        // Some docs don't have a point:
-        lats[docID] = Double.NaN;
-        if (VERBOSE) {
-          System.err.println("  doc=" + docID + " is missing");
-        }
-        continue;
-      }
-
-      if (docID > 0 && x < 3 && haveRealDoc) {
-        int oldDocID;
-        while (true) {
-          oldDocID = random().nextInt(docID);
-          if (Double.isNaN(lats[oldDocID]) == false) {
-            break;
-          }
-        }
-            
-        if (x == 0) {
-          // Identical lat to old point
-          lats[docID] = lats[oldDocID];
-          lons[docID] = toRadians(randomLon());
-          if (VERBOSE) {
-            System.err.println("  doc=" + docID + " lat=" + lats[docID] + " lon=" + lons[docID] + " (same lat as doc=" + oldDocID + ")");
-          }
-        } else if (x == 1) {
-          // Identical lon to old point
-          lats[docID] = toRadians(randomLat());
-          lons[docID] = lons[oldDocID];
-          if (VERBOSE) {
-            System.err.println("  doc=" + docID + " lat=" + lats[docID] + " lon=" + lons[docID] + " (same lon as doc=" + oldDocID + ")");
-          }
-        } else {
-          assert x == 2;
-          // Fully identical point:
-          lats[docID] = lats[oldDocID];
-          lons[docID] = lons[oldDocID];
-          if (VERBOSE) {
-            System.err.println("  doc=" + docID + " lat=" + lats[docID] + " lon=" + lons[docID] + " (same lat/lon as doc=" + oldDocID + ")");
-          }
-        }
-      } else {
-        lats[docID] = toRadians(randomLat());
-        lons[docID] = toRadians(randomLon());
-        haveRealDoc = true;
-        if (VERBOSE) {
-          System.err.println("  doc=" + docID + " lat=" + lats[docID] + " lon=" + lons[docID]);
-        }
-      }
-    }
-
-    verify(lats, lons);
-  }
-
-  private static double randomLat() {
-    if (smallBBox) {
-      return 2.0 * (random().nextDouble()-0.5);
-    } else {
-      return -90 + 180.0 * random().nextDouble();
-    }
-  }
-
-  private static double randomLon() {
-    if (smallBBox) {
-      return 2.0 * (random().nextDouble()-0.5);
-    } else {
-      return -180 + 360.0 * random().nextDouble();
-    }
-  }
-
-  // Poached from Geo3dRptTest.randomShape:
-  private static GeoShape randomShape(PlanetModel planetModel) {
-    while (true) {
-      final int shapeType = random().nextInt(4);
-      switch (shapeType) {
-      case 0: {
-        // Polygons
-        final int vertexCount = random().nextInt(3) + 3;
-        final List<GeoPoint> geoPoints = new ArrayList<>();
-        while (geoPoints.size() < vertexCount) {
-          final GeoPoint gPt = new GeoPoint(planetModel, toRadians(randomLat()), toRadians(randomLon()));
-          geoPoints.add(gPt);
-        }
-        final int convexPointIndex = random().nextInt(vertexCount);       //If we get this wrong, hopefully we get IllegalArgumentException
-        try {
-          return GeoPolygonFactory.makeGeoPolygon(planetModel, geoPoints, convexPointIndex);
-        } catch (IllegalArgumentException e) {
-          // This is what happens when we create a shape that is invalid.  Although it is conceivable that there are cases where
-          // the exception is thrown incorrectly, we aren't going to be able to do that in this random test.
-          continue;
-        }
-      }
-
-      case 1: {
-        // Circles
-
-        double lat = toRadians(randomLat());
-        double lon = toRadians(randomLon());
-
-        double angle;
-        if (smallBBox) {
-          angle = random().nextDouble() * Math.PI/360.0;
-        } else {
-          angle = random().nextDouble() * Math.PI/2.0;
-        }
-
-        try {
-          return GeoCircleFactory.makeGeoCircle(planetModel, lat, lon, angle);
-        } catch (IllegalArgumentException iae) {
-          // angle is too small; try again:
-          continue;
-        }
-      }
-
-      case 2: {
-        // Rectangles
-        double lat0 = toRadians(randomLat());
-        double lat1 = toRadians(randomLat());
-        if (lat1 < lat0) {
-          double x = lat0;
-          lat0 = lat1;
-          lat1 = x;
-        }
-        double lon0 = toRadians(randomLon());
-        double lon1 = toRadians(randomLon());
-        if (lon1 < lon0) {
-          double x = lon0;
-          lon0 = lon1;
-          lon1 = x;
-        }
-
-        return GeoBBoxFactory.makeGeoBBox(planetModel, lat1, lat0, lon0, lon1);
-      }
-
-      case 3: {
-        // Paths
-        final int pointCount = random().nextInt(5) + 1;
-        final double width = toRadians(random().nextInt(89)+1);
-        try {
-          final GeoPath path = new GeoPath(planetModel, width);
-          for (int i = 0; i < pointCount; i++) {
-            path.addPoint(toRadians(randomLat()), toRadians(randomLon()));
-          }
-          path.done();
-          return path;
-        } catch (IllegalArgumentException e) {
-          // This is what happens when we create a shape that is invalid.  Although it is conceivable that there are cases where
-          // the exception is thrown incorrectly, we aren't going to be able to do that in this random test.
-          continue;
-        }
-      }
-
-      default:
-        throw new IllegalStateException("Unexpected shape type");
-      }
-    }
-  }
-
-  private static void verify(double[] lats, double[] lons) throws Exception {
-    IndexWriterConfig iwc = newIndexWriterConfig();
-
-    // Else we can get O(N^2) merging:
-    int mbd = iwc.getMaxBufferedDocs();
-    if (mbd != -1 && mbd < lats.length/100) {
-      iwc.setMaxBufferedDocs(lats.length/100);
-    }
-    iwc.setCodec(getCodec());
-    Directory dir;
-    if (lats.length > 100000) {
-      dir = newFSDirectory(createTempDir("TestBKDTree"));
-    } else {
-      dir = getDirectory();
-    }
-    Set<Integer> deleted = new HashSet<>();
-    // RandomIndexWriter is too slow here:
-    IndexWriter w = new IndexWriter(dir, iwc);
-    for(int id=0;id<lats.length;id++) {
-      Document doc = new Document();
-      doc.add(newStringField("id", ""+id, Field.Store.NO));
-      doc.add(new NumericDocValuesField("id", id));
-      if (Double.isNaN(lats[id]) == false) {
-        doc.add(new Geo3DPoint("point", lats[id], lons[id]));
-      }
-      w.addDocument(doc);
-      if (id > 0 && random().nextInt(100) == 42) {
-        int idToDelete = random().nextInt(id);
-        w.deleteDocuments(new Term("id", ""+idToDelete));
-        deleted.add(idToDelete);
-        if (VERBOSE) {
-          System.err.println("  delete id=" + idToDelete);
-        }
-      }
-    }
-    if (random().nextBoolean()) {
-      w.forceMerge(1);
-    }
-    final IndexReader r = DirectoryReader.open(w);
-    w.close();
-
-    // We can't wrap with "exotic" readers because the geo3d query must see the Geo3DDVFormat:
-    IndexSearcher s = newSearcher(r, false);
-
-    int numThreads = TestUtil.nextInt(random(), 2, 5);
-
-    List<Thread> threads = new ArrayList<>();
-    final int iters = atLeast(100);
-
-    final CountDownLatch startingGun = new CountDownLatch(1);
-    final AtomicBoolean failed = new AtomicBoolean();
-
-    for(int i=0;i<numThreads;i++) {
-      Thread thread = new Thread() {
-          @Override
-          public void run() {
-            try {
-              _run();
-            } catch (Exception e) {
-              failed.set(true);
-              throw new RuntimeException(e);
-            }
-          }
-
-          private void _run() throws Exception {
-            startingGun.await();
-
-            NumericDocValues docIDToID = MultiDocValues.getNumericValues(r, "id");
-
-            for (int iter=0;iter<iters && failed.get() == false;iter++) {
-
-              GeoShape shape = randomShape(PlanetModel.WGS84);
-
-              if (VERBOSE) {
-                System.err.println("\n" + Thread.currentThread() + ": TEST: iter=" + iter + " shape="+shape);
-              }
-              
-              Query query = Geo3DPoint.newShapeQuery("point", shape);
-
-              if (VERBOSE) {
-                System.err.println("  using query: " + query);
-              }
-
-              final FixedBitSet hits = new FixedBitSet(r.maxDoc());
-
-              s.search(query, new SimpleCollector() {
-
-                  private int docBase;
-
-                  @Override
-                  public boolean needsScores() {
-                    return false;
-                  }
-
-                  @Override
-                  protected void doSetNextReader(LeafReaderContext context) throws IOException {
-                    docBase = context.docBase;
-                  }
-
-                  @Override
-                  public void collect(int doc) {
-                    hits.set(docBase+doc);
-                  }
-                });
-
-              if (VERBOSE) {
-                System.err.println("  hitCount: " + hits.cardinality());
-              }
-      
-              for(int docID=0;docID<r.maxDoc();docID++) {
-                int id = (int) docIDToID.get(docID);
-                if (Double.isNaN(lats[id]) == false) {
-
-                  // Accurate point:
-                  GeoPoint point1 = new GeoPoint(PlanetModel.WGS84, lats[id], lons[id]);
-
-                  // Quantized point (32 bits per dim):
-                  GeoPoint point2 = quantize(PlanetModel.WGS84.getMaximumMagnitude(), point1);
-
-                  if (shape.isWithin(point1) != shape.isWithin(point2)) {
-                    if (VERBOSE) {
-                      System.out.println("  skip checking docID=" + docID + " quantization changed the expected result from " + shape.isWithin(point1) + " to " + shape.isWithin(point2));
-                    }
-                    continue;
-                  }
-
-                  boolean expected = ((deleted.contains(id) == false) && shape.isWithin(point2));
-                  if (hits.get(docID) != expected) {
-                    fail(Thread.currentThread().getName() + ": iter=" + iter + " id=" + id + " docID=" + docID + " lat=" + lats[id] + " lon=" + lons[id] + " expected " + expected + " but got: " + hits.get(docID) + " deleted?=" + deleted.contains(id) + "\n  point1=" + point1 + ", iswithin="+shape.isWithin(point1)+"\n  point2=" + point2 + ", iswithin="+shape.isWithin(point2) + "\n  query=" + query);
-                  }
-                } else {
-                  assertFalse(hits.get(docID));
-                }
-
-              }
-            }
-          }
-        };
-      thread.setName("T" + i);
-      thread.start();
-      threads.add(thread);
-    }
-    startingGun.countDown();
-    for(Thread thread : threads) {
-      thread.join();
-    }
-    IOUtils.close(r, dir);
-  }
-
-  public void testToString() {
-    Geo3DPoint point = new Geo3DPoint("point", toRadians(44.244272), toRadians(7.769736));
-    assertEquals("Geo3DPoint <point: x=0.9248467864160119 y=0.06280434265368656 z=0.37682349005486243>", point.toString());
-  }
-
-  public void testShapeQueryToString() {
-    assertEquals("PointInGeo3DShapeQuery: field=point: Shape: GeoStandardCircle: {planetmodel=PlanetModel.WGS84, center=[lat=0.3861041107739683, lon=0.06780373760536706], radius=0.1(5.729577951308232)}",
-                 Geo3DPoint.newShapeQuery("point", GeoCircleFactory.makeGeoCircle(PlanetModel.WGS84, toRadians(44.244272), toRadians(7.769736), 0.1)).toString());
-  }
-
-  private static Directory getDirectory() {     
-    return newDirectory();
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/0a1951be/lucene/spatial3d/src/test/org/apache/lucene/geo3d/XYZSolidTest.java
----------------------------------------------------------------------
diff --git a/lucene/spatial3d/src/test/org/apache/lucene/geo3d/XYZSolidTest.java b/lucene/spatial3d/src/test/org/apache/lucene/geo3d/XYZSolidTest.java
deleted file mode 100644
index 876a525..0000000
--- a/lucene/spatial3d/src/test/org/apache/lucene/geo3d/XYZSolidTest.java
+++ /dev/null
@@ -1,220 +0,0 @@
-/*
- * 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.
- */
-package org.apache.lucene.geo3d;
-
-import org.apache.lucene.util.LuceneTestCase;
-import org.junit.Test;
-
-public class XYZSolidTest extends LuceneTestCase {
-
-  @Test
-  public void testNonDegenerateRelationships() {
-    XYZSolid s;
-    GeoShape shape;
-    // Something bigger than the world
-    s = new StandardXYZSolid(PlanetModel.SPHERE, -2.0, 2.0, -2.0, 2.0, -2.0, 2.0);
-    // Any shape, except whole world, should be within.
-    shape = new GeoStandardCircle(PlanetModel.SPHERE, 0.0, 0.0, 0.1);
-    assertEquals(GeoArea.WITHIN, s.getRelationship(shape));
-    shape = new GeoWorld(PlanetModel.SPHERE);
-    // An XYZSolid represents a surface shape, which when larger than the world is in fact
-    // the entire world, so it should overlap the world.
-    assertEquals(GeoArea.OVERLAPS, s.getRelationship(shape));
-
-    // Something overlapping the world on only one side
-    s = new StandardXYZSolid(PlanetModel.SPHERE, -2.0, 0.0, -2.0, 2.0, -2.0, 2.0);
-    // Some things should be disjoint...
-    shape = new GeoStandardCircle(PlanetModel.SPHERE, 0.0, 0.0, 0.1);
-    assertEquals(GeoArea.DISJOINT, s.getRelationship(shape));
-    // And, some things should be within... 
-    shape = new GeoStandardCircle(PlanetModel.SPHERE, 0.0, Math.PI, 0.1);
-    assertEquals(GeoArea.WITHIN, s.getRelationship(shape));
-    // And, some things should overlap.
-    shape = new GeoStandardCircle(PlanetModel.SPHERE, 0.0, Math.PI * 0.5, 0.1);
-    assertEquals(GeoArea.OVERLAPS, s.getRelationship(shape));
-
-    // Partial world should be contained by GeoWorld object...
-    shape = new GeoWorld(PlanetModel.SPHERE);
-    assertEquals(GeoArea.CONTAINS, s.getRelationship(shape));
-    
-    // Something inside the world
-    s = new StandardXYZSolid(PlanetModel.SPHERE, -0.1, 0.1, -0.1, 0.1, -0.1, 0.1);
-    // All shapes should be disjoint
-    shape = new GeoStandardCircle(PlanetModel.SPHERE, 0.0, 0.0, 0.1);
-    assertEquals(GeoArea.DISJOINT, s.getRelationship(shape));
-    shape = new GeoWorld(PlanetModel.SPHERE);
-    assertEquals(GeoArea.DISJOINT, s.getRelationship(shape));
-    
-  }
-
-  @Test
-  public void testDegenerateRelationships() {
-    GeoArea solid;
-    GeoShape shape;
-    
-    // Basic test of the factory method - non-degenerate
-    solid = GeoAreaFactory.makeGeoArea(PlanetModel.SPHERE, -2.0, 2.0, -2.0, 2.0, -2.0, 2.0);
-    // Any shape, except whole world, should be within.
-    shape = new GeoStandardCircle(PlanetModel.SPHERE, 0.0, 0.0, 0.1);
-    assertEquals(GeoArea.WITHIN, solid.getRelationship(shape));
-    shape = new GeoWorld(PlanetModel.SPHERE);
-    // An XYZSolid represents a surface shape, which when larger than the world is in fact
-    // the entire world, so it should overlap the world.
-    assertEquals(GeoArea.OVERLAPS, solid.getRelationship(shape));
-
-    // Build a degenerate point, not on sphere
-    solid = GeoAreaFactory.makeGeoArea(PlanetModel.SPHERE, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
-    // disjoint with everything?
-    shape = new GeoStandardCircle(PlanetModel.SPHERE, 0.0, 0.0, 0.1);
-    assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));
-    shape = new GeoWorld(PlanetModel.SPHERE);
-    assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));
-
-    // Build a degenerate point that IS on the sphere
-    solid = GeoAreaFactory.makeGeoArea(PlanetModel.SPHERE, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0);
-    // inside everything that it touches?
-    shape = new GeoStandardCircle(PlanetModel.SPHERE, 0.0, 0.0, 0.1);
-    assertEquals(GeoArea.CONTAINS, solid.getRelationship(shape));
-    shape = new GeoWorld(PlanetModel.SPHERE);
-    assertEquals(GeoArea.CONTAINS, solid.getRelationship(shape));
-    shape = new GeoStandardCircle(PlanetModel.SPHERE, 0.0, Math.PI, 0.1);
-    assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));
-
-    // Build a shape degenerate in (x,y), which has no points on sphere
-    solid = GeoAreaFactory.makeGeoArea(PlanetModel.SPHERE, 0.0, 0.0, 0.0, 0.0, -0.1, 0.1);
-    // disjoint with everything?
-    shape = new GeoStandardCircle(PlanetModel.SPHERE, 0.0, 0.0, 0.1);
-    assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));
-    shape = new GeoWorld(PlanetModel.SPHERE);
-    assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));
-
-    // Build a shape degenerate in (x,y) which has one point on sphere
-    solid = GeoAreaFactory.makeGeoArea(PlanetModel.SPHERE, 0.0, 0.0, 0.0, 0.0, -0.1, 1.1);
-    // inside everything that it touches?
-    shape = new GeoStandardCircle(PlanetModel.SPHERE, 0.0, 0.0, 0.1);
-    assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));
-    shape = new GeoWorld(PlanetModel.SPHERE);
-    assertEquals(GeoArea.CONTAINS, solid.getRelationship(shape));
-    shape = new GeoStandardCircle(PlanetModel.SPHERE, Math.PI * 0.5, 0.0, 0.1);
-    assertEquals(GeoArea.CONTAINS, solid.getRelationship(shape));
-    shape = new GeoStandardCircle(PlanetModel.SPHERE, -Math.PI * 0.5, 0.0, 0.1);
-    assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));
-
-    // Build a shape degenerate in (x,y) which has two points on sphere
-    solid = GeoAreaFactory.makeGeoArea(PlanetModel.SPHERE, 0.0, 0.0, 0.0, 0.0, -1.1, 1.1);
-    // inside everything that it touches?
-    shape = new GeoStandardCircle(PlanetModel.SPHERE, 0.0, 0.0, 0.1);
-    assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));
-    shape = new GeoWorld(PlanetModel.SPHERE);
-    assertEquals(GeoArea.CONTAINS, solid.getRelationship(shape));
-    shape = new GeoStandardCircle(PlanetModel.SPHERE, Math.PI * 0.5, 0.0, 0.1);
-    assertEquals(GeoArea.OVERLAPS, solid.getRelationship(shape));
-    shape = new GeoStandardCircle(PlanetModel.SPHERE, -Math.PI * 0.5, 0.0, 0.1);
-    assertEquals(GeoArea.OVERLAPS, solid.getRelationship(shape));
-    
-    // Build a shape degenerate in (x,z), which has no points on sphere
-    solid = GeoAreaFactory.makeGeoArea(PlanetModel.SPHERE, 0.0, 0.0, -0.1, 0.1, 0.0, 0.0);
-    // disjoint with everything?
-    shape = new GeoStandardCircle(PlanetModel.SPHERE, 0.0, 0.0, 0.1);
-    assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));
-    shape = new GeoWorld(PlanetModel.SPHERE);
-    assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));
-
-    // Build a shape degenerate in (x,z) which has one point on sphere
-    solid = GeoAreaFactory.makeGeoArea(PlanetModel.SPHERE, 0.0, 0.0, -0.1, 1.1, 0.0, 0.0);
-    // inside everything that it touches?
-    shape = new GeoStandardCircle(PlanetModel.SPHERE, 0.0, 0.0, 0.1);
-    assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));
-    shape = new GeoWorld(PlanetModel.SPHERE);
-    assertEquals(GeoArea.CONTAINS, solid.getRelationship(shape));
-    shape = new GeoStandardCircle(PlanetModel.SPHERE, 0.0, Math.PI * 0.5, 0.1);
-    assertEquals(GeoArea.CONTAINS, solid.getRelationship(shape));
-    shape = new GeoStandardCircle(PlanetModel.SPHERE, 0.0, -Math.PI * 0.5, 0.1);
-    assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));
-
-    // Build a shape degenerate in (x,y) which has two points on sphere
-    solid = GeoAreaFactory.makeGeoArea(PlanetModel.SPHERE, 0.0, 0.0, -1.1, 1.1, 0.0, 0.0);
-    // inside everything that it touches?
-    shape = new GeoStandardCircle(PlanetModel.SPHERE, 0.0, 0.0, 0.1);
-    assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));
-    shape = new GeoWorld(PlanetModel.SPHERE);
-    assertEquals(GeoArea.CONTAINS, solid.getRelationship(shape));
-    shape = new GeoStandardCircle(PlanetModel.SPHERE, 0.0, Math.PI * 0.5, 0.1);
-    assertEquals(GeoArea.OVERLAPS, solid.getRelationship(shape));
-    shape = new GeoStandardCircle(PlanetModel.SPHERE, 0.0, -Math.PI * 0.5, 0.1);
-    assertEquals(GeoArea.OVERLAPS, solid.getRelationship(shape));
-
-    // MHL for y-z check
-    
-    // Build a shape that is degenerate in x, which has zero points intersecting sphere
-    solid = GeoAreaFactory.makeGeoArea(PlanetModel.SPHERE, 0.0, 0.0, -0.1, 0.1, -0.1, 0.1);
-    // disjoint with everything?
-    shape = new GeoStandardCircle(PlanetModel.SPHERE, 0.0, 0.0, 0.1);
-    assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));
-    shape = new GeoWorld(PlanetModel.SPHERE);
-    assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));
-
-    // Build a shape that is degenerate in x, which has zero points intersecting sphere, second variation
-    solid = GeoAreaFactory.makeGeoArea(PlanetModel.SPHERE, 0.0, 0.0, -0.1, 0.1, 1.1, 1.2);
-    // disjoint with everything?
-    shape = new GeoStandardCircle(PlanetModel.SPHERE, 0.0, 0.0, 0.1);
-    assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));
-    shape = new GeoWorld(PlanetModel.SPHERE);
-    assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));
-
-    // Build a shape that is disjoint in X but intersects sphere in a complete circle
-    solid = GeoAreaFactory.makeGeoArea(PlanetModel.SPHERE, 0.0, 0.0, -1.1, 1.1, -1.1, 1.1);
-    // inside everything that it touches?
-    shape = new GeoWorld(PlanetModel.SPHERE);
-    assertEquals(GeoArea.CONTAINS, solid.getRelationship(shape));
-    shape = new GeoStandardCircle(PlanetModel.SPHERE, 0.0, 0.0, 0.1);
-    assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));
-    shape = new GeoStandardCircle(PlanetModel.SPHERE, 0.0, Math.PI, 0.1);
-    assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));
-    shape = new GeoStandardCircle(PlanetModel.SPHERE, 0.0, Math.PI * 0.5, 0.1);
-    assertEquals(GeoArea.OVERLAPS, solid.getRelationship(shape));
-    shape = new GeoStandardCircle(PlanetModel.SPHERE, 0.0, -Math.PI * 0.5, 0.1);
-    assertEquals(GeoArea.OVERLAPS, solid.getRelationship(shape));
-    shape = new GeoStandardCircle(PlanetModel.SPHERE, Math.PI * 0.5, 0.0, 0.1);
-    assertEquals(GeoArea.OVERLAPS, solid.getRelationship(shape));
-    shape = new GeoStandardCircle(PlanetModel.SPHERE, -Math.PI * 0.5, 0.0, 0.1);
-    assertEquals(GeoArea.OVERLAPS, solid.getRelationship(shape));
-
-    // Build a shape that is disjoint in X but intersects sphere in a half circle in Y
-    solid = GeoAreaFactory.makeGeoArea(PlanetModel.SPHERE, 0.0, 0.0, 0.0, 1.1, -1.1, 1.1);
-    // inside everything that it touches?
-    shape = new GeoWorld(PlanetModel.SPHERE);
-    assertEquals(GeoArea.CONTAINS, solid.getRelationship(shape));
-    shape = new GeoStandardCircle(PlanetModel.SPHERE, 0.0, 0.0, 0.1);
-    assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));
-    shape = new GeoStandardCircle(PlanetModel.SPHERE, 0.0, Math.PI, 0.1);
-    assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));
-    shape = new GeoStandardCircle(PlanetModel.SPHERE, 0.0, Math.PI * 0.5, 0.1);
-    assertEquals(GeoArea.OVERLAPS, solid.getRelationship(shape));
-    shape = new GeoStandardCircle(PlanetModel.SPHERE, 0.0, -Math.PI * 0.5, 0.1);
-    assertEquals(GeoArea.DISJOINT, solid.getRelationship(shape));
-    shape = new GeoStandardCircle(PlanetModel.SPHERE, Math.PI * 0.5, 0.0, 0.1);
-    assertEquals(GeoArea.OVERLAPS, solid.getRelationship(shape));
-    shape = new GeoStandardCircle(PlanetModel.SPHERE, -Math.PI * 0.5, 0.0, 0.1);
-    assertEquals(GeoArea.OVERLAPS, solid.getRelationship(shape));
-
-    // MHL for degenerate Y
-    // MHL for degenerate Z
-    
-  }
-  
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/0a1951be/lucene/spatial3d/src/test/org/apache/lucene/spatial3d/TestGeo3DPoint.java
----------------------------------------------------------------------
diff --git a/lucene/spatial3d/src/test/org/apache/lucene/spatial3d/TestGeo3DPoint.java b/lucene/spatial3d/src/test/org/apache/lucene/spatial3d/TestGeo3DPoint.java
new file mode 100644
index 0000000..a4d8ed1
--- /dev/null
+++ b/lucene/spatial3d/src/test/org/apache/lucene/spatial3d/TestGeo3DPoint.java
@@ -0,0 +1,810 @@
+/*
+ * 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.
+ */
+package org.apache.lucene.spatial3d;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.lucene.codecs.Codec;
+import org.apache.lucene.codecs.FilterCodec;
+import org.apache.lucene.codecs.PointsFormat;
+import org.apache.lucene.codecs.PointsReader;
+import org.apache.lucene.codecs.PointsWriter;
+import org.apache.lucene.codecs.lucene60.Lucene60PointsReader;
+import org.apache.lucene.codecs.lucene60.Lucene60PointsWriter;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.document.NumericDocValuesField;
+import org.apache.lucene.spatial3d.geom.GeoArea;
+import org.apache.lucene.spatial3d.geom.GeoAreaFactory;
+import org.apache.lucene.spatial3d.geom.GeoBBoxFactory;
+import org.apache.lucene.spatial3d.geom.GeoCircleFactory;
+import org.apache.lucene.spatial3d.geom.GeoPath;
+import org.apache.lucene.spatial3d.geom.GeoPoint;
+import org.apache.lucene.spatial3d.geom.GeoPolygonFactory;
+import org.apache.lucene.spatial3d.geom.GeoShape;
+import org.apache.lucene.spatial3d.geom.PlanetModel;
+import org.apache.lucene.spatial3d.geom.XYZBounds;
+import org.apache.lucene.index.DirectoryReader;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.IndexWriterConfig;
+import org.apache.lucene.index.LeafReaderContext;
+import org.apache.lucene.index.MultiDocValues;
+import org.apache.lucene.index.NumericDocValues;
+import org.apache.lucene.index.SegmentReadState;
+import org.apache.lucene.index.SegmentWriteState;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.SimpleCollector;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.util.FixedBitSet;
+import org.apache.lucene.util.IOUtils;
+import org.apache.lucene.util.LuceneTestCase;
+import org.apache.lucene.util.TestUtil;
+import org.junit.BeforeClass;
+
+import com.carrotsearch.randomizedtesting.generators.RandomInts;
+
+public class TestGeo3DPoint extends LuceneTestCase {
+
+  private static boolean smallBBox;
+  
+  @BeforeClass
+  public static void beforeClass() {
+    smallBBox = random().nextBoolean();
+    if (VERBOSE) {
+      System.err.println("TEST: smallBBox=" + smallBBox);
+    }
+  }
+
+  private static Codec getCodec() {
+    if (Codec.getDefault().getName().equals("Lucene60")) {
+      int maxPointsInLeafNode = TestUtil.nextInt(random(), 16, 2048);
+      double maxMBSortInHeap = 3.0 + (3*random().nextDouble());
+      if (VERBOSE) {
+        System.out.println("TEST: using Lucene60PointsFormat with maxPointsInLeafNode=" + maxPointsInLeafNode + " and maxMBSortInHeap=" + maxMBSortInHeap);
+      }
+
+      return new FilterCodec("Lucene60", Codec.getDefault()) {
+        @Override
+        public PointsFormat pointsFormat() {
+          return new PointsFormat() {
+            @Override
+            public PointsWriter fieldsWriter(SegmentWriteState writeState) throws IOException {
+              return new Lucene60PointsWriter(writeState, maxPointsInLeafNode, maxMBSortInHeap);
+            }
+
+            @Override
+            public PointsReader fieldsReader(SegmentReadState readState) throws IOException {
+              return new Lucene60PointsReader(readState);
+            }
+          };
+        }
+      };
+    } else {
+      return Codec.getDefault();
+    }
+  }
+
+  public void testBasic() throws Exception {
+    Directory dir = getDirectory();
+    IndexWriterConfig iwc = newIndexWriterConfig();
+    iwc.setCodec(getCodec());
+    IndexWriter w = new IndexWriter(dir, iwc);
+    Document doc = new Document();
+    doc.add(new Geo3DPoint("field", toRadians(50.7345267), toRadians(-97.5303555)));
+    w.addDocument(doc);
+    IndexReader r = DirectoryReader.open(w);
+    // We can't wrap with "exotic" readers because the query must see the BKD3DDVFormat:
+    IndexSearcher s = newSearcher(r, false);
+    assertEquals(1, s.search(Geo3DPoint.newShapeQuery("field",
+                                                      GeoCircleFactory.makeGeoCircle(PlanetModel.WGS84, toRadians(50), toRadians(-97), Math.PI/180.)), 1).totalHits);
+    w.close();
+    r.close();
+    dir.close();
+  }
+
+  private static double toRadians(double degrees) {
+    return Math.PI*(degrees/360.0);
+  }
+
+  private static PlanetModel getPlanetModel() {
+    if (random().nextBoolean()) {
+      // Use one of the earth models:
+      if (random().nextBoolean()) {
+        return PlanetModel.WGS84;
+      } else {
+        return PlanetModel.SPHERE;
+      }
+    } else {
+      // Make a randomly squashed planet:
+      double oblateness = random().nextDouble() * 0.5 - 0.25;
+      return new PlanetModel(1.0 + oblateness, 1.0 - oblateness);
+    }
+  }
+
+  private static class Cell {
+    static int nextCellID;
+
+    final Cell parent;
+    final int cellID;
+    final int xMinEnc, xMaxEnc;
+    final int yMinEnc, yMaxEnc;
+    final int zMinEnc, zMaxEnc;
+    final int splitCount;
+
+    public Cell(Cell parent,
+                int xMinEnc, int xMaxEnc,
+                int yMinEnc, int yMaxEnc,
+                int zMinEnc, int zMaxEnc,
+                int splitCount) {
+      this.parent = parent;
+      this.xMinEnc = xMinEnc;
+      this.xMaxEnc = xMaxEnc;
+      this.yMinEnc = yMinEnc;
+      this.yMaxEnc = yMaxEnc;
+      this.zMinEnc = zMinEnc;
+      this.zMaxEnc = zMaxEnc;
+      this.cellID = nextCellID++;
+      this.splitCount = splitCount;
+    }
+
+    /** Returns true if the quantized point lies within this cell, inclusive on all bounds. */
+    public boolean contains(double planetMax, GeoPoint point) {
+      int docX = Geo3DUtil.encodeValue(planetMax, point.x);
+      int docY = Geo3DUtil.encodeValue(planetMax, point.y);
+      int docZ = Geo3DUtil.encodeValue(planetMax, point.z);
+
+      return docX >= xMinEnc && docX <= xMaxEnc &&
+        docY >= yMinEnc && docY <= yMaxEnc && 
+        docZ >= zMinEnc && docZ <= zMaxEnc;
+    }
+
+    @Override
+    public String toString() {
+      return "cell=" + cellID + (parent == null ? "" : " parentCellID=" + parent.cellID) + " x: " + xMinEnc + " TO " + xMaxEnc + ", y: " + yMinEnc + " TO " + yMaxEnc + ", z: " + zMinEnc + " TO " + zMaxEnc + ", splits: " + splitCount;
+    }
+  }
+
+  private static GeoPoint quantize(double planetMax, GeoPoint point) {
+    return new GeoPoint(Geo3DUtil.decodeValueCenter(planetMax, Geo3DUtil.encodeValue(planetMax, point.x)),
+                        Geo3DUtil.decodeValueCenter(planetMax, Geo3DUtil.encodeValue(planetMax, point.y)),
+                        Geo3DUtil.decodeValueCenter(planetMax, Geo3DUtil.encodeValue(planetMax, point.z)));
+  }
+
+  /** Tests consistency of GeoArea.getRelationship vs GeoShape.isWithin */
+  public void testGeo3DRelations() throws Exception {
+
+    PlanetModel planetModel = getPlanetModel();
+
+    int numDocs = atLeast(1000);
+    if (VERBOSE) {
+      System.out.println("TEST: " + numDocs + " docs");
+    }
+
+    GeoPoint[] docs = new GeoPoint[numDocs];
+    for(int docID=0;docID<numDocs;docID++) {
+      docs[docID] = new GeoPoint(planetModel, toRadians(randomLat()), toRadians(randomLon()));
+      if (VERBOSE) {
+        System.out.println("  doc=" + docID + ": " + docs[docID]);
+      }
+    }
+
+    double planetMax = planetModel.getMaximumMagnitude();
+
+    int iters = atLeast(10);
+
+    int recurseDepth = RandomInts.randomIntBetween(random(), 5, 15);
+
+    iters = atLeast(50);
+    
+    for(int iter=0;iter<iters;iter++) {
+      GeoShape shape = randomShape(planetModel);
+
+      StringWriter sw = new StringWriter();
+      PrintWriter log = new PrintWriter(sw, true);
+
+      if (VERBOSE) {
+        log.println("TEST: iter=" + iter + " shape=" + shape);
+      }
+
+      XYZBounds bounds = new XYZBounds();
+      shape.getBounds(bounds);
+
+      // Start with the root cell that fully contains the shape:
+      Cell root = new Cell(null,
+                           Geo3DUtil.encodeValueLenient(planetMax, bounds.getMinimumX()),
+                           Geo3DUtil.encodeValueLenient(planetMax, bounds.getMaximumX()),
+                           Geo3DUtil.encodeValueLenient(planetMax, bounds.getMinimumY()),
+                           Geo3DUtil.encodeValueLenient(planetMax, bounds.getMaximumY()),
+                           Geo3DUtil.encodeValueLenient(planetMax, bounds.getMinimumZ()),
+                           Geo3DUtil.encodeValueLenient(planetMax, bounds.getMaximumZ()),
+                           0);
+
+      if (VERBOSE) {
+        log.println("  root cell: " + root);
+      }
+
+      List<Cell> queue = new ArrayList<>();
+      queue.add(root);
+      Set<Integer> hits = new HashSet<>();
+
+      while (queue.size() > 0) {
+        Cell cell = queue.get(queue.size()-1);
+        queue.remove(queue.size()-1);
+        if (VERBOSE) {
+          log.println("  cycle: " + cell + " queue.size()=" + queue.size());
+        }
+
+        if (random().nextInt(10) == 7 || cell.splitCount > recurseDepth) {
+          if (VERBOSE) {
+            log.println("    leaf");
+          }
+          // Leaf cell: brute force check all docs that fall within this cell:
+          for(int docID=0;docID<numDocs;docID++) {
+            GeoPoint point = docs[docID];
+            if (cell.contains(planetMax, point)) {
+              if (shape.isWithin(quantize(planetMax, point))) {
+                if (VERBOSE) {
+                  log.println("    check doc=" + docID + ": match!");
+                }
+                hits.add(docID);
+              } else {
+                if (VERBOSE) {
+                  log.println("    check doc=" + docID + ": no match");
+                }
+              }
+            }
+          }
+        } else {
+          
+          GeoArea xyzSolid = GeoAreaFactory.makeGeoArea(planetModel,
+                                                        Geo3DUtil.decodeValueMin(planetMax, cell.xMinEnc), Geo3DUtil.decodeValueMax(planetMax, cell.xMaxEnc),
+                                                        Geo3DUtil.decodeValueMin(planetMax, cell.yMinEnc), Geo3DUtil.decodeValueMax(planetMax, cell.yMaxEnc),
+                                                        Geo3DUtil.decodeValueMin(planetMax, cell.zMinEnc), Geo3DUtil.decodeValueMax(planetMax, cell.zMaxEnc));
+
+          if (VERBOSE) {
+            log.println("    minx="+Geo3DUtil.decodeValueMin(planetMax, cell.xMinEnc)+" maxx="+Geo3DUtil.decodeValueMax(planetMax, cell.xMaxEnc)+
+              " miny="+Geo3DUtil.decodeValueMin(planetMax, cell.yMinEnc)+" maxy="+Geo3DUtil.decodeValueMax(planetMax, cell.yMaxEnc)+
+              " minz="+Geo3DUtil.decodeValueMin(planetMax, cell.zMinEnc)+" maxz="+Geo3DUtil.decodeValueMax(planetMax, cell.zMaxEnc));
+          }
+
+          switch (xyzSolid.getRelationship(shape)) {          
+          case GeoArea.CONTAINS:
+            // Shape fully contains the cell: blindly add all docs in this cell:
+            if (VERBOSE) {
+              log.println("    GeoArea.CONTAINS: now addAll");
+            }
+            for(int docID=0;docID<numDocs;docID++) {
+              if (cell.contains(planetMax, docs[docID])) {
+                if (VERBOSE) {
+                  log.println("    addAll doc=" + docID);
+                }
+                hits.add(docID);
+              }
+            }
+            continue;
+          case GeoArea.OVERLAPS:
+            if (VERBOSE) {
+              log.println("    GeoArea.OVERLAPS: keep splitting");
+            }
+            // They do overlap but neither contains the other:
+            //log.println("    crosses1");
+            break;
+          case GeoArea.WITHIN:
+            if (VERBOSE) {
+              log.println("    GeoArea.WITHIN: keep splitting");
+            }
+            // Cell fully contains the shape:
+            //log.println("    crosses2");
+            break;
+          case GeoArea.DISJOINT:
+            // They do not overlap at all: don't recurse on this cell
+            //log.println("    outside");
+            if (VERBOSE) {
+              log.println("    GeoArea.DISJOINT: drop this cell");
+              for(int docID=0;docID<numDocs;docID++) {
+                if (cell.contains(planetMax, docs[docID])) {
+                  if (VERBOSE) {
+                    log.println("    skip doc=" + docID);
+                  }
+                }
+              }
+            }
+            continue;
+          default:
+            assert false;
+          }
+
+          // Randomly split:
+          switch(random().nextInt(3)) {
+
+          case 0:
+            // Split on X:
+            {
+              int splitValue = RandomInts.randomIntBetween(random(), cell.xMinEnc, cell.xMaxEnc);
+              if (VERBOSE) {
+                log.println("    now split on x=" + splitValue);
+              }
+              Cell cell1 = new Cell(cell,
+                                 cell.xMinEnc, splitValue,
+                                 cell.yMinEnc, cell.yMaxEnc,
+                                 cell.zMinEnc, cell.zMaxEnc,
+                                 cell.splitCount+1);
+              Cell cell2 = new Cell(cell,
+                                 splitValue, cell.xMaxEnc,
+                                 cell.yMinEnc, cell.yMaxEnc,
+                                 cell.zMinEnc, cell.zMaxEnc,
+                                 cell.splitCount+1);
+              if (VERBOSE) {
+                log.println("    split cell1: " + cell1);
+                log.println("    split cell2: " + cell2);
+              }
+              queue.add(cell1);
+              queue.add(cell2);
+            }
+            break;
+
+          case 1:
+            // Split on Y:
+            {
+              int splitValue = RandomInts.randomIntBetween(random(), cell.yMinEnc, cell.yMaxEnc);
+              if (VERBOSE) {
+                log.println("    now split on y=" + splitValue);
+              }
+              Cell cell1 = new Cell(cell,
+                                 cell.xMinEnc, cell.xMaxEnc,
+                                 cell.yMinEnc, splitValue,
+                                 cell.zMinEnc, cell.zMaxEnc,
+                                 cell.splitCount+1);
+              Cell cell2 = new Cell(cell,
+                                 cell.xMinEnc, cell.xMaxEnc,
+                                 splitValue, cell.yMaxEnc,
+                                 cell.zMinEnc, cell.zMaxEnc,
+                                 cell.splitCount+1);
+              if (VERBOSE) {
+                log.println("    split cell1: " + cell1);
+                log.println("    split cell2: " + cell2);
+              }
+              queue.add(cell1);
+              queue.add(cell2);
+            }
+            break;
+
+          case 2:
+            // Split on Z:
+            {
+              int splitValue = RandomInts.randomIntBetween(random(), cell.zMinEnc, cell.zMaxEnc);
+              if (VERBOSE) {
+                log.println("    now split on z=" + splitValue);
+              }
+              Cell cell1 = new Cell(cell,
+                                 cell.xMinEnc, cell.xMaxEnc,
+                                 cell.yMinEnc, cell.yMaxEnc,
+                                 cell.zMinEnc, splitValue,
+                                 cell.splitCount+1);
+              Cell cell2 = new Cell(cell,
+                                 cell.xMinEnc, cell.xMaxEnc,
+                                 cell.yMinEnc, cell.yMaxEnc,
+                                 splitValue, cell.zMaxEnc,
+                                 cell.splitCount+1);
+              if (VERBOSE) {
+                log.println("    split cell1: " + cell1);
+                log.println("    split cell2: " + cell2);
+              }
+              queue.add(cell1);
+              queue.add(cell2);
+            }
+            break;
+          }
+        }
+      }
+
+      if (VERBOSE) {
+        log.println("  " + hits.size() + " hits");
+      }
+
+      // Done matching, now verify:
+      boolean fail = false;
+      for(int docID=0;docID<numDocs;docID++) {
+        GeoPoint point = docs[docID];
+        GeoPoint quantized = quantize(planetMax, point);
+        boolean expected = shape.isWithin(quantized);
+
+        if (expected != shape.isWithin(point)) {
+          // Quantization changed the result; skip testing this doc:
+          continue;
+        }
+
+        boolean actual = hits.contains(docID);
+        if (actual != expected) {
+          if (actual) {
+            log.println("doc=" + docID + " matched but should not");
+          } else {
+            log.println("doc=" + docID + " did not match but should");
+          }
+          log.println("  point=" + docs[docID]);
+          log.println("  quantized=" + quantize(planetMax, docs[docID]));
+          fail = true;
+        }
+      }
+
+      if (fail) {
+        System.out.print(sw.toString());
+        fail("invalid hits for shape=" + shape);
+      }
+    }
+  }
+
+  public void testRandomTiny() throws Exception {
+    // Make sure single-leaf-node case is OK:
+    doTestRandom(10);
+  }
+
+  public void testRandomMedium() throws Exception {
+    doTestRandom(10000);
+  }
+
+  @Nightly
+  public void testRandomBig() throws Exception {
+    doTestRandom(200000);
+  }
+
+  private void doTestRandom(int count) throws Exception {
+    int numPoints = atLeast(count);
+
+    if (VERBOSE) {
+      System.err.println("TEST: numPoints=" + numPoints);
+    }
+
+    double[] lats = new double[numPoints];
+    double[] lons = new double[numPoints];
+
+    boolean haveRealDoc = false;
+
+    for (int docID=0;docID<numPoints;docID++) {
+      int x = random().nextInt(20);
+      if (x == 17) {
+        // Some docs don't have a point:
+        lats[docID] = Double.NaN;
+        if (VERBOSE) {
+          System.err.println("  doc=" + docID + " is missing");
+        }
+        continue;
+      }
+
+      if (docID > 0 && x < 3 && haveRealDoc) {
+        int oldDocID;
+        while (true) {
+          oldDocID = random().nextInt(docID);
+          if (Double.isNaN(lats[oldDocID]) == false) {
+            break;
+          }
+        }
+            
+        if (x == 0) {
+          // Identical lat to old point
+          lats[docID] = lats[oldDocID];
+          lons[docID] = toRadians(randomLon());
+          if (VERBOSE) {
+            System.err.println("  doc=" + docID + " lat=" + lats[docID] + " lon=" + lons[docID] + " (same lat as doc=" + oldDocID + ")");
+          }
+        } else if (x == 1) {
+          // Identical lon to old point
+          lats[docID] = toRadians(randomLat());
+          lons[docID] = lons[oldDocID];
+          if (VERBOSE) {
+            System.err.println("  doc=" + docID + " lat=" + lats[docID] + " lon=" + lons[docID] + " (same lon as doc=" + oldDocID + ")");
+          }
+        } else {
+          assert x == 2;
+          // Fully identical point:
+          lats[docID] = lats[oldDocID];
+          lons[docID] = lons[oldDocID];
+          if (VERBOSE) {
+            System.err.println("  doc=" + docID + " lat=" + lats[docID] + " lon=" + lons[docID] + " (same lat/lon as doc=" + oldDocID + ")");
+          }
+        }
+      } else {
+        lats[docID] = toRadians(randomLat());
+        lons[docID] = toRadians(randomLon());
+        haveRealDoc = true;
+        if (VERBOSE) {
+          System.err.println("  doc=" + docID + " lat=" + lats[docID] + " lon=" + lons[docID]);
+        }
+      }
+    }
+
+    verify(lats, lons);
+  }
+
+  private static double randomLat() {
+    if (smallBBox) {
+      return 2.0 * (random().nextDouble()-0.5);
+    } else {
+      return -90 + 180.0 * random().nextDouble();
+    }
+  }
+
+  private static double randomLon() {
+    if (smallBBox) {
+      return 2.0 * (random().nextDouble()-0.5);
+    } else {
+      return -180 + 360.0 * random().nextDouble();
+    }
+  }
+
+  // Poached from Geo3dRptTest.randomShape:
+  private static GeoShape randomShape(PlanetModel planetModel) {
+    while (true) {
+      final int shapeType = random().nextInt(4);
+      switch (shapeType) {
+      case 0: {
+        // Polygons
+        final int vertexCount = random().nextInt(3) + 3;
+        final List<GeoPoint> geoPoints = new ArrayList<>();
+        while (geoPoints.size() < vertexCount) {
+          final GeoPoint gPt = new GeoPoint(planetModel, toRadians(randomLat()), toRadians(randomLon()));
+          geoPoints.add(gPt);
+        }
+        final int convexPointIndex = random().nextInt(vertexCount);       //If we get this wrong, hopefully we get IllegalArgumentException
+        try {
+          return GeoPolygonFactory.makeGeoPolygon(planetModel, geoPoints, convexPointIndex);
+        } catch (IllegalArgumentException e) {
+          // This is what happens when we create a shape that is invalid.  Although it is conceivable that there are cases where
+          // the exception is thrown incorrectly, we aren't going to be able to do that in this random test.
+          continue;
+        }
+      }
+
+      case 1: {
+        // Circles
+
+        double lat = toRadians(randomLat());
+        double lon = toRadians(randomLon());
+
+        double angle;
+        if (smallBBox) {
+          angle = random().nextDouble() * Math.PI/360.0;
+        } else {
+          angle = random().nextDouble() * Math.PI/2.0;
+        }
+
+        try {
+          return GeoCircleFactory.makeGeoCircle(planetModel, lat, lon, angle);
+        } catch (IllegalArgumentException iae) {
+          // angle is too small; try again:
+          continue;
+        }
+      }
+
+      case 2: {
+        // Rectangles
+        double lat0 = toRadians(randomLat());
+        double lat1 = toRadians(randomLat());
+        if (lat1 < lat0) {
+          double x = lat0;
+          lat0 = lat1;
+          lat1 = x;
+        }
+        double lon0 = toRadians(randomLon());
+        double lon1 = toRadians(randomLon());
+        if (lon1 < lon0) {
+          double x = lon0;
+          lon0 = lon1;
+          lon1 = x;
+        }
+
+        return GeoBBoxFactory.makeGeoBBox(planetModel, lat1, lat0, lon0, lon1);
+      }
+
+      case 3: {
+        // Paths
+        final int pointCount = random().nextInt(5) + 1;
+        final double width = toRadians(random().nextInt(89)+1);
+        try {
+          final GeoPath path = new GeoPath(planetModel, width);
+          for (int i = 0; i < pointCount; i++) {
+            path.addPoint(toRadians(randomLat()), toRadians(randomLon()));
+          }
+          path.done();
+          return path;
+        } catch (IllegalArgumentException e) {
+          // This is what happens when we create a shape that is invalid.  Although it is conceivable that there are cases where
+          // the exception is thrown incorrectly, we aren't going to be able to do that in this random test.
+          continue;
+        }
+      }
+
+      default:
+        throw new IllegalStateException("Unexpected shape type");
+      }
+    }
+  }
+
+  private static void verify(double[] lats, double[] lons) throws Exception {
+    IndexWriterConfig iwc = newIndexWriterConfig();
+
+    // Else we can get O(N^2) merging:
+    int mbd = iwc.getMaxBufferedDocs();
+    if (mbd != -1 && mbd < lats.length/100) {
+      iwc.setMaxBufferedDocs(lats.length/100);
+    }
+    iwc.setCodec(getCodec());
+    Directory dir;
+    if (lats.length > 100000) {
+      dir = newFSDirectory(createTempDir("TestBKDTree"));
+    } else {
+      dir = getDirectory();
+    }
+    Set<Integer> deleted = new HashSet<>();
+    // RandomIndexWriter is too slow here:
+    IndexWriter w = new IndexWriter(dir, iwc);
+    for(int id=0;id<lats.length;id++) {
+      Document doc = new Document();
+      doc.add(newStringField("id", ""+id, Field.Store.NO));
+      doc.add(new NumericDocValuesField("id", id));
+      if (Double.isNaN(lats[id]) == false) {
+        doc.add(new Geo3DPoint("point", lats[id], lons[id]));
+      }
+      w.addDocument(doc);
+      if (id > 0 && random().nextInt(100) == 42) {
+        int idToDelete = random().nextInt(id);
+        w.deleteDocuments(new Term("id", ""+idToDelete));
+        deleted.add(idToDelete);
+        if (VERBOSE) {
+          System.err.println("  delete id=" + idToDelete);
+        }
+      }
+    }
+    if (random().nextBoolean()) {
+      w.forceMerge(1);
+    }
+    final IndexReader r = DirectoryReader.open(w);
+    w.close();
+
+    // We can't wrap with "exotic" readers because the geo3d query must see the Geo3DDVFormat:
+    IndexSearcher s = newSearcher(r, false);
+
+    int numThreads = TestUtil.nextInt(random(), 2, 5);
+
+    List<Thread> threads = new ArrayList<>();
+    final int iters = atLeast(100);
+
+    final CountDownLatch startingGun = new CountDownLatch(1);
+    final AtomicBoolean failed = new AtomicBoolean();
+
+    for(int i=0;i<numThreads;i++) {
+      Thread thread = new Thread() {
+          @Override
+          public void run() {
+            try {
+              _run();
+            } catch (Exception e) {
+              failed.set(true);
+              throw new RuntimeException(e);
+            }
+          }
+
+          private void _run() throws Exception {
+            startingGun.await();
+
+            NumericDocValues docIDToID = MultiDocValues.getNumericValues(r, "id");
+
+            for (int iter=0;iter<iters && failed.get() == false;iter++) {
+
+              GeoShape shape = randomShape(PlanetModel.WGS84);
+
+              if (VERBOSE) {
+                System.err.println("\n" + Thread.currentThread() + ": TEST: iter=" + iter + " shape="+shape);
+              }
+              
+              Query query = Geo3DPoint.newShapeQuery("point", shape);
+
+              if (VERBOSE) {
+                System.err.println("  using query: " + query);
+              }
+
+              final FixedBitSet hits = new FixedBitSet(r.maxDoc());
+
+              s.search(query, new SimpleCollector() {
+
+                  private int docBase;
+
+                  @Override
+                  public boolean needsScores() {
+                    return false;
+                  }
+
+                  @Override
+                  protected void doSetNextReader(LeafReaderContext context) throws IOException {
+                    docBase = context.docBase;
+                  }
+
+                  @Override
+                  public void collect(int doc) {
+                    hits.set(docBase+doc);
+                  }
+                });
+
+              if (VERBOSE) {
+                System.err.println("  hitCount: " + hits.cardinality());
+              }
+      
+              for(int docID=0;docID<r.maxDoc();docID++) {
+                int id = (int) docIDToID.get(docID);
+                if (Double.isNaN(lats[id]) == false) {
+
+                  // Accurate point:
+                  GeoPoint point1 = new GeoPoint(PlanetModel.WGS84, lats[id], lons[id]);
+
+                  // Quantized point (32 bits per dim):
+                  GeoPoint point2 = quantize(PlanetModel.WGS84.getMaximumMagnitude(), point1);
+
+                  if (shape.isWithin(point1) != shape.isWithin(point2)) {
+                    if (VERBOSE) {
+                      System.out.println("  skip checking docID=" + docID + " quantization changed the expected result from " + shape.isWithin(point1) + " to " + shape.isWithin(point2));
+                    }
+                    continue;
+                  }
+
+                  boolean expected = ((deleted.contains(id) == false) && shape.isWithin(point2));
+                  if (hits.get(docID) != expected) {
+                    fail(Thread.currentThread().getName() + ": iter=" + iter + " id=" + id + " docID=" + docID + " lat=" + lats[id] + " lon=" + lons[id] + " expected " + expected + " but got: " + hits.get(docID) + " deleted?=" + deleted.contains(id) + "\n  point1=" + point1 + ", iswithin="+shape.isWithin(point1)+"\n  point2=" + point2 + ", iswithin="+shape.isWithin(point2) + "\n  query=" + query);
+                  }
+                } else {
+                  assertFalse(hits.get(docID));
+                }
+
+              }
+            }
+          }
+        };
+      thread.setName("T" + i);
+      thread.start();
+      threads.add(thread);
+    }
+    startingGun.countDown();
+    for(Thread thread : threads) {
+      thread.join();
+    }
+    IOUtils.close(r, dir);
+  }
+
+  public void testToString() {
+    Geo3DPoint point = new Geo3DPoint("point", toRadians(44.244272), toRadians(7.769736));
+    assertEquals("Geo3DPoint <point: x=0.9248467864160119 y=0.06280434265368656 z=0.37682349005486243>", point.toString());
+  }
+
+  public void testShapeQueryToString() {
+    assertEquals("PointInGeo3DShapeQuery: field=point: Shape: GeoStandardCircle: {planetmodel=PlanetModel.WGS84, center=[lat=0.3861041107739683, lon=0.06780373760536706], radius=0.1(5.729577951308232)}",
+                 Geo3DPoint.newShapeQuery("point", GeoCircleFactory.makeGeoCircle(PlanetModel.WGS84, toRadians(44.244272), toRadians(7.769736), 0.1)).toString());
+  }
+
+  private static Directory getDirectory() {     
+    return newDirectory();
+  }
+}