You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by rm...@apache.org on 2016/04/18 06:38:54 UTC
lucene-solr:branch_6x: LUCENE-7185: improve random test point/box
generation for spatial tests
Repository: lucene-solr
Updated Branches:
refs/heads/branch_6x a0221f469 -> 2138bc053
LUCENE-7185: improve random test point/box generation for spatial tests
Project: http://git-wip-us.apache.org/repos/asf/lucene-solr/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucene-solr/commit/2138bc05
Tree: http://git-wip-us.apache.org/repos/asf/lucene-solr/tree/2138bc05
Diff: http://git-wip-us.apache.org/repos/asf/lucene-solr/diff/2138bc05
Branch: refs/heads/branch_6x
Commit: 2138bc05365e362dd5aa8df4224fd853e981de2f
Parents: a0221f4
Author: Robert Muir <rm...@apache.org>
Authored: Mon Apr 18 00:16:56 2016 -0400
Committer: Robert Muir <rm...@apache.org>
Committed: Mon Apr 18 00:19:28 2016 -0400
----------------------------------------------------------------------
.../org/apache/lucene/geo/TestGeoUtils.java | 78 +---
.../test/org/apache/lucene/geo/TestPolygon.java | 145 +++---
.../geopoint/search/TestGeoPointQuery.java | 20 -
.../search/TestLegacyGeoPointQuery.java | 20 -
.../apache/lucene/geo/BaseGeoPointTestCase.java | 141 ++----
.../java/org/apache/lucene/geo/GeoTestUtil.java | 468 ++++++++++++++-----
6 files changed, 487 insertions(+), 385 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/2138bc05/lucene/core/src/test/org/apache/lucene/geo/TestGeoUtils.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/test/org/apache/lucene/geo/TestGeoUtils.java b/lucene/core/src/test/org/apache/lucene/geo/TestGeoUtils.java
index 48db105..e75ae85 100644
--- a/lucene/core/src/test/org/apache/lucene/geo/TestGeoUtils.java
+++ b/lucene/core/src/test/org/apache/lucene/geo/TestGeoUtils.java
@@ -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 radiusMeters;
-
- double centerLat = randomLat(useSmallRanges);
- double centerLon = randomLon(useSmallRanges);
+ double centerLat = GeoTestUtil.nextLatitude();
+ double centerLon = GeoTestUtil.nextLongitude();
- 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);
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/2138bc05/lucene/core/src/test/org/apache/lucene/geo/TestPolygon.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/test/org/apache/lucene/geo/TestPolygon.java b/lucene/core/src/test/org/apache/lucene/geo/TestPolygon.java
index 86545ed..12c3690 100644
--- a/lucene/core/src/test/org/apache/lucene/geo/TestPolygon.java
+++ b/lucene/core/src/test/org/apache/lucene/geo/TestPolygon.java
@@ -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));
- }
- }
- }
}
}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/2138bc05/lucene/spatial/src/test/org/apache/lucene/spatial/geopoint/search/TestGeoPointQuery.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/test/org/apache/lucene/spatial/geopoint/search/TestGeoPointQuery.java b/lucene/spatial/src/test/org/apache/lucene/spatial/geopoint/search/TestGeoPointQuery.java
index e5b766d..ab6b646 100644
--- a/lucene/spatial/src/test/org/apache/lucene/spatial/geopoint/search/TestGeoPointQuery.java
+++ b/lucene/spatial/src/test/org/apache/lucene/spatial/geopoint/search/TestGeoPointQuery.java
@@ -70,37 +70,17 @@ public class TestGeoPointQuery extends BaseGeoPointTestCase {
}
@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);
- }
}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/2138bc05/lucene/spatial/src/test/org/apache/lucene/spatial/geopoint/search/TestLegacyGeoPointQuery.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/test/org/apache/lucene/spatial/geopoint/search/TestLegacyGeoPointQuery.java b/lucene/spatial/src/test/org/apache/lucene/spatial/geopoint/search/TestLegacyGeoPointQuery.java
index d0b57fe..e56e28b 100644
--- a/lucene/spatial/src/test/org/apache/lucene/spatial/geopoint/search/TestLegacyGeoPointQuery.java
+++ b/lucene/spatial/src/test/org/apache/lucene/spatial/geopoint/search/TestLegacyGeoPointQuery.java
@@ -86,37 +86,17 @@ public class TestLegacyGeoPointQuery extends BaseGeoPointTestCase {
}
@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);
- }
}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/2138bc05/lucene/test-framework/src/java/org/apache/lucene/geo/BaseGeoPointTestCase.java
----------------------------------------------------------------------
diff --git a/lucene/test-framework/src/java/org/apache/lucene/geo/BaseGeoPointTestCase.java b/lucene/test-framework/src/java/org/apache/lucene/geo/BaseGeoPointTestCase.java
index dbdf189..6bc1e6e 100644
--- a/lucene/test-framework/src/java/org/apache/lucene/geo/BaseGeoPointTestCase.java
+++ b/lucene/test-framework/src/java/org/apache/lucene/geo/BaseGeoPointTestCase.java
@@ -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);
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/2138bc05/lucene/test-framework/src/java/org/apache/lucene/geo/GeoTestUtil.java
----------------------------------------------------------------------
diff --git a/lucene/test-framework/src/java/org/apache/lucene/geo/GeoTestUtil.java b/lucene/test-framework/src/java/org/apache/lucene/geo/GeoTestUtil.java
index f7d4eae..eb48a8f 100644
--- a/lucene/test-framework/src/java/org/apache/lucene/geo/GeoTestUtil.java
+++ b/lucene/test-framework/src/java/org/apache/lucene/geo/GeoTestUtil.java
@@ -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,112 +30,277 @@ 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 next pseudorandom box: can cross the 180th meridian */
- public static Rectangle nextBox() {
- return nextBoxInternal(nextLatitude(), nextLatitude(), nextLongitude(), nextLongitude(), true);
+
+ /** 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 pseudorandom box: will not cross the 180th meridian */
- public static Rectangle nextSimpleBox() {
- return nextBoxInternal(nextLatitude(), nextLatitude(), nextLongitude(), nextLongitude(), false);
+ /** 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 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 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 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);
+ /** 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);
}
/** Makes an n-gon, centered at the provided lat/lon, and each vertex approximately
@@ -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);
+ /** Keep it simple, we don't need to take arbitrary Random for geo tests */
+ private static Random random() {
+ return RandomizedContext.current().getRandom();
}
- /** 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
+ /**
+ * 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);
+ }
}
- 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
+ // 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);
+ }
}
- double off = (longitude + 180) % 360;
- if (off < 0) {
- return 180 + off;
- } else if (off == 0 && longitude > 0) {
- return 180;
- } else {
- return -180 + off;
+ 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");
}
- }
-
- /** Keep it simple, we don't need to take arbitrary Random for geo tests */
- private static Random random() {
- return RandomizedContext.current().getRandom();
+
+ // 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();
}
}