mirror of https://github.com/apache/lucene.git
LUCENE-7185: improve random test point/box generation for spatial tests
This commit is contained in:
parent
aafdc372d9
commit
c135e9a7d6
|
@ -20,7 +20,6 @@ import java.util.Locale;
|
|||
|
||||
import org.apache.lucene.util.LuceneTestCase;
|
||||
import org.apache.lucene.util.SloppyMath;
|
||||
import org.junit.BeforeClass;
|
||||
|
||||
/**
|
||||
* Tests class for methods in GeoUtils
|
||||
|
@ -29,49 +28,16 @@ import org.junit.BeforeClass;
|
|||
*/
|
||||
public class TestGeoUtils extends LuceneTestCase {
|
||||
|
||||
// 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;
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() throws Exception {
|
||||
originLon = GeoTestUtil.nextLongitude();
|
||||
originLat = GeoTestUtil.nextLatitude();
|
||||
}
|
||||
|
||||
public double randomLat(boolean small) {
|
||||
double result;
|
||||
if (small) {
|
||||
result = GeoTestUtil.nextLatitudeNear(originLat);
|
||||
} else {
|
||||
result = GeoTestUtil.nextLatitude();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public double randomLon(boolean small) {
|
||||
double result;
|
||||
if (small) {
|
||||
result = GeoTestUtil.nextLongitudeNear(originLon);
|
||||
} else {
|
||||
result = GeoTestUtil.nextLongitude();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// 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 centerLat = GeoTestUtil.nextLatitude();
|
||||
double centerLon = GeoTestUtil.nextLongitude();
|
||||
|
||||
double radiusMeters;
|
||||
|
||||
double centerLat = randomLat(useSmallRanges);
|
||||
double centerLon = randomLon(useSmallRanges);
|
||||
|
||||
if (useSmallRanges) {
|
||||
final double radiusMeters;
|
||||
if (random().nextBoolean()) {
|
||||
// Approx 4 degrees lon at the equator:
|
||||
radiusMeters = random().nextDouble() * 444000;
|
||||
} else {
|
||||
|
@ -85,25 +51,9 @@ public class TestGeoUtils extends LuceneTestCase {
|
|||
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 = GeoTestUtil.nextLatitudeAround(bbox.minLat, bbox.maxLat);
|
||||
if (bbox.crossesDateline()) {
|
||||
if (random().nextBoolean()) {
|
||||
lon = GeoTestUtil.nextLongitudeAround(bbox.maxLon, -180);
|
||||
} else {
|
||||
lon = GeoTestUtil.nextLongitudeAround(0, bbox.minLon);
|
||||
}
|
||||
} else {
|
||||
lon = GeoTestUtil.nextLongitudeAround(bbox.minLon, bbox.maxLon);
|
||||
}
|
||||
}
|
||||
double point[] = GeoTestUtil.nextPointNear(bbox);
|
||||
double lat = point[0];
|
||||
double lon = point[1];
|
||||
|
||||
double distanceMeters = SloppyMath.haversinMeters(centerLat, centerLon, lat, lon);
|
||||
|
||||
|
@ -124,7 +74,7 @@ public class TestGeoUtils extends LuceneTestCase {
|
|||
|
||||
if (haversinSays) {
|
||||
if (bboxSays == false) {
|
||||
System.out.println("small=" + useSmallRanges + " centerLat=" + centerLat + " cetnerLon=" + centerLon + " radiusMeters=" + radiusMeters);
|
||||
System.out.println("centerLat=" + centerLat + " centerLon=" + 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);
|
||||
|
@ -154,9 +104,10 @@ public class TestGeoUtils extends LuceneTestCase {
|
|||
box2 = null;
|
||||
}
|
||||
|
||||
for (int j = 0; j < 10000; j++) {
|
||||
double lat2 = GeoTestUtil.nextLatitude();
|
||||
double lon2 = GeoTestUtil.nextLongitude();
|
||||
for (int j = 0; j < 1000; j++) {
|
||||
double point[] = GeoTestUtil.nextPointNear(box);
|
||||
double lat2 = point[0];
|
||||
double lon2 = point[1];
|
||||
// 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);
|
||||
|
@ -179,8 +130,9 @@ public class TestGeoUtils extends LuceneTestCase {
|
|||
SloppyMath.haversinSortKey(lat, lon, box.maxLat, lon));
|
||||
|
||||
for (int j = 0; j < 10000; j++) {
|
||||
double lat2 = GeoTestUtil.nextLatitude();
|
||||
double lon2 = GeoTestUtil.nextLongitude();
|
||||
double point[] = GeoTestUtil.nextPointNear(box);
|
||||
double lat2 = point[0];
|
||||
double lon2 = point[1];
|
||||
// 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);
|
||||
|
|
|
@ -21,11 +21,12 @@ import org.apache.lucene.index.PointValues.Relation;
|
|||
import org.apache.lucene.util.LuceneTestCase;
|
||||
|
||||
import static org.apache.lucene.geo.GeoTestUtil.nextLatitude;
|
||||
import static org.apache.lucene.geo.GeoTestUtil.nextLatitudeAround;
|
||||
import static org.apache.lucene.geo.GeoTestUtil.nextLongitude;
|
||||
import static org.apache.lucene.geo.GeoTestUtil.nextLongitudeAround;
|
||||
import static org.apache.lucene.geo.GeoTestUtil.nextPolygon;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class TestPolygon extends LuceneTestCase {
|
||||
|
||||
/** null polyLats not allowed */
|
||||
|
@ -124,13 +125,15 @@ public class TestPolygon extends LuceneTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
// targets the bounding box directly
|
||||
public void testBoundingBoxEdgeCases() throws Exception {
|
||||
for (int i = 0; i < 100; i++) {
|
||||
Polygon polygon = nextPolygon();
|
||||
|
||||
for (int j = 0; j < 100; j++) {
|
||||
double latitude = nextLatitudeAround(polygon.minLat, polygon.maxLat);
|
||||
double longitude = nextLongitudeAround(polygon.minLon, polygon.maxLon);
|
||||
double point[] = GeoTestUtil.nextPointNear(polygon);
|
||||
double latitude = point[0];
|
||||
double longitude = point[1];
|
||||
// if the point is within poly, then it should be in our bounding box
|
||||
if (polygon.contains(latitude, longitude)) {
|
||||
assertTrue(latitude >= polygon.minLat && latitude <= polygon.maxLat);
|
||||
|
@ -146,13 +149,24 @@ public class TestPolygon extends LuceneTestCase {
|
|||
Polygon polygon = nextPolygon();
|
||||
|
||||
for (int j = 0; j < 100; j++) {
|
||||
Rectangle rectangle = GeoTestUtil.nextSimpleBox();
|
||||
Rectangle rectangle = GeoTestUtil.nextBoxNear(polygon);
|
||||
// allowed to conservatively return false
|
||||
if (polygon.relate(rectangle.minLat, rectangle.maxLat, rectangle.minLon, rectangle.maxLon) == Relation.CELL_INSIDE_QUERY) {
|
||||
for (int k = 0; k < 1000; k++) {
|
||||
for (int k = 0; k < 500; k++) {
|
||||
// this tests in our range but sometimes outside! so we have to double-check its really in other box
|
||||
double latitude = nextLatitudeAround(rectangle.minLat, rectangle.maxLat);
|
||||
double longitude = nextLongitudeAround(rectangle.minLon, rectangle.maxLon);
|
||||
double point[] = GeoTestUtil.nextPointNear(rectangle);
|
||||
double latitude = point[0];
|
||||
double longitude = point[1];
|
||||
// check for sure its in our box
|
||||
if (latitude >= rectangle.minLat && latitude <= rectangle.maxLat && longitude >= rectangle.minLon && longitude <= rectangle.maxLon) {
|
||||
assertTrue(polygon.contains(latitude, longitude));
|
||||
}
|
||||
}
|
||||
for (int k = 0; k < 100; k++) {
|
||||
// this tests in our range but sometimes outside! so we have to double-check its really in other box
|
||||
double point[] = GeoTestUtil.nextPointNear(polygon);
|
||||
double latitude = point[0];
|
||||
double longitude = point[1];
|
||||
// check for sure its in our box
|
||||
if (latitude >= rectangle.minLat && latitude <= rectangle.maxLat && longitude >= rectangle.minLon && longitude <= rectangle.maxLon) {
|
||||
assertTrue(polygon.contains(latitude, longitude));
|
||||
|
@ -169,23 +183,29 @@ public class TestPolygon extends LuceneTestCase {
|
|||
public void testContainsEdgeCases() throws Exception {
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
Polygon polygon = nextPolygon();
|
||||
|
||||
double polyLats[] = polygon.getPolyLats();
|
||||
double polyLons[] = polygon.getPolyLons();
|
||||
|
||||
for (int vertex = 0; vertex < polyLats.length; vertex++) {
|
||||
for (int j = 0; j < 10; j++) {
|
||||
Rectangle rectangle = GeoTestUtil.nextSimpleBoxNear(polyLats[vertex], polyLons[vertex]);
|
||||
// allowed to conservatively return false
|
||||
if (polygon.relate(rectangle.minLat, rectangle.maxLat, rectangle.minLon, rectangle.maxLon) == Relation.CELL_INSIDE_QUERY) {
|
||||
for (int k = 0; k < 100; k++) {
|
||||
// this tests in our range but sometimes outside! so we have to double-check its really in other box
|
||||
double latitude = nextLatitudeAround(rectangle.minLat, rectangle.maxLat);
|
||||
double longitude = nextLongitudeAround(rectangle.minLon, rectangle.maxLon);
|
||||
// check for sure its in our box
|
||||
if (latitude >= rectangle.minLat && latitude <= rectangle.maxLat && longitude >= rectangle.minLon && longitude <= rectangle.maxLon) {
|
||||
assertTrue(polygon.contains(latitude, longitude));
|
||||
}
|
||||
|
||||
for (int j = 0; j < 10; j++) {
|
||||
Rectangle rectangle = GeoTestUtil.nextBoxNear(polygon);
|
||||
// allowed to conservatively return false
|
||||
if (polygon.relate(rectangle.minLat, rectangle.maxLat, rectangle.minLon, rectangle.maxLon) == Relation.CELL_INSIDE_QUERY) {
|
||||
for (int k = 0; k < 100; k++) {
|
||||
// this tests in our range but sometimes outside! so we have to double-check its really in other box
|
||||
double point[] = GeoTestUtil.nextPointNear(rectangle);
|
||||
double latitude = point[0];
|
||||
double longitude = point[1];
|
||||
// check for sure its in our box
|
||||
if (latitude >= rectangle.minLat && latitude <= rectangle.maxLat && longitude >= rectangle.minLon && longitude <= rectangle.maxLon) {
|
||||
assertTrue(polygon.contains(latitude, longitude));
|
||||
}
|
||||
}
|
||||
for (int k = 0; k < 20; k++) {
|
||||
// this tests in our range but sometimes outside! so we have to double-check its really in other box
|
||||
double point[] = GeoTestUtil.nextPointNear(polygon);
|
||||
double latitude = point[0];
|
||||
double longitude = point[1];
|
||||
// check for sure its in our box
|
||||
if (latitude >= rectangle.minLat && latitude <= rectangle.maxLat && longitude >= rectangle.minLon && longitude <= rectangle.maxLon) {
|
||||
assertTrue(polygon.contains(latitude, longitude));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -199,13 +219,24 @@ public class TestPolygon extends LuceneTestCase {
|
|||
Polygon polygon = nextPolygon();
|
||||
|
||||
for (int j = 0; j < 100; j++) {
|
||||
Rectangle rectangle = GeoTestUtil.nextSimpleBox();
|
||||
Rectangle rectangle = GeoTestUtil.nextBoxNear(polygon);
|
||||
// allowed to conservatively return true.
|
||||
if (polygon.relate(rectangle.minLat, rectangle.maxLat, rectangle.minLon, rectangle.maxLon) == Relation.CELL_OUTSIDE_QUERY) {
|
||||
for (int k = 0; k < 1000; k++) {
|
||||
double point[] = GeoTestUtil.nextPointNear(rectangle);
|
||||
// this tests in our range but sometimes outside! so we have to double-check its really in other box
|
||||
double latitude = nextLatitudeAround(rectangle.minLat, rectangle.maxLat);
|
||||
double longitude = nextLongitudeAround(rectangle.minLon, rectangle.maxLon);
|
||||
double latitude = point[0];
|
||||
double longitude = point[1];
|
||||
// check for sure its in our box
|
||||
if (latitude >= rectangle.minLat && latitude <= rectangle.maxLat && longitude >= rectangle.minLon && longitude <= rectangle.maxLon) {
|
||||
assertFalse(polygon.contains(latitude, longitude));
|
||||
}
|
||||
}
|
||||
for (int k = 0; k < 100; k++) {
|
||||
double point[] = GeoTestUtil.nextPointNear(polygon);
|
||||
// this tests in our range but sometimes outside! so we have to double-check its really in other box
|
||||
double latitude = point[0];
|
||||
double longitude = point[1];
|
||||
// check for sure its in our box
|
||||
if (latitude >= rectangle.minLat && latitude <= rectangle.maxLat && longitude >= rectangle.minLon && longitude <= rectangle.maxLon) {
|
||||
assertFalse(polygon.contains(latitude, longitude));
|
||||
|
@ -223,22 +254,28 @@ public class TestPolygon extends LuceneTestCase {
|
|||
for (int i = 0; i < 100; i++) {
|
||||
Polygon polygon = nextPolygon();
|
||||
|
||||
double polyLats[] = polygon.getPolyLats();
|
||||
double polyLons[] = polygon.getPolyLons();
|
||||
|
||||
for (int vertex = 0; vertex < polyLats.length; vertex++) {
|
||||
for (int j = 0; j < 10; j++) {
|
||||
Rectangle rectangle = GeoTestUtil.nextSimpleBoxNear(polyLats[vertex], polyLons[vertex]);
|
||||
// allowed to conservatively return true.
|
||||
if (polygon.relate(rectangle.minLat, rectangle.maxLat, rectangle.minLon, rectangle.maxLon) == Relation.CELL_OUTSIDE_QUERY) {
|
||||
for (int k = 0; k < 100; k++) {
|
||||
// this tests in our range but sometimes outside! so we have to double-check its really in other box
|
||||
double latitude = nextLatitudeAround(rectangle.minLat, rectangle.maxLat);
|
||||
double longitude = nextLongitudeAround(rectangle.minLon, rectangle.maxLon);
|
||||
// check for sure its in our box
|
||||
if (latitude >= rectangle.minLat && latitude <= rectangle.maxLat && longitude >= rectangle.minLon && longitude <= rectangle.maxLon) {
|
||||
assertFalse(polygon.contains(latitude, longitude));
|
||||
}
|
||||
for (int j = 0; j < 10; j++) {
|
||||
Rectangle rectangle = GeoTestUtil.nextBoxNear(polygon);
|
||||
// allowed to conservatively return false.
|
||||
if (polygon.relate(rectangle.minLat, rectangle.maxLat, rectangle.minLon, rectangle.maxLon) == Relation.CELL_OUTSIDE_QUERY) {
|
||||
for (int k = 0; k < 100; k++) {
|
||||
// this tests in our range but sometimes outside! so we have to double-check its really in other box
|
||||
double point[] = GeoTestUtil.nextPointNear(rectangle);
|
||||
double latitude = point[0];
|
||||
double longitude = point[1];
|
||||
// check for sure its in our box
|
||||
if (latitude >= rectangle.minLat && latitude <= rectangle.maxLat && longitude >= rectangle.minLon && longitude <= rectangle.maxLon) {
|
||||
assertFalse(polygon.contains(latitude, longitude));
|
||||
}
|
||||
}
|
||||
for (int k = 0; k < 50; k++) {
|
||||
// this tests in our range but sometimes outside! so we have to double-check its really in other box
|
||||
double point[] = GeoTestUtil.nextPointNear(polygon);
|
||||
double latitude = point[0];
|
||||
double longitude = point[1];
|
||||
// check for sure its in our box
|
||||
if (latitude >= rectangle.minLat && latitude <= rectangle.maxLat && longitude >= rectangle.minLon && longitude <= rectangle.maxLon) {
|
||||
assertFalse(polygon.contains(latitude, longitude));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -298,29 +335,17 @@ public class TestPolygon extends LuceneTestCase {
|
|||
double polyLats[] = polygon.getPolyLats();
|
||||
double polyLons[] = polygon.getPolyLons();
|
||||
|
||||
// random lat/lons in bounding box
|
||||
// random lat/lons against polygon
|
||||
for (int j = 0; j < 1000; j++) {
|
||||
double latitude = nextLatitudeAround(polygon.minLat, polygon.maxLat);
|
||||
double longitude = nextLongitudeAround(polygon.minLon, polygon.maxLon);
|
||||
double point[] = GeoTestUtil.nextPointNear(polygon);
|
||||
double latitude = point[0];
|
||||
double longitude = point[1];
|
||||
// bounding box check required due to rounding errors (we don't solve that problem)
|
||||
if (latitude >= polygon.minLat && latitude <= polygon.maxLat && longitude >= polygon.minLon && longitude <= polygon.maxLon) {
|
||||
boolean expected = containsOriginal(polyLats, polyLons, latitude, longitude);
|
||||
assertEquals(expected, polygon.contains(latitude, longitude));
|
||||
}
|
||||
}
|
||||
|
||||
// lat lons targeted near vertices
|
||||
for (int vertex = 0; vertex < polyLats.length; vertex++) {
|
||||
for (int j = 0; j < 100; j++) {
|
||||
double latitude = GeoTestUtil.nextLatitudeNear(polyLats[vertex]);
|
||||
double longitude = GeoTestUtil.nextLongitudeNear(polyLons[vertex]);
|
||||
// bounding box check required due to rounding errors (we don't solve that problem)
|
||||
if (latitude >= polygon.minLat && latitude <= polygon.maxLat && longitude >= polygon.minLon && longitude <= polygon.maxLon) {
|
||||
boolean expected = containsOriginal(polyLats, polyLons, latitude, longitude);
|
||||
assertEquals(expected, polygon.contains(latitude, longitude));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -69,38 +69,18 @@ public class TestGeoPointQuery extends BaseGeoPointTestCase {
|
|||
return GeoPointTestUtil.nextLongitude();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected double nextLongitudeNear(double other) {
|
||||
return GeoPointTestUtil.nextLongitudeNear(other);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected double nextLatitude() {
|
||||
return GeoPointTestUtil.nextLatitude();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected double nextLatitudeNear(double other) {
|
||||
return GeoPointTestUtil.nextLatitudeNear(other);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Rectangle nextBox() {
|
||||
return GeoPointTestUtil.nextBox();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Rectangle nextBoxNear(double latitude, double longitude) {
|
||||
return GeoPointTestUtil.nextBoxNear(latitude, longitude);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Polygon nextPolygon() {
|
||||
return GeoPointTestUtil.nextPolygon();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Polygon nextPolygonNear(double latitude, double longitude) {
|
||||
return GeoPointTestUtil.nextPolygonNear(latitude, longitude);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -85,38 +85,18 @@ public class TestLegacyGeoPointQuery extends BaseGeoPointTestCase {
|
|||
return GeoPointTestUtil.nextLongitude();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected double nextLongitudeNear(double other) {
|
||||
return GeoPointTestUtil.nextLongitudeNear(other);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected double nextLatitude() {
|
||||
return GeoPointTestUtil.nextLatitude();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected double nextLatitudeNear(double other) {
|
||||
return GeoPointTestUtil.nextLatitudeNear(other);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Rectangle nextBox() {
|
||||
return GeoPointTestUtil.nextBox();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Rectangle nextBoxNear(double latitude, double longitude) {
|
||||
return GeoPointTestUtil.nextBoxNear(latitude, longitude);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Polygon nextPolygon() {
|
||||
return GeoPointTestUtil.nextPolygon();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Polygon nextPolygonNear(double latitude, double longitude) {
|
||||
return GeoPointTestUtil.nextPolygonNear(latitude, longitude);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -83,16 +83,6 @@ import org.apache.lucene.util.bkd.BKDWriter;
|
|||
public abstract class BaseGeoPointTestCase extends LuceneTestCase {
|
||||
|
||||
protected static final String FIELD_NAME = "point";
|
||||
|
||||
private double originLat;
|
||||
private double originLon;
|
||||
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
originLon = nextLongitude();
|
||||
originLat = nextLatitude();
|
||||
}
|
||||
|
||||
// TODO: remove these hooks once all subclasses can pass with new random!
|
||||
|
||||
|
@ -100,34 +90,18 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
|
|||
return org.apache.lucene.geo.GeoTestUtil.nextLongitude();
|
||||
}
|
||||
|
||||
protected double nextLongitudeNear(double other) {
|
||||
return org.apache.lucene.geo.GeoTestUtil.nextLongitudeNear(other);
|
||||
}
|
||||
|
||||
protected double nextLatitude() {
|
||||
return org.apache.lucene.geo.GeoTestUtil.nextLatitude();
|
||||
}
|
||||
|
||||
protected double nextLatitudeNear(double other) {
|
||||
return org.apache.lucene.geo.GeoTestUtil.nextLatitudeNear(other);
|
||||
}
|
||||
|
||||
protected Rectangle nextBox() {
|
||||
return org.apache.lucene.geo.GeoTestUtil.nextBox();
|
||||
}
|
||||
|
||||
protected Rectangle nextBoxNear(double latitude, double longitude) {
|
||||
return org.apache.lucene.geo.GeoTestUtil.nextBoxNear(latitude, longitude);
|
||||
}
|
||||
|
||||
protected Polygon nextPolygon() {
|
||||
return org.apache.lucene.geo.GeoTestUtil.nextPolygon();
|
||||
}
|
||||
|
||||
protected Polygon nextPolygonNear(double latitude, double longitude) {
|
||||
return org.apache.lucene.geo.GeoTestUtil.nextPolygonNear(latitude, longitude);
|
||||
}
|
||||
|
||||
/** Valid values that should not cause exception */
|
||||
public void testIndexExtremeValues() {
|
||||
Document document = new Document();
|
||||
|
@ -418,11 +392,10 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
|
|||
// A particularly tricky adversary for BKD tree:
|
||||
public void testSamePointManyTimes() throws Exception {
|
||||
int numPoints = atLeast(1000);
|
||||
boolean small = random().nextBoolean();
|
||||
|
||||
// Every doc has 2 points:
|
||||
double theLat = randomLat(small);
|
||||
double theLon = randomLon(small);
|
||||
double theLat = nextLatitude();
|
||||
double theLon = nextLongitude();
|
||||
|
||||
double[] lats = new double[numPoints];
|
||||
Arrays.fill(lats, theLat);
|
||||
|
@ -430,13 +403,12 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
|
|||
double[] lons = new double[numPoints];
|
||||
Arrays.fill(lons, theLon);
|
||||
|
||||
verify(small, lats, lons);
|
||||
verify(lats, lons);
|
||||
}
|
||||
|
||||
public void testAllLatEqual() throws Exception {
|
||||
int numPoints = atLeast(10000);
|
||||
boolean small = random().nextBoolean();
|
||||
double lat = randomLat(small);
|
||||
double lat = nextLatitude();
|
||||
double[] lats = new double[numPoints];
|
||||
double[] lons = new double[numPoints];
|
||||
|
||||
|
@ -468,7 +440,7 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
|
|||
System.out.println(" doc=" + docID + " lat=" + lat + " lon=" + lons[docID] + " (same lat/lon as doc=" + oldDocID + ")");
|
||||
}
|
||||
} else {
|
||||
lons[docID] = randomLon(small);
|
||||
lons[docID] = nextLongitude();
|
||||
haveRealDoc = true;
|
||||
if (VERBOSE) {
|
||||
System.out.println(" doc=" + docID + " lat=" + lat + " lon=" + lons[docID]);
|
||||
|
@ -477,13 +449,12 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
|
|||
lats[docID] = lat;
|
||||
}
|
||||
|
||||
verify(small, lats, lons);
|
||||
verify(lats, lons);
|
||||
}
|
||||
|
||||
public void testAllLonEqual() throws Exception {
|
||||
int numPoints = atLeast(10000);
|
||||
boolean small = random().nextBoolean();
|
||||
double theLon = randomLon(small);
|
||||
double theLon = nextLongitude();
|
||||
double[] lats = new double[numPoints];
|
||||
double[] lons = new double[numPoints];
|
||||
|
||||
|
@ -517,7 +488,7 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
|
|||
System.out.println(" doc=" + docID + " lat=" + lats[docID] + " lon=" + theLon + " (same lat/lon as doc=" + oldDocID + ")");
|
||||
}
|
||||
} else {
|
||||
lats[docID] = randomLat(small);
|
||||
lats[docID] = nextLatitude();
|
||||
haveRealDoc = true;
|
||||
if (VERBOSE) {
|
||||
System.out.println(" doc=" + docID + " lat=" + lats[docID] + " lon=" + theLon);
|
||||
|
@ -526,7 +497,7 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
|
|||
lons[docID] = theLon;
|
||||
}
|
||||
|
||||
verify(small, lats, lons);
|
||||
verify(lats, lons);
|
||||
}
|
||||
|
||||
public void testMultiValued() throws Exception {
|
||||
|
@ -543,16 +514,14 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
|
|||
iwc.setMergeScheduler(new SerialMergeScheduler());
|
||||
RandomIndexWriter w = new RandomIndexWriter(random(), dir, iwc);
|
||||
|
||||
boolean small = random().nextBoolean();
|
||||
|
||||
for (int id=0;id<numPoints;id++) {
|
||||
Document doc = new Document();
|
||||
lats[2*id] = quantizeLat(randomLat(small));
|
||||
lons[2*id] = quantizeLon(randomLon(small));
|
||||
lats[2*id] = quantizeLat(nextLatitude());
|
||||
lons[2*id] = quantizeLon(nextLongitude());
|
||||
doc.add(newStringField("id", ""+id, Field.Store.YES));
|
||||
addPointToDoc(FIELD_NAME, doc, lats[2*id], lons[2*id]);
|
||||
lats[2*id+1] = quantizeLat(randomLat(small));
|
||||
lons[2*id+1] = quantizeLon(randomLon(small));
|
||||
lats[2*id+1] = quantizeLat(nextLatitude());
|
||||
lons[2*id+1] = quantizeLon(nextLongitude());
|
||||
addPointToDoc(FIELD_NAME, doc, lats[2*id+1], lons[2*id+1]);
|
||||
|
||||
if (VERBOSE) {
|
||||
|
@ -574,7 +543,7 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
|
|||
|
||||
int iters = atLeast(25);
|
||||
for (int iter=0;iter<iters;iter++) {
|
||||
Rectangle rect = randomRect(small);
|
||||
Rectangle rect = nextBox();
|
||||
|
||||
if (VERBOSE) {
|
||||
System.out.println("\nTEST: iter=" + iter + " rect=" + rect);
|
||||
|
@ -665,8 +634,6 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
|
|||
double[] lats = new double[numPoints];
|
||||
double[] lons = new double[numPoints];
|
||||
|
||||
boolean small = random().nextBoolean();
|
||||
|
||||
boolean haveRealDoc = false;
|
||||
|
||||
for (int id=0;id<numPoints;id++) {
|
||||
|
@ -692,13 +659,13 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
|
|||
if (x == 0) {
|
||||
// Identical lat to old point
|
||||
lats[id] = lats[oldID];
|
||||
lons[id] = randomLon(small);
|
||||
lons[id] = nextLongitude();
|
||||
if (VERBOSE) {
|
||||
System.out.println(" id=" + id + " lat=" + lats[id] + " lon=" + lons[id] + " (same lat as doc=" + oldID + ")");
|
||||
}
|
||||
} else if (x == 1) {
|
||||
// Identical lon to old point
|
||||
lats[id] = randomLat(small);
|
||||
lats[id] = nextLatitude();
|
||||
lons[id] = lons[oldID];
|
||||
if (VERBOSE) {
|
||||
System.out.println(" id=" + id + " lat=" + lats[id] + " lon=" + lons[id] + " (same lon as doc=" + oldID + ")");
|
||||
|
@ -713,8 +680,8 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
lats[id] = randomLat(small);
|
||||
lons[id] = randomLon(small);
|
||||
lats[id] = nextLatitude();
|
||||
lons[id] = nextLongitude();
|
||||
haveRealDoc = true;
|
||||
if (VERBOSE) {
|
||||
System.out.println(" id=" + id + " lat=" + lats[id] + " lon=" + lons[id]);
|
||||
|
@ -722,23 +689,7 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
verify(small, lats, lons);
|
||||
}
|
||||
|
||||
public final double randomLat(boolean small) {
|
||||
if (small) {
|
||||
return nextLatitudeNear(originLat);
|
||||
} else {
|
||||
return nextLatitude();
|
||||
}
|
||||
}
|
||||
|
||||
public final double randomLon(boolean small) {
|
||||
if (small) {
|
||||
return nextLongitudeNear(originLon);
|
||||
} else {
|
||||
return nextLongitude();
|
||||
}
|
||||
verify(lats, lons);
|
||||
}
|
||||
|
||||
/** Override this to quantize randomly generated lat, so the test won't fail due to quantization errors, which are 1) annoying to debug,
|
||||
|
@ -753,14 +704,6 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
|
|||
return lon;
|
||||
}
|
||||
|
||||
protected final Rectangle randomRect(boolean small) {
|
||||
if (small) {
|
||||
return nextBoxNear(originLat, originLon);
|
||||
} else {
|
||||
return nextBox();
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void addPointToDoc(String field, Document doc, double lat, double lon);
|
||||
|
||||
protected abstract Query newRectQuery(String field, double minLat, double maxLat, double minLon, double maxLon);
|
||||
|
@ -784,7 +727,7 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
private void verify(boolean small, double[] lats, double[] lons) throws Exception {
|
||||
private void verify(double[] lats, double[] lons) throws Exception {
|
||||
// quantize each value the same way the index does
|
||||
// NaN means missing for the doc!!!!!
|
||||
for (int i = 0; i < lats.length; i++) {
|
||||
|
@ -797,12 +740,12 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
|
|||
lons[i] = quantizeLon(lons[i]);
|
||||
}
|
||||
}
|
||||
verifyRandomRectangles(small, lats, lons);
|
||||
verifyRandomDistances(small, lats, lons);
|
||||
verifyRandomPolygons(small, lats, lons);
|
||||
verifyRandomRectangles(lats, lons);
|
||||
verifyRandomDistances(lats, lons);
|
||||
verifyRandomPolygons(lats, lons);
|
||||
}
|
||||
|
||||
protected void verifyRandomRectangles(boolean small, double[] lats, double[] lons) throws Exception {
|
||||
protected void verifyRandomRectangles(double[] lats, double[] lons) throws Exception {
|
||||
IndexWriterConfig iwc = newIndexWriterConfig();
|
||||
// Else seeds may not reproduce:
|
||||
iwc.setMergeScheduler(new SerialMergeScheduler());
|
||||
|
@ -860,7 +803,7 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
|
|||
System.out.println("\nTEST: iter=" + iter + " s=" + s);
|
||||
}
|
||||
|
||||
Rectangle rect = randomRect(small);
|
||||
Rectangle rect = nextBox();
|
||||
|
||||
Query query = newRectQuery(FIELD_NAME, rect.minLat, rect.maxLat, rect.minLon, rect.maxLon);
|
||||
|
||||
|
@ -930,7 +873,7 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
|
|||
IOUtils.close(r, dir);
|
||||
}
|
||||
|
||||
protected void verifyRandomDistances(boolean small, double[] lats, double[] lons) throws Exception {
|
||||
protected void verifyRandomDistances(double[] lats, double[] lons) throws Exception {
|
||||
IndexWriterConfig iwc = newIndexWriterConfig();
|
||||
// Else seeds may not reproduce:
|
||||
iwc.setMergeScheduler(new SerialMergeScheduler());
|
||||
|
@ -989,17 +932,11 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
|
|||
}
|
||||
|
||||
// Distance
|
||||
final double centerLat = randomLat(small);
|
||||
final double centerLon = randomLon(small);
|
||||
final double centerLat = nextLatitude();
|
||||
final double centerLon = nextLongitude();
|
||||
|
||||
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.EARTH_MEAN_RADIUS_METERS * Math.PI / 2.0 + 1.0;
|
||||
}
|
||||
// So the query can cover at most 50% of the earth's surface:
|
||||
final double radiusMeters = random().nextDouble() * GeoUtils.EARTH_MEAN_RADIUS_METERS * Math.PI / 2.0 + 1.0;
|
||||
|
||||
if (VERBOSE) {
|
||||
final DecimalFormat df = new DecimalFormat("#,###.00", DecimalFormatSymbols.getInstance(Locale.ENGLISH));
|
||||
|
@ -1077,7 +1014,7 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
|
|||
IOUtils.close(r, dir);
|
||||
}
|
||||
|
||||
protected void verifyRandomPolygons(boolean small, double[] lats, double[] lons) throws Exception {
|
||||
protected void verifyRandomPolygons(double[] lats, double[] lons) throws Exception {
|
||||
IndexWriterConfig iwc = newIndexWriterConfig();
|
||||
// Else seeds may not reproduce:
|
||||
iwc.setMergeScheduler(new SerialMergeScheduler());
|
||||
|
@ -1137,13 +1074,7 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
|
|||
}
|
||||
|
||||
// Polygon
|
||||
final Polygon polygon;
|
||||
if (small) {
|
||||
polygon = nextPolygonNear(originLat, originLon);
|
||||
} else {
|
||||
polygon = nextPolygon();
|
||||
}
|
||||
|
||||
Polygon polygon = nextPolygon();
|
||||
Query query = newPolygonQuery(FIELD_NAME, polygon);
|
||||
|
||||
if (VERBOSE) {
|
||||
|
@ -1216,7 +1147,7 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
|
|||
Rectangle rect;
|
||||
// TODO: why this dateline leniency???
|
||||
while (true) {
|
||||
rect = randomRect(random().nextBoolean());
|
||||
rect = nextBox();
|
||||
if (rect.crossesDateline() == false) {
|
||||
break;
|
||||
}
|
||||
|
@ -1386,7 +1317,7 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
|
|||
public void testEquals() throws Exception {
|
||||
Query q1, q2;
|
||||
|
||||
Rectangle rect = randomRect(false);
|
||||
Rectangle rect = nextBox();
|
||||
|
||||
q1 = newRectQuery("field", rect.minLat, rect.maxLat, rect.minLon, rect.maxLon);
|
||||
q2 = newRectQuery("field", rect.minLat, rect.maxLat, rect.minLon, rect.maxLon);
|
||||
|
@ -1397,8 +1328,8 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
|
|||
assertFalse(q1.equals(newRectQuery("field2", rect.minLat, rect.maxLat, rect.minLon, rect.maxLon)));
|
||||
}
|
||||
|
||||
double lat = randomLat(false);
|
||||
double lon = randomLon(false);
|
||||
double lat = nextLatitude();
|
||||
double lon = nextLongitude();
|
||||
q1 = newDistanceQuery("field", lat, lon, 10000.0);
|
||||
q2 = newDistanceQuery("field", lat, lon, 10000.0);
|
||||
assertEquals(q1, q2);
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
package org.apache.lucene.geo;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import org.apache.lucene.util.NumericUtils;
|
||||
|
@ -28,113 +30,278 @@ import com.carrotsearch.randomizedtesting.RandomizedContext;
|
|||
/** static methods for testing geo */
|
||||
public class GeoTestUtil {
|
||||
|
||||
private static final long LATITUDE_MIN_SORTABLE = NumericUtils.doubleToSortableLong(-90);
|
||||
private static final long LATITUDE_MAX_SORTABLE = NumericUtils.doubleToSortableLong(90);
|
||||
|
||||
/** returns next pseudorandom latitude (anywhere) */
|
||||
public static double nextLatitude() {
|
||||
int surpriseMe = random().nextInt(17);
|
||||
if (surpriseMe == 0) {
|
||||
// random bitpattern in range
|
||||
return NumericUtils.sortableLongToDouble(TestUtil.nextLong(random(), LATITUDE_MIN_SORTABLE, LATITUDE_MAX_SORTABLE));
|
||||
} else if (surpriseMe == 1) {
|
||||
// edge case
|
||||
return -90.0;
|
||||
} else if (surpriseMe == 2) {
|
||||
// edge case
|
||||
return 90.0;
|
||||
} else if (surpriseMe == 3) {
|
||||
// may trigger divide by zero
|
||||
return 0.0;
|
||||
} else {
|
||||
// distributed ~ evenly
|
||||
return -90 + 180.0 * random().nextDouble();
|
||||
}
|
||||
return nextDoubleInternal(-90, 90);
|
||||
}
|
||||
|
||||
private static final long LONGITUDE_MIN_SORTABLE = NumericUtils.doubleToSortableLong(-180);
|
||||
private static final long LONGITUDE_MAX_SORTABLE = NumericUtils.doubleToSortableLong(180);
|
||||
|
||||
/** returns next pseudorandom longitude (anywhere) */
|
||||
public static double nextLongitude() {
|
||||
return nextDoubleInternal(-180, 180);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns next double within range.
|
||||
* <p>
|
||||
* Don't pass huge numbers or infinity or anything like that yet. may have bugs!
|
||||
*/
|
||||
// the goal is to adjust random number generation to test edges, create more duplicates, create "one-offs" in floating point space, etc.
|
||||
// we do this by first picking a good "base value" (explicitly targeting edges, zero if allowed, or "discrete values"). but it also
|
||||
// ensures we pick any double in the range and generally still produces randomish looking numbers.
|
||||
// then we sometimes perturb that by one ulp.
|
||||
private static double nextDoubleInternal(double low, double high) {
|
||||
assert low >= Integer.MIN_VALUE;
|
||||
assert high <= Integer.MAX_VALUE;
|
||||
assert Double.isFinite(low);
|
||||
assert Double.isFinite(high);
|
||||
assert high >= low : "low=" + low + " high=" + high;
|
||||
|
||||
// if they are equal, not much we can do
|
||||
if (low == high) {
|
||||
return low;
|
||||
}
|
||||
|
||||
// first pick a base value.
|
||||
final double baseValue;
|
||||
int surpriseMe = random().nextInt(17);
|
||||
if (surpriseMe == 0) {
|
||||
// random bitpattern in range
|
||||
return NumericUtils.sortableLongToDouble(TestUtil.nextLong(random(), LONGITUDE_MIN_SORTABLE, LONGITUDE_MAX_SORTABLE));
|
||||
// random bits
|
||||
long lowBits = NumericUtils.doubleToSortableLong(low);
|
||||
long highBits = NumericUtils.doubleToSortableLong(high);
|
||||
baseValue = NumericUtils.sortableLongToDouble(TestUtil.nextLong(random(), lowBits, highBits));
|
||||
} else if (surpriseMe == 1) {
|
||||
// edge case
|
||||
return -180.0;
|
||||
baseValue = low;
|
||||
} else if (surpriseMe == 2) {
|
||||
// edge case
|
||||
return 180.0;
|
||||
} else if (surpriseMe == 3) {
|
||||
baseValue = high;
|
||||
} else if (surpriseMe == 3 && low <= 0 && high >= 0) {
|
||||
// may trigger divide by 0
|
||||
return 0.0;
|
||||
baseValue = 0.0;
|
||||
} else if (surpriseMe == 4) {
|
||||
// divide up space into block of 360
|
||||
double delta = (high - low) / 360;
|
||||
int block = random().nextInt(360);
|
||||
baseValue = low + delta * block;
|
||||
} else {
|
||||
// distributed ~ evenly
|
||||
return -180 + 360.0 * random().nextDouble();
|
||||
baseValue = low + (high - low) * random().nextDouble();
|
||||
}
|
||||
|
||||
assert baseValue >= low;
|
||||
assert baseValue <= high;
|
||||
|
||||
// either return the base value or adjust it by 1 ulp in a random direction (if possible)
|
||||
int adjustMe = random().nextInt(17);
|
||||
if (adjustMe == 0) {
|
||||
return Math.nextAfter(adjustMe, high);
|
||||
} else if (adjustMe == 1) {
|
||||
return Math.nextAfter(adjustMe, low);
|
||||
} else {
|
||||
return baseValue;
|
||||
}
|
||||
}
|
||||
|
||||
/** returns next pseudorandom latitude, kinda close to {@code otherLatitude} */
|
||||
public static double nextLatitudeNear(double otherLatitude) {
|
||||
private static double nextLatitudeNear(double otherLatitude, double delta) {
|
||||
delta = Math.abs(delta);
|
||||
GeoUtils.checkLatitude(otherLatitude);
|
||||
return normalizeLatitude(otherLatitude + random().nextDouble() - 0.5);
|
||||
int surpriseMe = random().nextInt(97);
|
||||
if (surpriseMe == 0) {
|
||||
// purely random
|
||||
return nextLatitude();
|
||||
} else if (surpriseMe < 49) {
|
||||
// upper half of region (the exact point or 1 ulp difference is still likely)
|
||||
return nextDoubleInternal(otherLatitude, Math.min(90, otherLatitude + delta));
|
||||
} else {
|
||||
// lower half of region (the exact point or 1 ulp difference is still likely)
|
||||
return nextDoubleInternal(Math.max(-90, otherLatitude - delta), otherLatitude);
|
||||
}
|
||||
}
|
||||
|
||||
/** returns next pseudorandom longitude, kinda close to {@code otherLongitude} */
|
||||
public static double nextLongitudeNear(double otherLongitude) {
|
||||
private static double nextLongitudeNear(double otherLongitude, double delta) {
|
||||
delta = Math.abs(delta);
|
||||
GeoUtils.checkLongitude(otherLongitude);
|
||||
return normalizeLongitude(otherLongitude + random().nextDouble() - 0.5);
|
||||
int surpriseMe = random().nextInt(97);
|
||||
if (surpriseMe == 0) {
|
||||
// purely random
|
||||
return nextLongitude();
|
||||
} else if (surpriseMe < 49) {
|
||||
// upper half of region (the exact point or 1 ulp difference is still likely)
|
||||
return nextDoubleInternal(otherLongitude, Math.min(180, otherLongitude + delta));
|
||||
} else {
|
||||
// lower half of region (the exact point or 1 ulp difference is still likely)
|
||||
return nextDoubleInternal(Math.max(-180, otherLongitude - delta), otherLongitude);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* outside of that range! this is to facilitate edge testing of lines
|
||||
*/
|
||||
public static double nextLatitudeAround(double minLatitude, double maxLatitude) {
|
||||
private static double nextLatitudeBetween(double minLatitude, double maxLatitude) {
|
||||
assert maxLatitude >= minLatitude;
|
||||
GeoUtils.checkLatitude(minLatitude);
|
||||
GeoUtils.checkLatitude(maxLatitude);
|
||||
return normalizeLatitude(randomRangeMaybeSlightlyOutside(minLatitude, maxLatitude));
|
||||
if (random().nextInt(47) == 0) {
|
||||
// purely random
|
||||
return nextLatitude();
|
||||
} else {
|
||||
// extend the range by 1%
|
||||
double difference = (maxLatitude - minLatitude) / 100;
|
||||
double lower = Math.max(-90, minLatitude - difference);
|
||||
double upper = Math.min(90, maxLatitude + difference);
|
||||
return nextDoubleInternal(lower, upper);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* outside of that range! this is to facilitate edge testing of lines
|
||||
*/
|
||||
public static double nextLongitudeAround(double minLongitude, double maxLongitude) {
|
||||
private static double nextLongitudeBetween(double minLongitude, double maxLongitude) {
|
||||
assert maxLongitude >= minLongitude;
|
||||
GeoUtils.checkLongitude(minLongitude);
|
||||
GeoUtils.checkLongitude(maxLongitude);
|
||||
return normalizeLongitude(randomRangeMaybeSlightlyOutside(minLongitude, maxLongitude));
|
||||
if (random().nextInt(47) == 0) {
|
||||
// purely random
|
||||
return nextLongitude();
|
||||
} else {
|
||||
// extend the range by 1%
|
||||
double difference = (maxLongitude - minLongitude) / 100;
|
||||
double lower = Math.max(-180, minLongitude - difference);
|
||||
double upper = Math.min(180, maxLongitude + difference);
|
||||
return nextDoubleInternal(lower, upper);
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the next point around a line (more or less) */
|
||||
private static double[] nextPointAroundLine(double lat1, double lon1, double lat2, double lon2) {
|
||||
double x1 = lon1;
|
||||
double x2 = lon2;
|
||||
double y1 = lat1;
|
||||
double y2 = lat2;
|
||||
double minX = Math.min(x1, x2);
|
||||
double maxX = Math.max(x1, x2);
|
||||
double minY = Math.min(y1, y2);
|
||||
double maxY = Math.max(y1, y2);
|
||||
if (minX == maxX) {
|
||||
return new double[] { nextLatitudeBetween(minY, maxY), nextLongitudeNear(minX, 0.01 * (maxY - minY)) };
|
||||
} else if (minY == maxY) {
|
||||
return new double[] { nextLatitudeNear(minY, 0.01 * (maxX - minX)), nextLongitudeBetween(minX, maxX) };
|
||||
} else {
|
||||
double x = nextLongitudeBetween(minX, maxX);
|
||||
double y = (y1 - y2) / (x1 - x2) * (x-x1) + y1;
|
||||
double delta = (maxY - minY) * 0.01;
|
||||
// our formula may put the targeted Y out of bounds
|
||||
y = Math.min(90, y);
|
||||
y = Math.max(-90, y);
|
||||
return new double[] { nextLatitudeNear(y, delta), x };
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns next point (lat/lon) for testing near a Box. It may cross the dateline */
|
||||
public static double[] nextPointNear(Rectangle rectangle) {
|
||||
if (rectangle.crossesDateline()) {
|
||||
// pick a "side" of the two boxes we really are
|
||||
if (random().nextBoolean()) {
|
||||
return nextPointNear(new Rectangle(rectangle.minLat, rectangle.maxLat, -180, rectangle.maxLon));
|
||||
} else {
|
||||
return nextPointNear(new Rectangle(rectangle.minLat, rectangle.maxLat, rectangle.minLon, 180));
|
||||
}
|
||||
} else {
|
||||
return nextPointNear(boxPolygon(rectangle));
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns next point (lat/lon) for testing near a Polygon */
|
||||
// see http://www-ma2.upc.es/geoc/Schirra-pointPolygon.pdf for more info on some of these strategies
|
||||
public static double[] nextPointNear(Polygon polygon) {
|
||||
double polyLats[] = polygon.getPolyLats();
|
||||
double polyLons[] = polygon.getPolyLons();
|
||||
Polygon holes[] = polygon.getHoles();
|
||||
|
||||
// if there are any holes, target them aggressively
|
||||
if (holes.length > 0 && random().nextInt(3) == 0) {
|
||||
return nextPointNear(holes[random().nextInt(holes.length)]);
|
||||
}
|
||||
|
||||
int surpriseMe = random().nextInt(97);
|
||||
if (surpriseMe == 0) {
|
||||
// purely random
|
||||
return new double[] { nextLatitude(), nextLongitude() };
|
||||
} else if (surpriseMe < 5) {
|
||||
// purely random within bounding box
|
||||
return new double[] { nextLatitudeBetween(polygon.minLat, polygon.maxLat), nextLongitudeBetween(polygon.minLon, polygon.maxLon) };
|
||||
} else if (surpriseMe < 20) {
|
||||
// target a vertex
|
||||
int vertex = random().nextInt(polyLats.length - 1);
|
||||
return new double[] { nextLatitudeNear(polyLats[vertex], polyLats[vertex+1] - polyLats[vertex]),
|
||||
nextLongitudeNear(polyLons[vertex], polyLons[vertex+1] - polyLons[vertex]) };
|
||||
} else if (surpriseMe < 30) {
|
||||
// target points around the bounding box edges
|
||||
Polygon container = boxPolygon(new Rectangle(polygon.minLat, polygon.maxLat, polygon.minLon, polygon.maxLon));
|
||||
double containerLats[] = container.getPolyLats();
|
||||
double containerLons[] = container.getPolyLons();
|
||||
int startVertex = random().nextInt(containerLats.length - 1);
|
||||
return nextPointAroundLine(containerLats[startVertex], containerLons[startVertex],
|
||||
containerLats[startVertex+1], containerLons[startVertex+1]);
|
||||
} else {
|
||||
// target points around diagonals between vertices
|
||||
int startVertex = random().nextInt(polyLats.length - 1);
|
||||
// but favor edges heavily
|
||||
int endVertex = random().nextBoolean() ? startVertex + 1 : random().nextInt(polyLats.length - 1);
|
||||
return nextPointAroundLine(polyLats[startVertex], polyLons[startVertex],
|
||||
polyLats[endVertex], polyLons[endVertex]);
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns next box for testing near a Polygon */
|
||||
public static Rectangle nextBoxNear(Polygon polygon) {
|
||||
final double point1[];
|
||||
final double point2[];
|
||||
|
||||
// if there are any holes, target them aggressively
|
||||
Polygon holes[] = polygon.getHoles();
|
||||
if (holes.length > 0 && random().nextInt(3) == 0) {
|
||||
return nextBoxNear(holes[random().nextInt(holes.length)]);
|
||||
}
|
||||
|
||||
int surpriseMe = random().nextInt(97);
|
||||
if (surpriseMe == 0) {
|
||||
// formed from two interesting points
|
||||
point1 = nextPointNear(polygon);
|
||||
point2 = nextPointNear(polygon);
|
||||
} else {
|
||||
// formed from one interesting point: then random within delta.
|
||||
point1 = nextPointNear(polygon);
|
||||
point2 = new double[2];
|
||||
// now figure out a good delta: we use a rough heuristic, up to the length of an edge
|
||||
double polyLats[] = polygon.getPolyLats();
|
||||
double polyLons[] = polygon.getPolyLons();
|
||||
int vertex = random().nextInt(polyLats.length - 1);
|
||||
double deltaX = polyLons[vertex+1] - polyLons[vertex];
|
||||
double deltaY = polyLats[vertex+1] - polyLats[vertex];
|
||||
double edgeLength = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
||||
point2[0] = nextLatitudeNear(point1[0], edgeLength);
|
||||
point2[1] = nextLongitudeNear(point1[1], edgeLength);
|
||||
}
|
||||
|
||||
// form a box from the two points
|
||||
double minLat = Math.min(point1[0], point2[0]);
|
||||
double maxLat = Math.max(point1[0], point2[0]);
|
||||
double minLon = Math.min(point1[1], point2[1]);
|
||||
double maxLon = Math.max(point1[1], point2[1]);
|
||||
return new Rectangle(minLat, maxLat, minLon, maxLon);
|
||||
}
|
||||
|
||||
/** returns next pseudorandom box: can cross the 180th meridian */
|
||||
public static Rectangle nextBox() {
|
||||
return nextBoxInternal(nextLatitude(), nextLatitude(), nextLongitude(), nextLongitude(), true);
|
||||
}
|
||||
|
||||
/** returns next pseudorandom box: will not cross the 180th meridian */
|
||||
public static Rectangle nextSimpleBox() {
|
||||
return nextBoxInternal(nextLatitude(), nextLatitude(), nextLongitude(), nextLongitude(), false);
|
||||
}
|
||||
|
||||
/** returns next pseudorandom box, can cross the 180th meridian, kinda close to {@code otherLatitude} and {@code otherLongitude} */
|
||||
public static Rectangle 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 box, will not cross the 180th meridian, kinda close to {@code otherLatitude} and {@code otherLongitude} */
|
||||
public static Rectangle nextSimpleBoxNear(double otherLatitude, double otherLongitude) {
|
||||
GeoUtils.checkLongitude(otherLongitude);
|
||||
GeoUtils.checkLongitude(otherLongitude);
|
||||
return nextBoxInternal(nextLatitudeNear(otherLatitude), nextLatitudeNear(otherLatitude),
|
||||
nextLongitudeNear(otherLongitude), nextLongitudeNear(otherLongitude), false);
|
||||
}
|
||||
|
||||
/** Makes an n-gon, centered at the provided lat/lon, and each vertex approximately
|
||||
* distanceMeters away from the center.
|
||||
|
@ -211,7 +378,7 @@ public class GeoTestUtil {
|
|||
/** returns next pseudorandom polygon */
|
||||
public static Polygon nextPolygon() {
|
||||
if (random().nextBoolean()) {
|
||||
return surpriseMePolygon(null, null);
|
||||
return surpriseMePolygon();
|
||||
} else if (random().nextInt(10) == 1) {
|
||||
// this poly is slow to create ... only do it 10% of the time:
|
||||
while (true) {
|
||||
|
@ -236,23 +403,6 @@ public class GeoTestUtil {
|
|||
}
|
||||
}
|
||||
|
||||
/** returns next pseudorandom polygon, kinda close to {@code otherLatitude} and {@code otherLongitude} */
|
||||
public static Polygon nextPolygonNear(double otherLatitude, double otherLongitude) {
|
||||
if (random().nextBoolean()) {
|
||||
return surpriseMePolygon(otherLatitude, otherLongitude);
|
||||
}
|
||||
|
||||
Rectangle 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 Rectangle nextBoxInternal(double lat0, double lat1, double lon0, double lon1, boolean canCrossDateLine) {
|
||||
if (lat1 < lat0) {
|
||||
double x = lat0;
|
||||
|
@ -301,23 +451,13 @@ public class GeoTestUtil {
|
|||
return new Polygon(polyLats, polyLons);
|
||||
}
|
||||
|
||||
private static Polygon surpriseMePolygon(Double otherLatitude, Double otherLongitude) {
|
||||
private static Polygon surpriseMePolygon() {
|
||||
// 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 centerLat = nextLatitude();
|
||||
double centerLon = nextLongitude();
|
||||
double radius = 0.1 + 20 * random().nextDouble();
|
||||
double radiusDelta = random().nextDouble();
|
||||
|
||||
|
@ -371,37 +511,131 @@ public class GeoTestUtil {
|
|||
}
|
||||
}
|
||||
|
||||
/** 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns svg of polygon for debugging.
|
||||
* <p>
|
||||
* You can pass any number of objects:
|
||||
* Polygon: polygon with optional holes
|
||||
* Polygon[]: arrays of polygons for convenience
|
||||
* Rectangle: for a box
|
||||
* double[2]: as latitude,longitude for a point
|
||||
* <p>
|
||||
* At least one object must be a polygon. The viewBox is formed around all polygons
|
||||
* found in the arguments.
|
||||
*/
|
||||
public static String toSVG(Object ...objects) {
|
||||
List<Object> flattened = new ArrayList<>();
|
||||
for (Object o : objects) {
|
||||
if (o instanceof Polygon[]) {
|
||||
flattened.addAll(Arrays.asList((Polygon[]) o));
|
||||
} else {
|
||||
flattened.add(o);
|
||||
}
|
||||
}
|
||||
// first compute bounding area of all the objects
|
||||
double minLat = Double.POSITIVE_INFINITY;
|
||||
double maxLat = Double.NEGATIVE_INFINITY;
|
||||
double minLon = Double.POSITIVE_INFINITY;
|
||||
double maxLon = Double.NEGATIVE_INFINITY;
|
||||
for (Object o : flattened) {
|
||||
final Rectangle r;
|
||||
if (o instanceof Polygon) {
|
||||
r = Rectangle.fromPolygon(new Polygon[] { (Polygon) o });
|
||||
minLat = Math.min(minLat, r.minLat);
|
||||
maxLat = Math.max(maxLat, r.maxLat);
|
||||
minLon = Math.min(minLon, r.minLon);
|
||||
maxLon = Math.max(maxLon, r.maxLon);
|
||||
}
|
||||
}
|
||||
if (Double.isFinite(minLat) == false || Double.isFinite(maxLat) == false ||
|
||||
Double.isFinite(minLon) == false || Double.isFinite(maxLon) == false) {
|
||||
throw new IllegalArgumentException("you must pass at least one polygon");
|
||||
}
|
||||
|
||||
// add some additional padding so we can really see what happens on the edges too
|
||||
double xpadding = (maxLon - minLon) / 64;
|
||||
double ypadding = (maxLat - minLat) / 64;
|
||||
// expand points to be this large
|
||||
double pointX = xpadding * 0.1;
|
||||
double pointY = ypadding * 0.1;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("<svg xmlns=\"http://www.w3.org/2000/svg\" height=\"640\" width=\"480\" viewBox=\"");
|
||||
sb.append(minLon - xpadding)
|
||||
.append(" ")
|
||||
.append(90 - maxLat - ypadding)
|
||||
.append(" ")
|
||||
.append(maxLon - minLon + (2*xpadding))
|
||||
.append(" ")
|
||||
.append(maxLat - minLat + (2*ypadding));
|
||||
sb.append("\">\n");
|
||||
|
||||
// encode each object
|
||||
for (Object o : flattened) {
|
||||
// tostring
|
||||
if (o instanceof double[]) {
|
||||
double point[] = (double[]) o;
|
||||
sb.append("<!-- point: ");
|
||||
sb.append(point[0] + "," + point[1]);
|
||||
sb.append(" -->\n");
|
||||
} else {
|
||||
sb.append("<!-- " + o.getClass().getSimpleName() + ": \n");
|
||||
sb.append(o.toString());
|
||||
sb.append("\n-->\n");
|
||||
}
|
||||
final Polygon gon;
|
||||
final String style;
|
||||
final String opacity;
|
||||
if (o instanceof Rectangle) {
|
||||
gon = boxPolygon((Rectangle) o);
|
||||
style = "fill:lightskyblue;stroke:black;stroke-width:0.2%;stroke-dasharray:0.5%,1%;";
|
||||
opacity = "0.3";
|
||||
} else if (o instanceof double[]) {
|
||||
double point[] = (double[]) o;
|
||||
gon = boxPolygon(new Rectangle(Math.max(-90, point[0]-pointY),
|
||||
Math.min(90, point[0]+pointY),
|
||||
Math.max(-180, point[1]-pointX),
|
||||
Math.min(180, point[1]+pointX)));
|
||||
style = "fill:red;stroke:red;stroke-width:0.1%;";
|
||||
opacity = "0.7";
|
||||
} else {
|
||||
gon = (Polygon) o;
|
||||
style = "fill:lawngreen;stroke:black;stroke-width:0.3%;";
|
||||
opacity = "0.5";
|
||||
}
|
||||
// polygon
|
||||
double polyLats[] = gon.getPolyLats();
|
||||
double polyLons[] = gon.getPolyLons();
|
||||
sb.append("<polygon fill-opacity=\"" + opacity + "\" points=\"");
|
||||
for (int i = 0; i < polyLats.length; i++) {
|
||||
if (i > 0) {
|
||||
sb.append(" ");
|
||||
}
|
||||
sb.append(polyLons[i])
|
||||
.append(",")
|
||||
.append(90 - polyLats[i]);
|
||||
}
|
||||
sb.append("\" style=\"" + style + "\"/>\n");
|
||||
for (Polygon hole : gon.getHoles()) {
|
||||
double holeLats[] = hole.getPolyLats();
|
||||
double holeLons[] = hole.getPolyLons();
|
||||
sb.append("<polygon points=\"");
|
||||
for (int i = 0; i < holeLats.length; i++) {
|
||||
if (i > 0) {
|
||||
sb.append(" ");
|
||||
}
|
||||
sb.append(holeLons[i])
|
||||
.append(",")
|
||||
.append(90 - holeLats[i]);
|
||||
}
|
||||
sb.append("\" style=\"fill:lightgray\"/>\n");
|
||||
}
|
||||
}
|
||||
sb.append("</svg>\n");
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue