add randomized test for GeoUtils.circleToBBox

This commit is contained in:
Mike McCandless 2016-03-04 16:53:58 -05:00
parent d700b149a5
commit 488591c419
3 changed files with 91 additions and 2 deletions

View File

@ -70,7 +70,7 @@ final class LatLonPointDistanceQuery extends Query {
final GeoRect box2;
// crosses dateline: split
if (box.maxLon < box.minLon) {
if (box.crossesDateline()) {
box1 = new GeoRect(-180.0, box.maxLon, box.minLat, box.maxLat);
box2 = new GeoRect(box.minLon, 180.0, box.minLat, box.maxLat);
} else {

View File

@ -16,11 +16,12 @@
*/
package org.apache.lucene.search;
import org.apache.lucene.document.LatLonPoint;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.LatLonPoint;
import org.apache.lucene.spatial.util.BaseGeoPointTestCase;
import org.apache.lucene.spatial.util.GeoDistanceUtils;
import org.apache.lucene.spatial.util.GeoRect;
import org.apache.lucene.spatial.util.GeoUtils;
public class TestLatLonPointQueries extends BaseGeoPointTestCase {
// TODO: remove this!
@ -146,4 +147,87 @@ public class TestLatLonPointQueries extends BaseGeoPointTestCase {
final double d = GeoDistanceUtils.haversin(centerLat, centerLon, pointLat, pointLon);
return d >= minRadiusMeters && d <= radiusMeters;
}
/** 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);
for(int iter=0;iter<iters;iter++) {
boolean useSmallRanges = random().nextBoolean();
double radiusMeters;
double centerLat = randomLat(useSmallRanges);
double centerLon = randomLon(useSmallRanges);
if (useSmallRanges) {
// Approx 4 degrees lon at the equator:
radiusMeters = random().nextDouble() * 444000;
} else {
radiusMeters = random().nextDouble() * 50000000;
}
// TODO: randomly quantize radius too, to provoke exact math errors?
GeoRect bbox = GeoUtils.circleToBBox(centerLon, centerLat, radiusMeters);
int numPointsToTry = 1000;
for(int i=0;i<numPointsToTry;i++) {
double lat;
double lon;
if (random().nextBoolean()) {
lat = randomLat(useSmallRanges);
lon = randomLon(useSmallRanges);
} else {
// pick a lat/lon within the bbox or "slightly" outside it to try to improve test efficiency
lat = quantizeLat(GeoUtils.normalizeLat(randomRangeMaybeSlightlyOutside(bbox.minLat, bbox.maxLat)));
if (bbox.crossesDateline()) {
if (random().nextBoolean()) {
lon = quantizeLon(GeoUtils.normalizeLon(randomRangeMaybeSlightlyOutside(bbox.maxLon, -180)));
} else {
lon = quantizeLon(GeoUtils.normalizeLon(randomRangeMaybeSlightlyOutside(0, bbox.minLon)));
}
} else {
lon = quantizeLon(GeoUtils.normalizeLon(randomRangeMaybeSlightlyOutside(bbox.minLon, bbox.maxLon)));
}
}
double distanceMeters = GeoDistanceUtils.haversin(centerLat, centerLon, lat, lon);
// Haversin says it's within the circle:
boolean haversinSays = distanceMeters <= radiusMeters;
// BBox says its within the box:
boolean bboxSays;
if (bbox.crossesDateline()) {
if (lat >= bbox.minLat && lat <= bbox.maxLat) {
bboxSays = lon <= bbox.maxLon || lon >= bbox.minLon;
} else {
bboxSays = false;
}
} else {
bboxSays = lat >= bbox.minLat && lat <= bbox.maxLat && lon >= bbox.minLon && lon <= bbox.maxLon;
}
if (haversinSays) {
if (bboxSays == false) {
System.out.println("small=" + useSmallRanges + " centerLat=" + centerLat + " cetnerLon=" + centerLon + " radiusMeters=" + radiusMeters);
System.out.println(" bbox: lat=" + bbox.minLat + " to " + bbox.maxLat + " lon=" + bbox.minLon + " to " + bbox.maxLon);
System.out.println(" point: lat=" + lat + " lon=" + lon);
System.out.println(" haversin: " + distanceMeters);
fail("point was within the distance according to haversin, but the bbox doesn't contain it");
}
} else {
// it's fine if haversin said it was outside the radius and bbox said it was inside the box
}
}
}
}
}

View File

@ -70,4 +70,9 @@ public class GeoRect {
return b.toString();
}
/** Returns true if this bounding box crosses the dateline */
public boolean crossesDateline() {
return maxLon < minLon;
}
}