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();
+ }
+}