LUCENE-8086: spatial-extras Geo3dFactory: Use GeoExactCircle with configurable precision for non-spherical planet models.

Some internal refactorings as well.
This commit is contained in:
David Smiley 2017-12-13 16:32:48 -05:00
parent 44cc0defe8
commit d66d9549d7
9 changed files with 261 additions and 350 deletions

View File

@ -72,6 +72,10 @@ Improvements
disk. This change adds an expert setting to opt ouf of this behavior unless
flusing is falling behind. (Simon Willnauer)
* LUCENE-8086: spatial-extras Geo3dFactory: Use GeoExactCircle with
configurable precision for non-spherical planet models.
(Ignacio Vera via David Smiley)
======================= Lucene 7.2.0 =======================
API Changes

View File

@ -20,13 +20,10 @@ package org.apache.lucene.spatial.spatial4j;
import org.apache.lucene.spatial3d.geom.GeoCircle;
import org.apache.lucene.spatial3d.geom.GeoCircleFactory;
import org.apache.lucene.spatial3d.geom.GeoPointShapeFactory;
import org.apache.lucene.spatial3d.geom.PlanetModel;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.distance.DistanceUtils;
import org.locationtech.spatial4j.shape.Circle;
import org.locationtech.spatial4j.shape.Point;
import org.locationtech.spatial4j.shape.Shape;
import org.locationtech.spatial4j.shape.SpatialRelation;
/**
* Specialization of a {@link Geo3dShape} which represents a {@link Circle}.
@ -67,16 +64,4 @@ public class Geo3dCircleShape extends Geo3dShape<GeoCircle> implements Circle {
}
return center;
}
//TODO Improve GeoCircle to properly relate a point with WGS84 model -- LUCENE-7970
@Override
public SpatialRelation relate(Shape other) {
if (shape.getPlanetModel() != PlanetModel.SPHERE && other instanceof Point) {
if (spatialcontext.getDistCalc().distance((Point) other, getCenter()) <= getRadius()) {
return SpatialRelation.CONTAINS;
}
return SpatialRelation.DISJOINT;
}
return super.relate(other);
}
}

View File

@ -73,62 +73,20 @@ public class Geo3dDistanceCalculator implements DistanceCalculator {
@Override
public Point pointOnBearing(Point from, double distDEG, double bearingDEG, SpatialContext ctx, Point reuse) {
// Algorithm using Vincenty's formulae (https://en.wikipedia.org/wiki/Vincenty%27s_formulae)
// which takes into account that planets may not be spherical.
//Code adaptation from http://www.movable-type.co.uk/scripts/latlong-vincenty.html
Geo3dPointShape geoFrom = (Geo3dPointShape) from;
GeoPoint point = (GeoPoint) geoFrom.shape;
double lat = point.getLatitude();
double lon = point.getLongitude();
double dist = DistanceUtils.DEGREES_TO_RADIANS * distDEG;
double bearing = DistanceUtils.DEGREES_TO_RADIANS * bearingDEG;
double sinα1 = Math.sin(bearing);
double cosα1 = Math.cos(bearing);
double tanU1 = (1 - planetModel.flattening) * Math.tan(lat);
double cosU1 = 1 / Math.sqrt((1 + tanU1 * tanU1));
double sinU1 = tanU1 * cosU1;
double σ1 = Math.atan2(tanU1, cosα1);
double sinα = cosU1 * sinα1;
double cosSqα = 1 - sinα * sinα;
double uSq = cosSqα * planetModel.squareRatio;// (planetModel.ab* planetModel.ab - planetModel.c*planetModel.c) / (planetModel.c*planetModel.c);
double A = 1 + uSq / 16384 * (4096 + uSq * (-768 + uSq * (320 - 175 * uSq)));
double B = uSq / 1024 * (256 + uSq * (-128 + uSq * (74 - 47 * uSq)));
double cos2σM;
double sinσ;
double cosσ;
double Δσ;
double σ = dist / (planetModel.c * A);
double σʹ;
double iterations = 0;
do {
cos2σM = Math.cos(2 * σ1 + σ);
sinσ = Math.sin(σ);
cosσ = Math.cos(σ);
Δσ = B * sinσ * (cos2σM + B / 4 * (cosσ * (-1 + 2 * cos2σM * cos2σM) -
B / 6 * cos2σM * (-3 + 4 * sinσ * sinσ) * (-3 + 4 * cos2σM * cos2σM)));
σʹ = σ;
σ = dist / (planetModel.c * A) + Δσ;
} while (Math.abs(σ - σʹ) > 1e-12 && ++iterations < 200);
if (iterations >= 200) {
throw new RuntimeException("Formula failed to converge");
GeoPoint newPoint = planetModel.surfacePointOnBearing(point, dist, bearing);
double newLat = newPoint.getLatitude() * DistanceUtils.RADIANS_TO_DEGREES;
double newLon = newPoint.getLongitude() * DistanceUtils.RADIANS_TO_DEGREES;
if (reuse != null) {
reuse.reset(newLon, newLat);
return reuse;
}
else {
return ctx.getShapeFactory().pointXY(newLon, newLat);
}
double x = sinU1 * sinσ - cosU1 * cosσ * cosα1;
double φ2 = Math.atan2(sinU1 * cosσ + cosU1 * sinσ * cosα1, (1 - planetModel.flattening) * Math.sqrt(sinα * sinα + x * x));
double λ = Math.atan2(sinσ * sinα1, cosU1 * cosσ - sinU1 * sinσ * cosα1);
double C = planetModel.flattening / 16 * cosSqα * (4 + planetModel.flattening * (4 - 3 * cosSqα));
double L = λ - (1 - C) * planetModel.flattening * sinα *
(σ + C * sinσ * (cos2σM + C * cosσ * (-1 + 2 * cos2σM * cos2σM)));
double λ2 = (lon + L + 3 * Math.PI) % (2 * Math.PI) - Math.PI; // normalise to -180..+180
return ctx.getShapeFactory().pointXY(λ2 * DistanceUtils.RADIANS_TO_DEGREES,
φ2 * DistanceUtils.RADIANS_TO_DEGREES);
}
@Override

View File

@ -112,11 +112,7 @@ public class Geo3dShape<T extends GeoAreaShape> implements Shape {
if (bbox == null) {
LatLonBounds bounds = new LatLonBounds();
shape.getBounds(bounds);
double leftLon = bounds.checkNoLongitudeBound() ? -Math.PI : bounds.getLeftLongitude();
double rightLon = bounds.checkNoLongitudeBound() ? Math.PI : bounds.getRightLongitude();
double minLat = bounds.checkNoBottomLatitudeBound() ? -Math.PI * 0.5 : bounds.getMinLatitude();
double maxLat = bounds.checkNoTopLatitudeBound() ? Math.PI * 0.5 : bounds.getMaxLatitude();
GeoBBox geoBBox = GeoBBoxFactory.makeGeoBBox(shape.getPlanetModel(), maxLat, minLat, leftLon, rightLon);
GeoBBox geoBBox = GeoBBoxFactory.makeGeoBBox(shape.getPlanetModel(), bounds);
bbox = new Geo3dRectangleShape(geoBBox, spatialcontext);
this.boundingBox = bbox;
}

View File

@ -55,6 +55,13 @@ public class Geo3dShapeFactory implements ShapeFactory {
private SpatialContext context;
private PlanetModel planetModel;
/**
* Default accuracy for circles when not using the unit sphere.
* It is equivalent to ~10m on the surface of the earth.
*/
private static final double DEFAULT_CIRCLE_ACCURACY = 1e-4;
private double circleAccuracy = DEFAULT_CIRCLE_ACCURACY;
@SuppressWarnings("unchecked")
public Geo3dShapeFactory(SpatialContext context, SpatialContextFactory factory) {
this.context = context;
@ -67,6 +74,16 @@ public class Geo3dShapeFactory implements ShapeFactory {
return context;
}
/**
* Set the accuracy for circles in decimal degrees. Note that accuracy has no effect
* when the planet model is a sphere. In that case, circles are always fully precise.
*
* @param circleAccuracy the provided accuracy in decimal degrees.
*/
public void setCircleAccuracy(double circleAccuracy) {
this.circleAccuracy = circleAccuracy;
}
@Override
public boolean isNormWrapLongitude() {
return normWrapLongitude;
@ -150,10 +167,23 @@ public class Geo3dShapeFactory implements ShapeFactory {
@Override
public Circle circle(double x, double y, double distance) {
GeoCircle circle = GeoCircleFactory.makeGeoCircle(planetModel,
y * DistanceUtils.DEGREES_TO_RADIANS,
x * DistanceUtils.DEGREES_TO_RADIANS,
distance * DistanceUtils.DEGREES_TO_RADIANS);
GeoCircle circle;
if (planetModel.isSphere()) {
circle = GeoCircleFactory.makeGeoCircle(planetModel,
y * DistanceUtils.DEGREES_TO_RADIANS,
x * DistanceUtils.DEGREES_TO_RADIANS,
distance * DistanceUtils.DEGREES_TO_RADIANS);
}
else {
//accuracy is defined as a linear distance in this class. At tiny distances, linear distance
//can be approximated to surface distance in radians.
circle = GeoCircleFactory.makeExactGeoCircle(planetModel,
y * DistanceUtils.DEGREES_TO_RADIANS,
x * DistanceUtils.DEGREES_TO_RADIANS,
distance * DistanceUtils.DEGREES_TO_RADIANS,
circleAccuracy * DistanceUtils.DEGREES_TO_RADIANS);
}
return new Geo3dCircleShape(circle, context);
}
@ -238,8 +268,7 @@ public class Geo3dShapeFactory implements ShapeFactory {
/**
* Geo3d implementation of {@link org.locationtech.spatial4j.shape.ShapeFactory.LineStringBuilder} to generate
* nine Strings. Note that GeoPath needs a buffer so we set the
* buffer to 1e-10.
* line strings.
*/
private class Geo3dLineStringBuilder extends Geo3dPointBuilder<LineStringBuilder> implements LineStringBuilder {
@ -373,7 +402,7 @@ public class Geo3dShapeFactory implements ShapeFactory {
/**
* Geo3d implementation of {@link org.locationtech.spatial4j.shape.ShapeFactory.MultiShapeBuilder} to generate
* geometry collections
* geometry collections.
*
* @param <T> is the type of shapes.
*/

View File

@ -1,264 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.lucene.spatial.spatial4j;
import java.util.ArrayList;
import java.util.List;
import org.apache.lucene.spatial3d.geom.GeoPath;
import org.apache.lucene.spatial3d.geom.GeoPolygon;
import org.locationtech.spatial4j.TestLog;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.distance.DistanceUtils;
import org.locationtech.spatial4j.shape.Circle;
import org.locationtech.spatial4j.shape.Point;
import org.locationtech.spatial4j.shape.RectIntersectionTestHelper;
import org.apache.lucene.spatial3d.geom.LatLonBounds;
import org.apache.lucene.spatial3d.geom.GeoBBox;
import org.apache.lucene.spatial3d.geom.GeoBBoxFactory;
import org.apache.lucene.spatial3d.geom.GeoCircle;
import org.apache.lucene.spatial3d.geom.GeoCircleFactory;
import org.apache.lucene.spatial3d.geom.GeoPathFactory;
import org.apache.lucene.spatial3d.geom.GeoPoint;
import org.apache.lucene.spatial3d.geom.GeoPolygonFactory;
import org.apache.lucene.spatial3d.geom.GeoShape;
import org.apache.lucene.spatial3d.geom.PlanetModel;
import org.junit.Rule;
import org.junit.Test;
import static org.locationtech.spatial4j.distance.DistanceUtils.DEGREES_TO_RADIANS;
public abstract class Geo3dShapeRectRelationTestCase extends RandomizedShapeTestCase {
protected final static double RADIANS_PER_DEGREE = Math.PI/180.0;
@Rule
public final TestLog testLog = TestLog.instance;
protected final PlanetModel planetModel;
public Geo3dShapeRectRelationTestCase(PlanetModel planetModel) {
super(SpatialContext.GEO);
this.planetModel = planetModel;
}
protected GeoBBox getBoundingBox(final GeoShape path) {
LatLonBounds bounds = new LatLonBounds();
path.getBounds(bounds);
double leftLon;
double rightLon;
if (bounds.checkNoLongitudeBound()) {
leftLon = -Math.PI;
rightLon = Math.PI;
} else {
leftLon = bounds.getLeftLongitude().doubleValue();
rightLon = bounds.getRightLongitude().doubleValue();
}
double minLat;
if (bounds.checkNoBottomLatitudeBound()) {
minLat = -Math.PI * 0.5;
} else {
minLat = bounds.getMinLatitude().doubleValue();
}
double maxLat;
if (bounds.checkNoTopLatitudeBound()) {
maxLat = Math.PI * 0.5;
} else {
maxLat = bounds.getMaxLatitude().doubleValue();
}
return GeoBBoxFactory.makeGeoBBox(planetModel, maxLat, minLat, leftLon, rightLon);
}
public abstract class Geo3dRectIntersectionTestHelper extends RectIntersectionTestHelper<Geo3dShape> {
public Geo3dRectIntersectionTestHelper(SpatialContext ctx) {
super(ctx);
}
//20 times each -- should be plenty
protected int getContainsMinimum(int laps) {
return 20;
}
protected int getIntersectsMinimum(int laps) {
return 20;
}
// producing "within" cases in Geo3D based on our random shapes doesn't happen often. It'd be nice to increase this.
protected int getWithinMinimum(int laps) {
return 2;
}
protected int getDisjointMinimum(int laps) {
return 20;
}
protected int getBoundingMinimum(int laps) {
return 20;
}
}
@Test
public void testGeoCircleRect() {
new Geo3dRectIntersectionTestHelper(ctx) {
@Override
protected Geo3dShape generateRandomShape(Point nearP) {
final int circleRadius = 180 - random().nextInt(180);//no 0-radius
final Point point = nearP;
final GeoCircle shape = GeoCircleFactory.makeGeoCircle(planetModel, point.getY() * DEGREES_TO_RADIANS, point.getX() * DEGREES_TO_RADIANS,
circleRadius * DEGREES_TO_RADIANS);
return new Geo3dShape(shape, ctx);
}
@Override
protected Point randomPointInEmptyShape(Geo3dShape shape) {
GeoPoint geoPoint = ((GeoCircle)shape.shape).getCenter();
return geoPointToSpatial4jPoint(geoPoint);
}
}.testRelateWithRectangle();
}
@Test
public void testGeoBBoxRect() {
new Geo3dRectIntersectionTestHelper(ctx) {
@Override
protected boolean isRandomShapeRectangular() {
return true;
}
@Override
protected Geo3dShape generateRandomShape(Point nearP) {
// (ignoring nearP)
Point ulhcPoint = randomPoint();
Point lrhcPoint = randomPoint();
if (ulhcPoint.getY() < lrhcPoint.getY()) {
//swap
Point temp = ulhcPoint;
ulhcPoint = lrhcPoint;
lrhcPoint = temp;
}
final GeoBBox shape = GeoBBoxFactory.makeGeoBBox(planetModel, ulhcPoint.getY() * DEGREES_TO_RADIANS,
lrhcPoint.getY() * DEGREES_TO_RADIANS,
ulhcPoint.getX() * DEGREES_TO_RADIANS,
lrhcPoint.getX() * DEGREES_TO_RADIANS);
return new Geo3dShape(shape, ctx);
}
@Override
protected Point randomPointInEmptyShape(Geo3dShape shape) {
return shape.getBoundingBox().getCenter();
}
}.testRelateWithRectangle();
}
@Test
public void testGeoPolygonRect() {
new Geo3dRectIntersectionTestHelper(ctx) {
@Override
protected Geo3dShape generateRandomShape(Point nearP) {
final Point centerPoint = randomPoint();
final int maxDistance = random().nextInt(160) + 20;
final Circle pointZone = ctx.makeCircle(centerPoint, maxDistance);
final int vertexCount = random().nextInt(3) + 3;
while (true) {
final List<GeoPoint> geoPoints = new ArrayList<>();
while (geoPoints.size() < vertexCount) {
final Point point = randomPointIn(pointZone);
final GeoPoint gPt = new GeoPoint(planetModel, point.getY() * DEGREES_TO_RADIANS, point.getX() * DEGREES_TO_RADIANS);
geoPoints.add(gPt);
}
try {
final GeoPolygon shape = GeoPolygonFactory.makeGeoPolygon(planetModel, geoPoints);
if (shape == null) {
continue;
}
return new Geo3dShape(shape, ctx);
} catch (IllegalArgumentException e) {
// This is what happens when we create a shape that is invalid. Although it is conceivable that there are cases where
// the exception is thrown incorrectly, we aren't going to be able to do that in this random test.
continue;
}
}
}
@Override
protected Point randomPointInEmptyShape(Geo3dShape shape) {
throw new IllegalStateException("unexpected; need to finish test code");
}
@Override
protected int getWithinMinimum(int laps) {
// Long/thin so lets just find 1.
return 1;
}
}.testRelateWithRectangle();
}
@Test
public void testGeoPathRect() {
new Geo3dRectIntersectionTestHelper(ctx) {
@Override
protected Geo3dShape generateRandomShape(Point nearP) {
final Point centerPoint = randomPoint();
final int maxDistance = random().nextInt(160) + 20;
final Circle pointZone = ctx.makeCircle(centerPoint, maxDistance);
final int pointCount = random().nextInt(5) + 1;
final double width = (random().nextInt(89)+1) * DEGREES_TO_RADIANS;
final GeoPoint[] points = new GeoPoint[pointCount];
while (true) {
for (int i = 0; i < pointCount; i++) {
final Point nextPoint = randomPointIn(pointZone);
points[i] = new GeoPoint(planetModel, nextPoint.getY() * DEGREES_TO_RADIANS, nextPoint.getX() * DEGREES_TO_RADIANS);
}
try {
final GeoPath path = GeoPathFactory.makeGeoPath(planetModel, width, points);
return new Geo3dShape(path, ctx);
} catch (IllegalArgumentException e) {
// This is what happens when we create a shape that is invalid. Although it is conceivable that there are cases where
// the exception is thrown incorrectly, we aren't going to be able to do that in this random test.
continue;
}
}
}
@Override
protected Point randomPointInEmptyShape(Geo3dShape shape) {
throw new IllegalStateException("unexpected; need to finish test code");
}
@Override
protected int getWithinMinimum(int laps) {
// Long/thin so lets just find 1.
return 1;
}
}.testRelateWithRectangle();
}
private Point geoPointToSpatial4jPoint(GeoPoint geoPoint) {
return ctx.makePoint(geoPoint.getLongitude() * DistanceUtils.RADIANS_TO_DEGREES,
geoPoint.getLatitude() * DistanceUtils.RADIANS_TO_DEGREES);
}
}

View File

@ -34,13 +34,13 @@ import org.locationtech.spatial4j.shape.Point;
import org.locationtech.spatial4j.shape.Rectangle;
import org.locationtech.spatial4j.shape.SpatialRelation;
public class Geo3dShapeSphereModelRectRelationTest extends Geo3dShapeRectRelationTestCase {
public class Geo3dShapeSphereModelRectRelationTest extends ShapeRectRelationTestCase {
PlanetModel planetModel = PlanetModel.SPHERE;
public Geo3dShapeSphereModelRectRelationTest() {
super(PlanetModel.SPHERE);
Geo3dSpatialContextFactory factory = new Geo3dSpatialContextFactory();
factory.planetModel = PlanetModel.SPHERE;
//factory.distCalc = new GeodesicSphereDistCalc.Haversine();
factory.planetModel = planetModel;
this.ctx = factory.newSpatialContext();
}

View File

@ -30,14 +30,16 @@ import org.locationtech.spatial4j.shape.Circle;
import org.locationtech.spatial4j.shape.Point;
import org.locationtech.spatial4j.shape.SpatialRelation;
public class Geo3dShapeWGS84ModelRectRelationTest extends Geo3dShapeRectRelationTestCase {
public class Geo3dShapeWGS84ModelRectRelationTest extends ShapeRectRelationTestCase {
PlanetModel planetModel = PlanetModel.WGS84;
public Geo3dShapeWGS84ModelRectRelationTest() {
super(PlanetModel.WGS84);
Geo3dSpatialContextFactory factory = new Geo3dSpatialContextFactory();
factory.planetModel = PlanetModel.WGS84;
//factory.distCalc = new GeodesicSphereDistCalc.Haversine();
factory.planetModel = planetModel;
this.ctx = factory.newSpatialContext();
this.maxRadius = 178;
((Geo3dShapeFactory)ctx.getShapeFactory()).setCircleAccuracy(1e-6);
}
@Test

View File

@ -0,0 +1,201 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.lucene.spatial.spatial4j;
import org.junit.Rule;
import org.junit.Test;
import org.locationtech.spatial4j.TestLog;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.shape.Circle;
import org.locationtech.spatial4j.shape.Point;
import org.locationtech.spatial4j.shape.RectIntersectionTestHelper;
import org.locationtech.spatial4j.shape.Shape;
import org.locationtech.spatial4j.shape.ShapeFactory;
import static org.locationtech.spatial4j.distance.DistanceUtils.DEGREES_TO_RADIANS;
public abstract class ShapeRectRelationTestCase extends RandomizedShapeTestCase {
protected final static double RADIANS_PER_DEGREE = Math.PI/180.0;
@Rule
public final TestLog testLog = TestLog.instance;
protected int maxRadius = 180;
public ShapeRectRelationTestCase() {
super(SpatialContext.GEO);
}
public abstract class AbstractRectIntersectionTestHelper extends RectIntersectionTestHelper<Shape> {
public AbstractRectIntersectionTestHelper(SpatialContext ctx) {
super(ctx);
}
//20 times each -- should be plenty
protected int getContainsMinimum(int laps) {
return 20;
}
protected int getIntersectsMinimum(int laps) {
return 20;
}
// producing "within" cases in Geo3D based on our random shapes doesn't happen often. It'd be nice to increase this.
protected int getWithinMinimum(int laps) {
return 2;
}
protected int getDisjointMinimum(int laps) {
return 20;
}
protected int getBoundingMinimum(int laps) {
return 20;
}
}
@Test
public void testGeoCircleRect() {
new AbstractRectIntersectionTestHelper(ctx) {
@Override
protected Shape generateRandomShape(Point nearP) {
final int circleRadius = maxRadius - random().nextInt(maxRadius);//no 0-radius
return ctx.getShapeFactory().circle(nearP, circleRadius);
}
@Override
protected Point randomPointInEmptyShape(Shape shape) {
return shape.getCenter();
}
}.testRelateWithRectangle();
}
@Test
public void testGeoBBoxRect() {
new AbstractRectIntersectionTestHelper(ctx) {
@Override
protected boolean isRandomShapeRectangular() {
return true;
}
@Override
protected Shape generateRandomShape(Point nearP) {
Point upperRight = randomPoint();
Point lowerLeft = randomPoint();
if (upperRight.getY() < lowerLeft.getY()) {
//swap
Point temp = upperRight;
upperRight = lowerLeft;
lowerLeft = temp;
}
return ctx.getShapeFactory().rect(lowerLeft, upperRight);
}
@Override
protected Point randomPointInEmptyShape(Shape shape) {
return shape.getCenter();
}
}.testRelateWithRectangle();
}
@Test
public void testGeoPolygonRect() {
new AbstractRectIntersectionTestHelper(ctx) {
@Override
protected Shape generateRandomShape(Point nearP) {
final Point centerPoint = randomPoint();
final int maxDistance = random().nextInt(maxRadius -20) + 20;
final Circle pointZone = ctx.getShapeFactory().circle(centerPoint, maxDistance);
final int vertexCount = random().nextInt(3) + 3;
while (true) {
ShapeFactory.PolygonBuilder builder = ctx.getShapeFactory().polygon();
for (int i = 0; i < vertexCount; i++) {
final Point point = randomPointIn(pointZone);
builder.pointXY(point.getX(), point.getY());
}
try {
return builder.build();
} catch (IllegalArgumentException e) {
// This is what happens when we create a shape that is invalid. Although it is conceivable that there are cases where
// the exception is thrown incorrectly, we aren't going to be able to do that in this random test.
continue;
}
}
}
@Override
protected Point randomPointInEmptyShape(Shape shape) {
throw new IllegalStateException("unexpected; need to finish test code");
}
@Override
protected int getWithinMinimum(int laps) {
// Long/thin so lets just find 1.
return 1;
}
}.testRelateWithRectangle();
}
@Test
public void testGeoPathRect() {
new AbstractRectIntersectionTestHelper(ctx) {
@Override
protected Shape generateRandomShape(Point nearP) {
final Point centerPoint = randomPoint();
final int maxDistance = random().nextInt(maxRadius -20) + 20;
final Circle pointZone = ctx.getShapeFactory().circle(centerPoint, maxDistance);
final int pointCount = random().nextInt(5) + 1;
final double width = (random().nextInt(89)+1) * DEGREES_TO_RADIANS;
final ShapeFactory.LineStringBuilder builder = ctx.getShapeFactory().lineString();
while (true) {
for (int i = 0; i < pointCount; i++) {
final Point nextPoint = randomPointIn(pointZone);
builder.pointXY(nextPoint.getX(), nextPoint.getY());
}
builder.buffer(width);
try {
return builder.build();
} catch (IllegalArgumentException e) {
// This is what happens when we create a shape that is invalid. Although it is conceivable that there are cases where
// the exception is thrown incorrectly, we aren't going to be able to do that in this random test.
continue;
}
}
}
@Override
protected Point randomPointInEmptyShape(Shape shape) {
throw new IllegalStateException("unexpected; need to finish test code");
}
@Override
protected int getWithinMinimum(int laps) {
// Long/thin so lets just find 1.
return 1;
}
}.testRelateWithRectangle();
}
}