mirror of https://github.com/apache/lucene.git
Merge branch 'branch_6x' of https://git-wip-us.apache.org/repos/asf/lucene-solr into branch_6x
This commit is contained in:
commit
f28f1096ce
|
@ -115,8 +115,8 @@ public class TestFieldMaskingSpanQuery extends LuceneTestCase {
|
|||
field("gender", "male"),
|
||||
field("first", "bubba"),
|
||||
field("last", "jones") }));
|
||||
reader = writer.getReader();
|
||||
writer.forceMerge(1);
|
||||
reader = writer.getReader();
|
||||
writer.close();
|
||||
searcher = new IndexSearcher(getOnlyLeafReader(reader));
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ import org.apache.lucene.search.MatchAllDocsQuery;
|
|||
import org.apache.lucene.search.Sort;
|
||||
import org.apache.lucene.search.SortField;
|
||||
import org.apache.lucene.search.TopDocs;
|
||||
import org.apache.lucene.spatial.util.GeoTestUtil;
|
||||
import org.apache.lucene.store.Directory;
|
||||
import org.apache.lucene.util.LuceneTestCase;
|
||||
import org.apache.lucene.util.SloppyMath;
|
||||
|
@ -182,8 +183,8 @@ public class TestLatLonPointDistanceSort extends LuceneTestCase {
|
|||
doc.add(new StoredField("id", i));
|
||||
doc.add(new NumericDocValuesField("id", i));
|
||||
if (random().nextInt(10) > 7) {
|
||||
double latRaw = -90 + 180.0 * random().nextDouble();
|
||||
double lonRaw = -180 + 360.0 * random().nextDouble();
|
||||
double latRaw = GeoTestUtil.nextLatitude();
|
||||
double lonRaw = GeoTestUtil.nextLongitude();
|
||||
// pre-normalize up front, so we can just use quantized value for testing and do simple exact comparisons
|
||||
double lat = LatLonPoint.decodeLatitude(LatLonPoint.encodeLatitude(latRaw));
|
||||
double lon = LatLonPoint.decodeLongitude(LatLonPoint.encodeLongitude(lonRaw));
|
||||
|
@ -198,8 +199,8 @@ public class TestLatLonPointDistanceSort extends LuceneTestCase {
|
|||
IndexSearcher searcher = newSearcher(reader);
|
||||
|
||||
for (int i = 0; i < numQueries; i++) {
|
||||
double lat = -90 + 180.0 * random().nextDouble();
|
||||
double lon = -180 + 360.0 * random().nextDouble();
|
||||
double lat = GeoTestUtil.nextLatitude();
|
||||
double lon = GeoTestUtil.nextLongitude();
|
||||
double missingValue = Double.POSITIVE_INFINITY;
|
||||
|
||||
Result expected[] = new Result[reader.maxDoc()];
|
||||
|
|
|
@ -19,13 +19,11 @@ package org.apache.lucene.spatial.util;
|
|||
import java.io.IOException;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.DecimalFormatSymbols;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.BitSet;
|
||||
import java.util.HashSet;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.apache.lucene.analysis.MockAnalyzer;
|
||||
import org.apache.lucene.codecs.FilterCodec;
|
||||
|
@ -45,6 +43,7 @@ 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.MultiFields;
|
||||
import org.apache.lucene.index.NumericDocValues;
|
||||
import org.apache.lucene.index.RandomIndexWriter;
|
||||
import org.apache.lucene.index.SegmentReadState;
|
||||
|
@ -57,6 +56,7 @@ import org.apache.lucene.search.SimpleCollector;
|
|||
import org.apache.lucene.search.Sort;
|
||||
import org.apache.lucene.search.TopDocs;
|
||||
import org.apache.lucene.store.Directory;
|
||||
import org.apache.lucene.util.Bits;
|
||||
import org.apache.lucene.util.FixedBitSet;
|
||||
import org.apache.lucene.util.IOUtils;
|
||||
import org.apache.lucene.util.LuceneTestCase;
|
||||
|
@ -82,41 +82,11 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
|
|||
|
||||
private static double originLat;
|
||||
private static double originLon;
|
||||
private static double lonRange;
|
||||
private static double latRange;
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClassBase() throws Exception {
|
||||
// Between 1.0 and 3.0:
|
||||
lonRange = 2 * (random().nextDouble() + 0.5);
|
||||
latRange = 2 * (random().nextDouble() + 0.5);
|
||||
|
||||
originLon = normalizeLon(GeoUtils.MIN_LON_INCL + lonRange + (GeoUtils.MAX_LON_INCL - GeoUtils.MIN_LON_INCL - 2 * lonRange) * random().nextDouble());
|
||||
originLat = normalizeLat(GeoUtils.MIN_LAT_INCL + latRange + (GeoUtils.MAX_LAT_INCL - GeoUtils.MIN_LAT_INCL - 2 * latRange) * random().nextDouble());
|
||||
}
|
||||
|
||||
/** Puts longitude in range of -180 to +180. */
|
||||
public static double normalizeLon(double lon_deg) {
|
||||
if (lon_deg >= -180 && lon_deg <= 180) {
|
||||
return lon_deg; //common case, and avoids slight double precision shifting
|
||||
}
|
||||
double off = (lon_deg + 180) % 360;
|
||||
if (off < 0) {
|
||||
return 180 + off;
|
||||
} else if (off == 0 && lon_deg > 0) {
|
||||
return 180;
|
||||
} else {
|
||||
return -180 + off;
|
||||
}
|
||||
}
|
||||
|
||||
/** Puts latitude in range of -90 to 90. */
|
||||
public static double normalizeLat(double lat_deg) {
|
||||
if (lat_deg >= -90 && lat_deg <= 90) {
|
||||
return lat_deg; //common case, and avoids slight double precision shifting
|
||||
}
|
||||
double off = Math.abs((lat_deg + 90) % 360);
|
||||
return (off <= 180 ? off : 360-off) - 90;
|
||||
originLon = GeoTestUtil.nextLongitude();
|
||||
originLat = GeoTestUtil.nextLatitude();
|
||||
}
|
||||
|
||||
/** Valid values that should not cause exception */
|
||||
|
@ -538,12 +508,11 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
|
|||
IndexReader r = w.getReader();
|
||||
w.close();
|
||||
|
||||
// We can't wrap with "exotic" readers because the BKD query must see the BKDDVFormat:
|
||||
IndexSearcher s = newSearcher(r, false);
|
||||
IndexSearcher s = newSearcher(r);
|
||||
|
||||
int iters = atLeast(75);
|
||||
int iters = atLeast(25);
|
||||
for (int iter=0;iter<iters;iter++) {
|
||||
GeoRect rect = randomRect(small, small == false);
|
||||
GeoRect rect = randomRect(small);
|
||||
|
||||
if (VERBOSE) {
|
||||
System.out.println("\nTEST: iter=" + iter + " rect=" + rect);
|
||||
|
@ -588,9 +557,9 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
|
|||
if (hits.get(docID) != expected) {
|
||||
String id = s.doc(docID).get("id");
|
||||
if (expected) {
|
||||
System.out.println(Thread.currentThread().getName() + ": id=" + id + " docID=" + docID + " should match but did not");
|
||||
System.out.println("TEST: id=" + id + " docID=" + docID + " should match but did not");
|
||||
} else {
|
||||
System.out.println(Thread.currentThread().getName() + ": id=" + id + " docID=" + docID + " should not match but did");
|
||||
System.out.println("TEST: id=" + id + " docID=" + docID + " should not match but did");
|
||||
}
|
||||
System.out.println(" rect=" + rect);
|
||||
System.out.println(" lat=" + latDoc1 + " lon=" + lonDoc1 + "\n lat=" + latDoc2 + " lon=" + lonDoc2);
|
||||
|
@ -697,9 +666,9 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
|
|||
public double randomLat(boolean small) {
|
||||
double result;
|
||||
if (small) {
|
||||
result = normalizeLat(originLat + latRange * (random().nextDouble() - 0.5));
|
||||
result = GeoTestUtil.nextLatitudeNear(originLat);
|
||||
} else {
|
||||
result = -90 + 180.0 * random().nextDouble();
|
||||
result = GeoTestUtil.nextLatitude();
|
||||
}
|
||||
return quantizeLat(result);
|
||||
}
|
||||
|
@ -707,74 +676,14 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
|
|||
public double randomLon(boolean small) {
|
||||
double result;
|
||||
if (small) {
|
||||
result = normalizeLon(originLon + lonRange * (random().nextDouble() - 0.5));
|
||||
result = GeoTestUtil.nextLongitudeNear(originLon);
|
||||
} else {
|
||||
result = -180 + 360.0 * random().nextDouble();
|
||||
result = GeoTestUtil.nextLongitude();
|
||||
}
|
||||
return quantizeLon(result);
|
||||
}
|
||||
|
||||
/** Returns {polyLats, polyLons} double[] array */
|
||||
private double[][] surpriseMePolygon() {
|
||||
// repeat until we get a poly that doesn't cross dateline:
|
||||
newPoly:
|
||||
while (true) {
|
||||
//System.out.println("\nPOLY ITER");
|
||||
double centerLat = randomLat(false);
|
||||
double centerLon = randomLon(false);
|
||||
|
||||
double radius = 0.1 + 20 * random().nextDouble();
|
||||
double radiusDelta = random().nextDouble();
|
||||
|
||||
ArrayList<Double> lats = new ArrayList<>();
|
||||
ArrayList<Double> lons = new ArrayList<>();
|
||||
double angle = 0.0;
|
||||
while (true) {
|
||||
angle += random().nextDouble()*40.0;
|
||||
//System.out.println(" angle " + angle);
|
||||
if (angle > 360) {
|
||||
break;
|
||||
}
|
||||
double len = radius * (1.0 - radiusDelta + radiusDelta * random().nextDouble());
|
||||
//System.out.println(" len=" + len);
|
||||
double lat = centerLat + len * Math.cos(Math.toRadians(angle));
|
||||
double lon = centerLon + len * Math.sin(Math.toRadians(angle));
|
||||
if (lon <= GeoUtils.MIN_LON_INCL || lon >= GeoUtils.MAX_LON_INCL) {
|
||||
// cannot cross dateline: try again!
|
||||
continue newPoly;
|
||||
}
|
||||
if (lat > 90) {
|
||||
// cross the north pole
|
||||
lat = 180 - lat;
|
||||
lon = 180 - lon;
|
||||
} else if (lat < -90) {
|
||||
// cross the south pole
|
||||
lat = -180 - lat;
|
||||
lon = 180 - lon;
|
||||
}
|
||||
if (lon <= GeoUtils.MIN_LON_INCL || lon >= GeoUtils.MAX_LON_INCL) {
|
||||
// cannot cross dateline: try again!
|
||||
continue newPoly;
|
||||
}
|
||||
lats.add(lat);
|
||||
lons.add(lon);
|
||||
|
||||
//System.out.println(" lat=" + lats.get(lats.size()-1) + " lon=" + lons.get(lons.size()-1));
|
||||
}
|
||||
|
||||
// close it
|
||||
lats.add(lats.get(0));
|
||||
lons.add(lons.get(0));
|
||||
|
||||
double[] latsArray = new double[lats.size()];
|
||||
double[] lonsArray = new double[lons.size()];
|
||||
for(int i=0;i<lats.size();i++) {
|
||||
latsArray[i] = lats.get(i);
|
||||
lonsArray[i] = lons.get(i);
|
||||
}
|
||||
return new double[][] {latsArray, lonsArray};
|
||||
}
|
||||
}
|
||||
|
||||
/** Override this to quantize randomly generated lat, so the test won't fail due to quantization errors, which are 1) annoying to debug,
|
||||
* and 2) should never affect "real" usage terribly. */
|
||||
|
@ -788,25 +697,12 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
|
|||
return lon;
|
||||
}
|
||||
|
||||
protected GeoRect randomRect(boolean small, boolean canCrossDateLine) {
|
||||
double lat0 = randomLat(small);
|
||||
double lat1 = randomLat(small);
|
||||
double lon0 = randomLon(small);
|
||||
double lon1 = randomLon(small);
|
||||
|
||||
if (lat1 < lat0) {
|
||||
double x = lat0;
|
||||
lat0 = lat1;
|
||||
lat1 = x;
|
||||
protected GeoRect randomRect(boolean small) {
|
||||
if (small) {
|
||||
return GeoTestUtil.nextBoxNear(originLat, originLon);
|
||||
} else {
|
||||
return GeoTestUtil.nextBox();
|
||||
}
|
||||
|
||||
if (canCrossDateLine == false && lon1 < lon0) {
|
||||
double x = lon0;
|
||||
lon0 = lon1;
|
||||
lon1 = x;
|
||||
}
|
||||
|
||||
return new GeoRect(lat0, lat1, lon0, lon1);
|
||||
}
|
||||
|
||||
protected void initIndexWriterConfig(String field, IndexWriterConfig iwc) {
|
||||
|
@ -832,97 +728,13 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
static final boolean polygonContainsPoint(double polyLats[], double polyLons[], double pointLat, double pointLon) {
|
||||
return GeoRelationUtils.pointInPolygon(polyLats, polyLons, pointLat, pointLon);
|
||||
private void verify(boolean small, double[] lats, double[] lons) throws Exception {
|
||||
verifyRandomRectangles(small, lats, lons);
|
||||
verifyRandomDistances(small, lats, lons);
|
||||
verifyRandomPolygons(small, lats, lons);
|
||||
}
|
||||
|
||||
static final boolean circleContainsPoint(double centerLat, double centerLon, double radiusMeters, double pointLat, double pointLon) {
|
||||
double distanceMeters = SloppyMath.haversinMeters(centerLat, centerLon, pointLat, pointLon);
|
||||
boolean result = distanceMeters <= radiusMeters;
|
||||
//System.out.println(" shouldMatch? centerLon=" + centerLon + " centerLat=" + centerLat + " pointLon=" + pointLon + " pointLat=" + pointLat + " result=" + result + " distanceMeters=" + (distanceKM * 1000));
|
||||
return result;
|
||||
}
|
||||
|
||||
private static abstract class VerifyHits {
|
||||
|
||||
public void test(AtomicBoolean failed, boolean small, IndexSearcher s, NumericDocValues docIDToID, Set<Integer> deleted, Query query, double[] lats, double[] lons) throws Exception {
|
||||
int maxDoc = s.getIndexReader().maxDoc();
|
||||
final FixedBitSet hits = new FixedBitSet(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);
|
||||
}
|
||||
});
|
||||
|
||||
boolean fail = false;
|
||||
|
||||
// Change to false to see all wrong hits:
|
||||
boolean failFast = true;
|
||||
|
||||
for(int docID=0;docID<maxDoc;docID++) {
|
||||
int id = (int) docIDToID.get(docID);
|
||||
boolean expected;
|
||||
if (deleted.contains(id)) {
|
||||
expected = false;
|
||||
} else if (Double.isNaN(lats[id])) {
|
||||
expected = false;
|
||||
} else {
|
||||
expected = shouldMatch(lats[id], lons[id]);
|
||||
}
|
||||
|
||||
if (hits.get(docID) != expected) {
|
||||
|
||||
// Print only one failed hit; add a true || in here to see all failures:
|
||||
if (failFast == false || failed.getAndSet(true) == false) {
|
||||
if (expected) {
|
||||
System.out.println(Thread.currentThread().getName() + ": id=" + id + " should match but did not");
|
||||
} else {
|
||||
System.out.println(Thread.currentThread().getName() + ": id=" + id + " should not match but did");
|
||||
}
|
||||
System.out.println(" small=" + small + " query=" + query +
|
||||
" docID=" + docID + "\n lat=" + lats[id] + " lon=" + lons[id] +
|
||||
"\n deleted?=" + deleted.contains(id));
|
||||
if (Double.isNaN(lats[id]) == false) {
|
||||
describe(docID, lats[id], lons[id]);
|
||||
}
|
||||
if (failFast) {
|
||||
fail("wrong hit (first of possibly more)");
|
||||
} else {
|
||||
fail = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
failed.set(true);
|
||||
fail("some hits were wrong");
|
||||
}
|
||||
}
|
||||
|
||||
/** Return true if we definitely should match, false if we definitely
|
||||
* should not match, and null if it's a borderline case which might
|
||||
* go either way. */
|
||||
protected abstract boolean shouldMatch(double lat, double lon);
|
||||
|
||||
protected abstract void describe(int docID, double lat, double lon);
|
||||
}
|
||||
|
||||
protected void verify(boolean small, double[] lats, double[] lons) throws Exception {
|
||||
protected void verifyRandomRectangles(boolean small, double[] lats, double[] lons) throws Exception {
|
||||
IndexWriterConfig iwc = newIndexWriterConfig();
|
||||
// Else we can get O(N^2) merging:
|
||||
int mbd = iwc.getMaxBufferedDocs();
|
||||
|
@ -963,142 +775,365 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
|
|||
final IndexReader r = DirectoryReader.open(w);
|
||||
w.close();
|
||||
|
||||
// We can't wrap with "exotic" readers because the BKD query must see the BKDDVFormat:
|
||||
IndexSearcher s = newSearcher(r, false);
|
||||
IndexSearcher s = newSearcher(r);
|
||||
|
||||
final int iters = atLeast(75);
|
||||
|
||||
final AtomicBoolean failed = new AtomicBoolean();
|
||||
int iters = atLeast(25);
|
||||
|
||||
NumericDocValues docIDToID = MultiDocValues.getNumericValues(r, "id");
|
||||
|
||||
for (int iter=0;iter<iters && failed.get() == false;iter++) {
|
||||
Bits liveDocs = MultiFields.getLiveDocs(s.getIndexReader());
|
||||
int maxDoc = s.getIndexReader().maxDoc();
|
||||
|
||||
for (int iter=0;iter<iters;iter++) {
|
||||
|
||||
if (VERBOSE) {
|
||||
System.out.println("\n" + Thread.currentThread().getName() + ": TEST: iter=" + iter + " s=" + s);
|
||||
System.out.println("\nTEST: iter=" + iter + " s=" + s);
|
||||
}
|
||||
Query query;
|
||||
VerifyHits verifyHits;
|
||||
|
||||
if (random().nextBoolean()) {
|
||||
// Rect: don't allow dateline crossing when testing small:
|
||||
final GeoRect rect = randomRect(small, small == false);
|
||||
GeoRect rect = randomRect(small);
|
||||
|
||||
query = newRectQuery(FIELD_NAME, rect.minLat, rect.maxLat, rect.minLon, rect.maxLon);
|
||||
Query query = newRectQuery(FIELD_NAME, rect.minLat, rect.maxLat, rect.minLon, rect.maxLon);
|
||||
|
||||
if (VERBOSE) {
|
||||
System.out.println(" query=" + query);
|
||||
}
|
||||
|
||||
final FixedBitSet hits = new FixedBitSet(maxDoc);
|
||||
s.search(query, new SimpleCollector() {
|
||||
|
||||
private int docBase;
|
||||
|
||||
verifyHits = new VerifyHits() {
|
||||
@Override
|
||||
protected boolean shouldMatch(double pointLat, double pointLon) {
|
||||
return rectContainsPoint(rect, pointLat, pointLon);
|
||||
public boolean needsScores() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void describe(int docID, double lat, double lon) {
|
||||
protected void doSetNextReader(LeafReaderContext context) throws IOException {
|
||||
docBase = context.docBase;
|
||||
}
|
||||
};
|
||||
|
||||
} else if (random().nextBoolean()) {
|
||||
// Distance
|
||||
final double centerLat = randomLat(small);
|
||||
final double centerLon = randomLon(small);
|
||||
@Override
|
||||
public void collect(int doc) {
|
||||
hits.set(docBase+doc);
|
||||
}
|
||||
});
|
||||
|
||||
final double radiusMeters;
|
||||
if (small) {
|
||||
// Approx 3 degrees lon at the equator:
|
||||
radiusMeters = random().nextDouble() * 333000 + 1.0;
|
||||
boolean fail = false;
|
||||
for(int docID=0;docID<maxDoc;docID++) {
|
||||
int id = (int) docIDToID.get(docID);
|
||||
boolean expected;
|
||||
if (liveDocs != null && liveDocs.get(docID) == false) {
|
||||
// document is deleted
|
||||
expected = false;
|
||||
} else if (Double.isNaN(lats[id])) {
|
||||
expected = false;
|
||||
} else {
|
||||
// So the query can cover at most 50% of the earth's surface:
|
||||
radiusMeters = random().nextDouble() * GeoUtils.SEMIMAJOR_AXIS * Math.PI / 2.0 + 1.0;
|
||||
expected = rectContainsPoint(rect, lats[id], lons[id]);
|
||||
}
|
||||
|
||||
if (hits.get(docID) != expected) {
|
||||
StringBuilder b = new StringBuilder();
|
||||
|
||||
if (expected) {
|
||||
b.append("FAIL: id=" + id + " should match but did not\n");
|
||||
} else {
|
||||
b.append("FAIL: id=" + id + " should not match but did\n");
|
||||
}
|
||||
b.append(" query=" + query + " docID=" + docID + "\n");
|
||||
b.append(" lat=" + lats[id] + " lon=" + lons[id] + "\n");
|
||||
b.append(" deleted?=" + (liveDocs != null && liveDocs.get(docID) == false));
|
||||
if (true) {
|
||||
fail("wrong hit (first of possibly more):\n\n" + b);
|
||||
} else {
|
||||
System.out.println(b.toString());
|
||||
fail = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (fail) {
|
||||
fail("some hits were wrong");
|
||||
}
|
||||
}
|
||||
|
||||
IOUtils.close(r, dir);
|
||||
}
|
||||
|
||||
protected void verifyRandomDistances(boolean small, 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);
|
||||
}
|
||||
Directory dir;
|
||||
if (lats.length > 100000) {
|
||||
dir = newFSDirectory(createTempDir(getClass().getSimpleName()));
|
||||
} else {
|
||||
dir = newDirectory();
|
||||
}
|
||||
|
||||
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) {
|
||||
addPointToDoc(FIELD_NAME, doc, 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) {
|
||||
final DecimalFormat df = new DecimalFormat("#,###.00", DecimalFormatSymbols.getInstance(Locale.ENGLISH));
|
||||
System.out.println(" radiusMeters = " + df.format(radiusMeters));
|
||||
System.out.println(" delete id=" + idToDelete);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
query = newDistanceQuery(FIELD_NAME, centerLat, centerLon, radiusMeters);
|
||||
if (random().nextBoolean()) {
|
||||
w.forceMerge(1);
|
||||
}
|
||||
final IndexReader r = DirectoryReader.open(w);
|
||||
w.close();
|
||||
|
||||
verifyHits = new VerifyHits() {
|
||||
@Override
|
||||
protected boolean shouldMatch(double pointLat, double pointLon) {
|
||||
return circleContainsPoint(centerLat, centerLon, radiusMeters, pointLat, pointLon);
|
||||
}
|
||||
IndexSearcher s = newSearcher(r);
|
||||
|
||||
@Override
|
||||
protected void describe(int docID, double pointLat, double pointLon) {
|
||||
double distanceMeters = SloppyMath.haversinMeters(centerLat, centerLon, pointLat, pointLon);
|
||||
System.out.println(" docID=" + docID + " centerLat=" + centerLat + " centerLon=" + centerLon
|
||||
+ " pointLat=" + pointLat + " pointLon=" + pointLon + " distanceMeters=" + distanceMeters
|
||||
+ " vs radiusMeters=" + radiusMeters);
|
||||
}
|
||||
};
|
||||
int iters = atLeast(25);
|
||||
|
||||
// TODO: get poly query working with dateline crossing too (how?)!
|
||||
} else {
|
||||
NumericDocValues docIDToID = MultiDocValues.getNumericValues(r, "id");
|
||||
|
||||
// TODO: poly query can't handle dateline crossing yet:
|
||||
final GeoRect bbox = randomRect(small, false);
|
||||
Bits liveDocs = MultiFields.getLiveDocs(s.getIndexReader());
|
||||
int maxDoc = s.getIndexReader().maxDoc();
|
||||
|
||||
// Polygon
|
||||
final double[] polyLats;
|
||||
final double[] polyLons;
|
||||
// TODO: factor this out, maybe if we add Polygon class?
|
||||
switch (random().nextInt(3)) {
|
||||
case 0:
|
||||
// box
|
||||
polyLats = new double[5];
|
||||
polyLons = new double[5];
|
||||
polyLats[0] = bbox.minLat;
|
||||
polyLons[0] = bbox.minLon;
|
||||
polyLats[1] = bbox.maxLat;
|
||||
polyLons[1] = bbox.minLon;
|
||||
polyLats[2] = bbox.maxLat;
|
||||
polyLons[2] = bbox.maxLon;
|
||||
polyLats[3] = bbox.minLat;
|
||||
polyLons[3] = bbox.maxLon;
|
||||
polyLats[4] = bbox.minLat;
|
||||
polyLons[4] = bbox.minLon;
|
||||
break;
|
||||
case 1:
|
||||
// right triangle
|
||||
polyLats = new double[4];
|
||||
polyLons = new double[4];
|
||||
polyLats[0] = bbox.minLat;
|
||||
polyLons[0] = bbox.minLon;
|
||||
polyLats[1] = bbox.maxLat;
|
||||
polyLons[1] = bbox.minLon;
|
||||
polyLats[2] = bbox.maxLat;
|
||||
polyLons[2] = bbox.maxLon;
|
||||
polyLats[3] = bbox.minLat;
|
||||
polyLons[3] = bbox.minLon;
|
||||
break;
|
||||
default:
|
||||
// surprise me!
|
||||
double[][] res = surpriseMePolygon();
|
||||
polyLats = res[0];
|
||||
polyLons = res[1];
|
||||
break;
|
||||
}
|
||||
query = newPolygonQuery(FIELD_NAME, polyLats, polyLons);
|
||||
for (int iter=0;iter<iters;iter++) {
|
||||
|
||||
verifyHits = new VerifyHits() {
|
||||
@Override
|
||||
protected boolean shouldMatch(double pointLat, double pointLon) {
|
||||
return polygonContainsPoint(polyLats, polyLons, pointLat, pointLon);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void describe(int docID, double lat, double lon) {
|
||||
}
|
||||
};
|
||||
if (VERBOSE) {
|
||||
System.out.println("\nTEST: iter=" + iter + " s=" + s);
|
||||
}
|
||||
|
||||
if (query != null) {
|
||||
// Distance
|
||||
final double centerLat = randomLat(small);
|
||||
final double centerLon = randomLon(small);
|
||||
|
||||
if (VERBOSE) {
|
||||
System.out.println(" query=" + query);
|
||||
final double radiusMeters;
|
||||
if (small) {
|
||||
// Approx 3 degrees lon at the equator:
|
||||
radiusMeters = random().nextDouble() * 333000 + 1.0;
|
||||
} else {
|
||||
// So the query can cover at most 50% of the earth's surface:
|
||||
radiusMeters = random().nextDouble() * GeoUtils.SEMIMAJOR_AXIS * Math.PI / 2.0 + 1.0;
|
||||
}
|
||||
|
||||
if (VERBOSE) {
|
||||
final DecimalFormat df = new DecimalFormat("#,###.00", DecimalFormatSymbols.getInstance(Locale.ENGLISH));
|
||||
System.out.println(" radiusMeters = " + df.format(radiusMeters));
|
||||
}
|
||||
|
||||
Query query = newDistanceQuery(FIELD_NAME, centerLat, centerLon, radiusMeters);
|
||||
|
||||
if (VERBOSE) {
|
||||
System.out.println(" query=" + query);
|
||||
}
|
||||
|
||||
final FixedBitSet hits = new FixedBitSet(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);
|
||||
}
|
||||
});
|
||||
|
||||
boolean fail = false;
|
||||
for(int docID=0;docID<maxDoc;docID++) {
|
||||
int id = (int) docIDToID.get(docID);
|
||||
boolean expected;
|
||||
if (liveDocs != null && liveDocs.get(docID) == false) {
|
||||
// document is deleted
|
||||
expected = false;
|
||||
} else if (Double.isNaN(lats[id])) {
|
||||
expected = false;
|
||||
} else {
|
||||
expected = SloppyMath.haversinMeters(centerLat, centerLon, lats[id], lons[id]) <= radiusMeters;
|
||||
}
|
||||
|
||||
verifyHits.test(failed, small, s, docIDToID, deleted, query, lats, lons);
|
||||
if (hits.get(docID) != expected) {
|
||||
StringBuilder b = new StringBuilder();
|
||||
|
||||
if (expected) {
|
||||
b.append("FAIL: id=" + id + " should match but did not\n");
|
||||
} else {
|
||||
b.append("FAIL: id=" + id + " should not match but did\n");
|
||||
}
|
||||
b.append(" query=" + query + " docID=" + docID + "\n");
|
||||
b.append(" lat=" + lats[id] + " lon=" + lons[id] + "\n");
|
||||
b.append(" deleted?=" + (liveDocs != null && liveDocs.get(docID) == false));
|
||||
if (Double.isNaN(lats[id]) == false) {
|
||||
double distanceMeters = SloppyMath.haversinMeters(centerLat, centerLon, lats[id], lons[id]);
|
||||
b.append(" centerLat=" + centerLat + " centerLon=" + centerLon + " distanceMeters=" + distanceMeters + " vs radiusMeters=" + radiusMeters);
|
||||
}
|
||||
if (true) {
|
||||
fail("wrong hit (first of possibly more):\n\n" + b);
|
||||
} else {
|
||||
System.out.println(b.toString());
|
||||
fail = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (fail) {
|
||||
fail("some hits were wrong");
|
||||
}
|
||||
}
|
||||
|
||||
IOUtils.close(r, dir);
|
||||
}
|
||||
|
||||
protected void verifyRandomPolygons(boolean small, 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);
|
||||
}
|
||||
Directory dir;
|
||||
if (lats.length > 100000) {
|
||||
dir = newFSDirectory(createTempDir(getClass().getSimpleName()));
|
||||
} else {
|
||||
dir = newDirectory();
|
||||
}
|
||||
|
||||
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) {
|
||||
addPointToDoc(FIELD_NAME, doc, 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.out.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 points needs to work:
|
||||
IndexSearcher s = newSearcher(r);
|
||||
|
||||
final int iters = atLeast(75);
|
||||
|
||||
NumericDocValues docIDToID = MultiDocValues.getNumericValues(r, "id");
|
||||
|
||||
Bits liveDocs = MultiFields.getLiveDocs(s.getIndexReader());
|
||||
int maxDoc = s.getIndexReader().maxDoc();
|
||||
|
||||
for (int iter=0;iter<iters;iter++) {
|
||||
|
||||
if (VERBOSE) {
|
||||
System.out.println("\nTEST: iter=" + iter + " s=" + s);
|
||||
}
|
||||
|
||||
// Polygon
|
||||
final double[][] polygon;
|
||||
if (small) {
|
||||
polygon = GeoTestUtil.nextPolygonNear(originLat, originLon);
|
||||
} else {
|
||||
polygon = GeoTestUtil.nextPolygon();
|
||||
}
|
||||
|
||||
final double[] polyLats = polygon[0];
|
||||
final double[] polyLons = polygon[1];
|
||||
Query query = newPolygonQuery(FIELD_NAME, polyLats, polyLons);
|
||||
|
||||
if (VERBOSE) {
|
||||
System.out.println(" query=" + query);
|
||||
}
|
||||
|
||||
final FixedBitSet hits = new FixedBitSet(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);
|
||||
}
|
||||
});
|
||||
|
||||
boolean fail = false;
|
||||
for(int docID=0;docID<maxDoc;docID++) {
|
||||
int id = (int) docIDToID.get(docID);
|
||||
boolean expected;
|
||||
if (liveDocs != null && liveDocs.get(docID) == false) {
|
||||
// document is deleted
|
||||
expected = false;
|
||||
} else if (Double.isNaN(lats[id])) {
|
||||
expected = false;
|
||||
} else {
|
||||
expected = GeoRelationUtils.pointInPolygon(polyLats, polyLons, lats[id], lons[id]);
|
||||
}
|
||||
|
||||
if (hits.get(docID) != expected) {
|
||||
StringBuilder b = new StringBuilder();
|
||||
|
||||
if (expected) {
|
||||
b.append("FAIL: id=" + id + " should match but did not\n");
|
||||
} else {
|
||||
b.append("FAIL: id=" + id + " should not match but did\n");
|
||||
}
|
||||
b.append(" query=" + query + " docID=" + docID + "\n");
|
||||
b.append(" lat=" + lats[id] + " lon=" + lons[id] + "\n");
|
||||
b.append(" deleted?=" + (liveDocs != null && liveDocs.get(docID) == false));
|
||||
b.append(" polyLats=" + Arrays.toString(polyLats));
|
||||
b.append(" polyLons=" + Arrays.toString(polyLons));
|
||||
if (true) {
|
||||
fail("wrong hit (first of possibly more):\n\n" + b);
|
||||
} else {
|
||||
System.out.println(b.toString());
|
||||
fail = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (fail) {
|
||||
fail("some hits were wrong");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1106,7 +1141,14 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
|
|||
}
|
||||
|
||||
public void testRectBoundariesAreInclusive() throws Exception {
|
||||
GeoRect rect = randomRect(random().nextBoolean(), false);
|
||||
GeoRect rect;
|
||||
// TODO: why this dateline leniency???
|
||||
while (true) {
|
||||
rect = randomRect(random().nextBoolean());
|
||||
if (rect.crossesDateline() == false) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Directory dir = newDirectory();
|
||||
IndexWriterConfig iwc = newIndexWriterConfig();
|
||||
RandomIndexWriter w = new RandomIndexWriter(random(), dir, iwc);
|
||||
|
@ -1183,8 +1225,8 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
|
|||
RandomIndexWriter writer = new RandomIndexWriter(random(), dir, iwc);
|
||||
|
||||
for (int i = 0; i < numDocs; i++) {
|
||||
double latRaw = -90 + 180.0 * random().nextDouble();
|
||||
double lonRaw = -180 + 360.0 * random().nextDouble();
|
||||
double latRaw = GeoTestUtil.nextLatitude();
|
||||
double lonRaw = GeoTestUtil.nextLongitude();
|
||||
// pre-normalize up front, so we can just use quantized value for testing and do simple exact comparisons
|
||||
double lat = quantizeLat(latRaw);
|
||||
double lon = quantizeLon(lonRaw);
|
||||
|
@ -1198,8 +1240,8 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
|
|||
IndexSearcher searcher = newSearcher(reader);
|
||||
|
||||
for (int i = 0; i < numQueries; i++) {
|
||||
double lat = -90 + 180.0 * random().nextDouble();
|
||||
double lon = -180 + 360.0 * random().nextDouble();
|
||||
double lat = GeoTestUtil.nextLatitude();
|
||||
double lon = GeoTestUtil.nextLongitude();
|
||||
double radius = 50000000D * random().nextDouble();
|
||||
|
||||
BitSet expected = new BitSet();
|
||||
|
@ -1239,7 +1281,7 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
|
|||
public void testEquals() throws Exception {
|
||||
Query q1, q2;
|
||||
|
||||
GeoRect rect = randomRect(false, true);
|
||||
GeoRect rect = randomRect(false);
|
||||
|
||||
q1 = newRectQuery("field", rect.minLat, rect.maxLat, rect.minLon, rect.maxLon);
|
||||
q2 = newRectQuery("field", rect.minLat, rect.maxLat, rect.minLon, rect.maxLon);
|
||||
|
|
|
@ -0,0 +1,269 @@
|
|||
/*
|
||||
* 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.spatial.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Random;
|
||||
|
||||
import com.carrotsearch.randomizedtesting.RandomizedContext;
|
||||
|
||||
/** static methods for testing geo */
|
||||
public class GeoTestUtil {
|
||||
|
||||
/** returns next pseudorandom latitude (anywhere) */
|
||||
public static double nextLatitude() {
|
||||
return -90 + 180.0 * random().nextDouble();
|
||||
}
|
||||
|
||||
/** returns next pseudorandom longitude (anywhere) */
|
||||
public static double nextLongitude() {
|
||||
return -180 + 360.0 * random().nextDouble();
|
||||
}
|
||||
|
||||
/** returns next pseudorandom latitude, kinda close to {@code otherLatitude} */
|
||||
public static double nextLatitudeNear(double otherLatitude) {
|
||||
GeoUtils.checkLatitude(otherLatitude);
|
||||
return normalizeLatitude(otherLatitude + random().nextDouble() - 0.5);
|
||||
}
|
||||
|
||||
/** returns next pseudorandom longitude, kinda close to {@code otherLongitude} */
|
||||
public static double nextLongitudeNear(double otherLongitude) {
|
||||
GeoUtils.checkLongitude(otherLongitude);
|
||||
return normalizeLongitude(otherLongitude + random().nextDouble() - 0.5);
|
||||
}
|
||||
|
||||
/**
|
||||
* returns next pseudorandom latitude, kinda close to {@code minLatitude/maxLatitude}
|
||||
* <b>NOTE:</b>minLatitude/maxLatitude are merely guidelines. the returned value is sometimes
|
||||
* outside of that range! this is to facilitate edge testing.
|
||||
*/
|
||||
public static double nextLatitudeAround(double minLatitude, double maxLatitude) {
|
||||
GeoUtils.checkLatitude(minLatitude);
|
||||
GeoUtils.checkLatitude(maxLatitude);
|
||||
return normalizeLatitude(randomRangeMaybeSlightlyOutside(minLatitude, maxLatitude));
|
||||
}
|
||||
|
||||
/**
|
||||
* returns next pseudorandom longitude, kinda close to {@code minLongitude/maxLongitude}
|
||||
* <b>NOTE:</b>minLongitude/maxLongitude are merely guidelines. the returned value is sometimes
|
||||
* outside of that range! this is to facilitate edge testing.
|
||||
*/
|
||||
public static double nextLongitudeAround(double minLongitude, double maxLongitude) {
|
||||
GeoUtils.checkLongitude(minLongitude);
|
||||
GeoUtils.checkLongitude(maxLongitude);
|
||||
return normalizeLongitude(randomRangeMaybeSlightlyOutside(minLongitude, maxLongitude));
|
||||
}
|
||||
|
||||
/** returns next pseudorandom box: can cross the 180th meridian */
|
||||
public static GeoRect nextBox() {
|
||||
return nextBoxInternal(nextLatitude(), nextLatitude(), nextLongitude(), nextLongitude(), true);
|
||||
}
|
||||
|
||||
/** returns next pseudorandom box, can cross the 180th meridian, kinda close to {@code otherLatitude} and {@code otherLongitude} */
|
||||
public static GeoRect nextBoxNear(double otherLatitude, double otherLongitude) {
|
||||
GeoUtils.checkLongitude(otherLongitude);
|
||||
GeoUtils.checkLongitude(otherLongitude);
|
||||
return nextBoxInternal(nextLatitudeNear(otherLatitude), nextLatitudeNear(otherLatitude),
|
||||
nextLongitudeNear(otherLongitude), nextLongitudeNear(otherLongitude), true);
|
||||
}
|
||||
|
||||
/** returns next pseudorandom polygon */
|
||||
public static double[][] nextPolygon() {
|
||||
if (random().nextBoolean()) {
|
||||
return surpriseMePolygon(null, null);
|
||||
}
|
||||
|
||||
GeoRect box = nextBoxInternal(nextLatitude(), nextLatitude(), nextLongitude(), nextLongitude(), false);
|
||||
if (random().nextBoolean()) {
|
||||
// box
|
||||
return boxPolygon(box);
|
||||
} else {
|
||||
// triangle
|
||||
return trianglePolygon(box);
|
||||
}
|
||||
}
|
||||
|
||||
/** returns next pseudorandom polygon, kinda close to {@code otherLatitude} and {@code otherLongitude} */
|
||||
public static double[][] nextPolygonNear(double otherLatitude, double otherLongitude) {
|
||||
if (random().nextBoolean()) {
|
||||
return surpriseMePolygon(otherLatitude, otherLongitude);
|
||||
}
|
||||
|
||||
GeoRect box = nextBoxInternal(nextLatitudeNear(otherLatitude), nextLatitudeNear(otherLatitude),
|
||||
nextLongitudeNear(otherLongitude), nextLongitudeNear(otherLongitude), false);
|
||||
if (random().nextBoolean()) {
|
||||
// box
|
||||
return boxPolygon(box);
|
||||
} else {
|
||||
// triangle
|
||||
return trianglePolygon(box);
|
||||
}
|
||||
}
|
||||
|
||||
private static GeoRect nextBoxInternal(double lat0, double lat1, double lon0, double lon1, boolean canCrossDateLine) {
|
||||
if (lat1 < lat0) {
|
||||
double x = lat0;
|
||||
lat0 = lat1;
|
||||
lat1 = x;
|
||||
}
|
||||
|
||||
if (canCrossDateLine == false && lon1 < lon0) {
|
||||
double x = lon0;
|
||||
lon0 = lon1;
|
||||
lon1 = x;
|
||||
}
|
||||
|
||||
return new GeoRect(lat0, lat1, lon0, lon1);
|
||||
}
|
||||
|
||||
private static double[][] boxPolygon(GeoRect box) {
|
||||
assert box.crossesDateline() == false;
|
||||
final double[] polyLats = new double[5];
|
||||
final double[] polyLons = new double[5];
|
||||
polyLats[0] = box.minLat;
|
||||
polyLons[0] = box.minLon;
|
||||
polyLats[1] = box.maxLat;
|
||||
polyLons[1] = box.minLon;
|
||||
polyLats[2] = box.maxLat;
|
||||
polyLons[2] = box.maxLon;
|
||||
polyLats[3] = box.minLat;
|
||||
polyLons[3] = box.maxLon;
|
||||
polyLats[4] = box.minLat;
|
||||
polyLons[4] = box.minLon;
|
||||
return new double[][] { polyLats, polyLons };
|
||||
}
|
||||
|
||||
private static double[][] trianglePolygon(GeoRect box) {
|
||||
assert box.crossesDateline() == false;
|
||||
final double[] polyLats = new double[4];
|
||||
final double[] polyLons = new double[4];
|
||||
polyLats[0] = box.minLat;
|
||||
polyLons[0] = box.minLon;
|
||||
polyLats[1] = box.maxLat;
|
||||
polyLons[1] = box.minLon;
|
||||
polyLats[2] = box.maxLat;
|
||||
polyLons[2] = box.maxLon;
|
||||
polyLats[3] = box.minLat;
|
||||
polyLons[3] = box.minLon;
|
||||
return new double[][] { polyLats, polyLons };
|
||||
}
|
||||
|
||||
/** Returns {polyLats, polyLons} double[] array */
|
||||
private static double[][] surpriseMePolygon(Double otherLatitude, Double otherLongitude) {
|
||||
// repeat until we get a poly that doesn't cross dateline:
|
||||
newPoly:
|
||||
while (true) {
|
||||
//System.out.println("\nPOLY ITER");
|
||||
final double centerLat;
|
||||
final double centerLon;
|
||||
if (otherLatitude == null) {
|
||||
centerLat = nextLatitude();
|
||||
centerLon = nextLongitude();
|
||||
} else {
|
||||
GeoUtils.checkLatitude(otherLatitude);
|
||||
GeoUtils.checkLongitude(otherLongitude);
|
||||
centerLat = nextLatitudeNear(otherLatitude);
|
||||
centerLon = nextLongitudeNear(otherLongitude);
|
||||
}
|
||||
|
||||
double radius = 0.1 + 20 * random().nextDouble();
|
||||
double radiusDelta = random().nextDouble();
|
||||
|
||||
ArrayList<Double> lats = new ArrayList<>();
|
||||
ArrayList<Double> lons = new ArrayList<>();
|
||||
double angle = 0.0;
|
||||
while (true) {
|
||||
angle += random().nextDouble()*40.0;
|
||||
//System.out.println(" angle " + angle);
|
||||
if (angle > 360) {
|
||||
break;
|
||||
}
|
||||
double len = radius * (1.0 - radiusDelta + radiusDelta * random().nextDouble());
|
||||
//System.out.println(" len=" + len);
|
||||
double lat = centerLat + len * Math.cos(Math.toRadians(angle));
|
||||
double lon = centerLon + len * Math.sin(Math.toRadians(angle));
|
||||
if (lon <= GeoUtils.MIN_LON_INCL || lon >= GeoUtils.MAX_LON_INCL) {
|
||||
// cannot cross dateline: try again!
|
||||
continue newPoly;
|
||||
}
|
||||
if (lat > 90) {
|
||||
// cross the north pole
|
||||
lat = 180 - lat;
|
||||
lon = 180 - lon;
|
||||
} else if (lat < -90) {
|
||||
// cross the south pole
|
||||
lat = -180 - lat;
|
||||
lon = 180 - lon;
|
||||
}
|
||||
if (lon <= GeoUtils.MIN_LON_INCL || lon >= GeoUtils.MAX_LON_INCL) {
|
||||
// cannot cross dateline: try again!
|
||||
continue newPoly;
|
||||
}
|
||||
lats.add(lat);
|
||||
lons.add(lon);
|
||||
|
||||
//System.out.println(" lat=" + lats.get(lats.size()-1) + " lon=" + lons.get(lons.size()-1));
|
||||
}
|
||||
|
||||
// close it
|
||||
lats.add(lats.get(0));
|
||||
lons.add(lons.get(0));
|
||||
|
||||
double[] latsArray = new double[lats.size()];
|
||||
double[] lonsArray = new double[lons.size()];
|
||||
for(int i=0;i<lats.size();i++) {
|
||||
latsArray[i] = lats.get(i);
|
||||
lonsArray[i] = lons.get(i);
|
||||
}
|
||||
return new double[][] {latsArray, lonsArray};
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns random double min to max or up to 1% outside of that range */
|
||||
private static double randomRangeMaybeSlightlyOutside(double min, double max) {
|
||||
return min + (random().nextDouble() + (0.5 - random().nextDouble()) * .02) * (max - min);
|
||||
}
|
||||
|
||||
/** Puts latitude in range of -90 to 90. */
|
||||
private static double normalizeLatitude(double latitude) {
|
||||
if (latitude >= -90 && latitude <= 90) {
|
||||
return latitude; //common case, and avoids slight double precision shifting
|
||||
}
|
||||
double off = Math.abs((latitude + 90) % 360);
|
||||
return (off <= 180 ? off : 360-off) - 90;
|
||||
}
|
||||
|
||||
/** Puts longitude in range of -180 to +180. */
|
||||
private static double normalizeLongitude(double longitude) {
|
||||
if (longitude >= -180 && longitude <= 180) {
|
||||
return longitude; //common case, and avoids slight double precision shifting
|
||||
}
|
||||
double off = (longitude + 180) % 360;
|
||||
if (off < 0) {
|
||||
return 180 + off;
|
||||
} else if (off == 0 && longitude > 0) {
|
||||
return 180;
|
||||
} else {
|
||||
return -180 + off;
|
||||
}
|
||||
}
|
||||
|
||||
/** Keep it simple, we don't need to take arbitrary Random for geo tests */
|
||||
private static Random random() {
|
||||
return RandomizedContext.current().getRandom();
|
||||
}
|
||||
}
|
|
@ -28,54 +28,22 @@ import org.junit.BeforeClass;
|
|||
*/
|
||||
public class TestGeoUtils extends LuceneTestCase {
|
||||
|
||||
private static final double LON_SCALE = (0x1L<<GeoEncodingUtils.BITS)/360.0D;
|
||||
private static final double LAT_SCALE = (0x1L<<GeoEncodingUtils.BITS)/180.0D;
|
||||
|
||||
// Global bounding box we will "cover" in the random test; we have to make this "smallish" else the queries take very long:
|
||||
private static double originLat;
|
||||
private static double originLon;
|
||||
// private static double range;
|
||||
private static double lonRange;
|
||||
private static double latRange;
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() throws Exception {
|
||||
// Between 1.0 and 3.0:
|
||||
lonRange = 2 * (random().nextDouble() + 0.5);
|
||||
latRange = 2 * (random().nextDouble() + 0.5);
|
||||
|
||||
originLon = GeoUtils.MIN_LON_INCL + lonRange + (GeoUtils.MAX_LON_INCL - GeoUtils.MIN_LON_INCL - 2 * lonRange) * random().nextDouble();
|
||||
originLon = BaseGeoPointTestCase.normalizeLon(originLon);
|
||||
originLat = GeoUtils.MIN_LAT_INCL + latRange + (GeoUtils.MAX_LAT_INCL - GeoUtils.MIN_LAT_INCL - 2 * latRange) * random().nextDouble();
|
||||
originLat = BaseGeoPointTestCase.normalizeLat(originLat);
|
||||
|
||||
if (VERBOSE) {
|
||||
System.out.println("TEST: originLon=" + originLon + " lonRange= " + lonRange + " originLat=" + originLat + " latRange=" + latRange);
|
||||
}
|
||||
}
|
||||
|
||||
public long scaleLon(final double val) {
|
||||
return (long) ((val-GeoUtils.MIN_LON_INCL) * LON_SCALE);
|
||||
}
|
||||
|
||||
public long scaleLat(final double val) {
|
||||
return (long) ((val-GeoUtils.MIN_LAT_INCL) * LAT_SCALE);
|
||||
}
|
||||
|
||||
public double unscaleLon(final long val) {
|
||||
return (val / LON_SCALE) + GeoUtils.MIN_LON_INCL;
|
||||
}
|
||||
|
||||
public double unscaleLat(final long val) {
|
||||
return (val / LAT_SCALE) + GeoUtils.MIN_LAT_INCL;
|
||||
originLon = GeoTestUtil.nextLongitude();
|
||||
originLat = GeoTestUtil.nextLatitude();
|
||||
}
|
||||
|
||||
public double randomLat(boolean small) {
|
||||
double result;
|
||||
if (small) {
|
||||
result = BaseGeoPointTestCase.normalizeLat(originLat + latRange * (random().nextDouble() - 0.5));
|
||||
result = GeoTestUtil.nextLatitudeNear(originLat);
|
||||
} else {
|
||||
result = -90 + 180.0 * random().nextDouble();
|
||||
result = GeoTestUtil.nextLatitude();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -83,9 +51,9 @@ public class TestGeoUtils extends LuceneTestCase {
|
|||
public double randomLon(boolean small) {
|
||||
double result;
|
||||
if (small) {
|
||||
result = BaseGeoPointTestCase.normalizeLon(originLon + lonRange * (random().nextDouble() - 0.5));
|
||||
result = GeoTestUtil.nextLongitudeNear(originLon);
|
||||
} else {
|
||||
result = -180 + 360.0 * random().nextDouble();
|
||||
result = GeoTestUtil.nextLongitude();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -149,11 +117,6 @@ public class TestGeoUtils extends LuceneTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
/** Returns random double min to max or up to 1% outside of that range */
|
||||
private double randomRangeMaybeSlightlyOutside(double min, double max) {
|
||||
return min + (random().nextDouble() + (0.5 - random().nextDouble()) * .02) * (max - min);
|
||||
}
|
||||
|
||||
// We rely heavily on GeoUtils.circleToBBox so we test it here:
|
||||
public void testRandomCircleToBBox() throws Exception {
|
||||
int iters = atLeast(1000);
|
||||
|
@ -188,15 +151,15 @@ public class TestGeoUtils extends LuceneTestCase {
|
|||
lon = randomLon(useSmallRanges);
|
||||
} else {
|
||||
// pick a lat/lon within the bbox or "slightly" outside it to try to improve test efficiency
|
||||
lat = BaseGeoPointTestCase.normalizeLat(randomRangeMaybeSlightlyOutside(bbox.minLat, bbox.maxLat));
|
||||
lat = GeoTestUtil.nextLatitudeAround(bbox.minLat, bbox.maxLat);
|
||||
if (bbox.crossesDateline()) {
|
||||
if (random().nextBoolean()) {
|
||||
lon = BaseGeoPointTestCase.normalizeLon(randomRangeMaybeSlightlyOutside(bbox.maxLon, -180));
|
||||
lon = GeoTestUtil.nextLongitudeAround(bbox.maxLon, -180);
|
||||
} else {
|
||||
lon = BaseGeoPointTestCase.normalizeLon(randomRangeMaybeSlightlyOutside(0, bbox.minLon));
|
||||
lon = GeoTestUtil.nextLongitudeAround(0, bbox.minLon);
|
||||
}
|
||||
} else {
|
||||
lon = BaseGeoPointTestCase.normalizeLon(randomRangeMaybeSlightlyOutside(bbox.minLon, bbox.maxLon));
|
||||
lon = GeoTestUtil.nextLongitudeAround(bbox.minLon, bbox.maxLon);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -235,8 +198,8 @@ public class TestGeoUtils extends LuceneTestCase {
|
|||
// similar to testRandomCircleToBBox, but different, less evil, maybe simpler
|
||||
public void testBoundingBoxOpto() {
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
double lat = -90 + 180.0 * random().nextDouble();
|
||||
double lon = -180 + 360.0 * random().nextDouble();
|
||||
double lat = GeoTestUtil.nextLatitude();
|
||||
double lon = GeoTestUtil.nextLongitude();
|
||||
double radius = 50000000 * random().nextDouble();
|
||||
GeoRect box = GeoUtils.circleToBBox(lat, lon, radius);
|
||||
final GeoRect box1;
|
||||
|
@ -250,8 +213,8 @@ public class TestGeoUtils extends LuceneTestCase {
|
|||
}
|
||||
|
||||
for (int j = 0; j < 10000; j++) {
|
||||
double lat2 = -90 + 180.0 * random().nextDouble();
|
||||
double lon2 = -180 + 360.0 * random().nextDouble();
|
||||
double lat2 = GeoTestUtil.nextLatitude();
|
||||
double lon2 = GeoTestUtil.nextLongitude();
|
||||
// if the point is within radius, then it should be in our bounding box
|
||||
if (SloppyMath.haversinMeters(lat, lon, lat2, lon2) <= radius) {
|
||||
assertTrue(lat >= box.minLat && lat <= box.maxLat);
|
||||
|
@ -264,8 +227,8 @@ public class TestGeoUtils extends LuceneTestCase {
|
|||
// test we can use haversinSortKey() for distance queries.
|
||||
public void testHaversinOpto() {
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
double lat = -90 + 180.0 * random().nextDouble();
|
||||
double lon = -180 + 360.0 * random().nextDouble();
|
||||
double lat = GeoTestUtil.nextLatitude();
|
||||
double lon = GeoTestUtil.nextLongitude();
|
||||
double radius = 50000000 * random().nextDouble();
|
||||
GeoRect box = GeoUtils.circleToBBox(lat, lon, radius);
|
||||
|
||||
|
@ -274,8 +237,8 @@ public class TestGeoUtils extends LuceneTestCase {
|
|||
SloppyMath.haversinSortKey(lat, lon, box.maxLat, lon));
|
||||
|
||||
for (int j = 0; j < 10000; j++) {
|
||||
double lat2 = -90 + 180.0 * random().nextDouble();
|
||||
double lon2 = -180 + 360.0 * random().nextDouble();
|
||||
double lat2 = GeoTestUtil.nextLatitude();
|
||||
double lon2 = GeoTestUtil.nextLongitude();
|
||||
// if the point is within radius, then it should be <= our sort key
|
||||
if (SloppyMath.haversinMeters(lat, lon, lat2, lon2) <= radius) {
|
||||
assertTrue(SloppyMath.haversinSortKey(lat, lon, lat2, lon2) <= minPartialDistance);
|
||||
|
@ -288,8 +251,8 @@ public class TestGeoUtils extends LuceneTestCase {
|
|||
/** Test infinite radius covers whole earth */
|
||||
public void testInfiniteRect() {
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
double centerLat = -90 + 180.0 * random().nextDouble();
|
||||
double centerLon = -180 + 360.0 * random().nextDouble();
|
||||
double centerLat = GeoTestUtil.nextLatitude();
|
||||
double centerLon = GeoTestUtil.nextLongitude();
|
||||
GeoRect rect = GeoUtils.circleToBBox(centerLat, centerLon, Double.POSITIVE_INFINITY);
|
||||
assertEquals(-180.0, rect.minLon, 0.0D);
|
||||
assertEquals(180.0, rect.maxLon, 0.0D);
|
||||
|
|
Loading…
Reference in New Issue