Added a getEnclosingCap method for spherical polygons.

In simple cases (small polygon, one piece only, no holes), the enclosing
cap will be the smallest one, but this is not guaranteed in the general
case.

The enclosing cap can be used to speed up inside/outside points when the
region is small with respect to the sphere as with only one check of an
angular separation, most points of the sphere can be safely identified
as outside. The more time-consuming checks involving the full boundary
are therefore done only for a small portion of the sphere.

git-svn-id: https://svn.apache.org/repos/asf/commons/proper/math/trunk@1566358 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Luc Maisonobe 2014-02-09 19:17:55 +00:00
parent 2535320878
commit 5ae01f1f99
7 changed files with 457 additions and 21 deletions

View File

@ -268,6 +268,28 @@ public abstract class AbstractRegion<S extends Space, T extends Space> implement
}
/** {@inheritDoc} */
public boolean isFull() {
return isFull(tree);
}
/** {@inheritDoc} */
public boolean isFull(final BSPTree<S> node) {
// we use a recursive function rather than the BSPTreeVisitor
// interface because we can stop visiting the tree as soon as we
// have found an outside cell
if (node.getCut() == null) {
// if we find an outside node, the region does not cover full space
return (Boolean) node.getAttribute();
}
// check both sides of the sub-tree
return isFull(node.getMinus()) && isFull(node.getPlus());
}
/** {@inheritDoc} */
public boolean contains(final Region<S> region) {
return new RegionFactory<S>().difference(region, this).isEmpty();

View File

@ -99,6 +99,20 @@ public interface Region<S extends Space> {
*/
boolean isEmpty(final BSPTree<S> node);
/** Check if the instance covers the full space.
* @return true if the instance covers the full space
*/
boolean isFull();
/** Check if the sub-tree starting at a given node covers the full space.
* @param node root node of the sub-tree (<em>must</em> have {@link
* Region Region} tree semantics, i.e. the leaf nodes must have
* {@code Boolean} attributes representing an inside/outside
* property)
* @return true if the sub-tree starting at the given node covers the full space
*/
boolean isFull(final BSPTree<S> node);
/** Check if the instance entirely contains another region.
* @param region region to check against the instance
* @return true if the instance contains the specified tree

View File

@ -132,8 +132,7 @@ class PropertiesComputer implements BSPTreeVisitor<Sphere2D> {
// loop around the cell
for (Edge e = start.getOutgoing(); n == 0 || e.getStart() != start; e = e.getEnd().getOutgoing()) {
final Vector3D middle = e.getPointAt(0.5 * e.getLength());
sumB = new Vector3D(1, sumB, e.getLength(), middle);
sumB = new Vector3D(1, sumB, e.getLength(), e.getCircle().getPole());
n++;
}

View File

@ -41,7 +41,7 @@ import org.apache.commons.math3.util.FastMath;
* boundary points only is ambiguous: one cannot say if what has been
* computed is the cap or its complement without further information. For
* this reason, the generator must be built with a point known to lie
* outside of the spherical cap to generate, so it can decide which of the
* inside of the spherical cap to generate, so it can decide which of the
* two caps should be generated.
* </p>
* @version $Id$
@ -49,21 +49,21 @@ import org.apache.commons.math3.util.FastMath;
*/
public class SphericalCapGenerator implements SupportBallGenerator<Sphere2D, S2Point> {
/** Reference point that must be outside of the cap. */
private final Vector3D outside;
/** Reference point that must be inside of the cap. */
private final Vector3D inside;
/** Simple constructor.
* @param outside reference point that must be outside of generated caps
* @param inside reference point that must be inside of generated caps
*/
public SphericalCapGenerator(final Vector3D outside) {
this.outside = outside;
public SphericalCapGenerator(final Vector3D inside) {
this.inside = inside;
}
/** {@inheritDoc} */
public EnclosingBall<Sphere2D, S2Point> ballOnSupport(final List<S2Point> support) {
if (support.size() < 1) {
return new EnclosingBall<Sphere2D, S2Point>(new S2Point(outside.negate()), -1.0);
return new EnclosingBall<Sphere2D, S2Point>(new S2Point(inside), -1.0);
} else {
final S2Point vA = support.get(0);
if (support.size() < 2) {
@ -92,6 +92,32 @@ public class SphericalCapGenerator implements SupportBallGenerator<Sphere2D, S2P
}
}
/** Generate a spherical cap enclosing three circles.
* @param c1 first circle
* @param c2 second circle
* @param c3 third circle
* @return spherical cap enclosing the circles
*/
public EnclosingBall<Sphere2D, S2Point> ballOnSupport(final Circle c1,
final Circle c2,
final Circle c3) {
final BigFraction[] p1 = convert(c1.getPole());
final BigFraction[] p2 = convert(c2.getPole());
final BigFraction[] p3 = convert(c3.getPole());
final BigFraction[] bfPole = crossProduct(subtract(p1, p2),
subtract(p2, p3));
if (dotProduct(bfPole, p1).doubleValue() < 0) {
bfPole[0] = bfPole[0].negate();
bfPole[1] = bfPole[1].negate();
bfPole[2] = bfPole[2].negate();
}
final Vector3D pole = convert(bfPole);
return new EnclosingBall<Sphere2D, S2Point>(new S2Point(pole),
Vector3D.angle(pole, c1.getPole()) + 0.5 * FastMath.PI);
}
/** Convert a vector to exact arithmetic array of Cartesian coordinates.
* @param v vector to convert
* @return exact arithmetic coordinates array
@ -132,6 +158,19 @@ public class SphericalCapGenerator implements SupportBallGenerator<Sphere2D, S2P
};
}
/** Subtract two vectors
* @param u first vector
* @param v second vector
* @return u - v
*/
private BigFraction[] subtract(final BigFraction[] u, final BigFraction[] v) {
return new BigFraction[] {
u[0].subtract(v[0]),
u[1].subtract(v[1]),
u[2].subtract(v[2]),
};
}
/** Compute cross product of two vectors
* @param u first vector
* @param v second vector
@ -145,6 +184,15 @@ public class SphericalCapGenerator implements SupportBallGenerator<Sphere2D, S2P
};
}
/** Compute dot product of two vectors
* @param u first vector
* @param v second vector
* @return u.v
*/
private BigFraction dotProduct(final BigFraction[] u, final BigFraction[] v) {
return u[0].multiply(v[0]).add(u[1].multiply(v[1])).add(u[2].multiply(v[2]));
}
/** Select the spherical cap or its complement to ensure outside point is outside.
* @param pole spherical cap pole (or its opposite)
* @param radius spherical cap angular radius (or its complement to \( \pi \))
@ -153,7 +201,7 @@ public class SphericalCapGenerator implements SupportBallGenerator<Sphere2D, S2P
*/
private EnclosingBall<Sphere2D, S2Point> selectCap(final Vector3D pole, final double radius,
final S2Point ... support) {
if (Vector3D.angle(pole, outside) >= radius) {
if (Vector3D.angle(pole, inside) <= radius) {
// it is already the good pole and radius
return new EnclosingBall<Sphere2D, S2Point>(new S2Point(pole), radius, support);
} else {

View File

@ -19,10 +19,14 @@ package org.apache.commons.math3.geometry.spherical.twod;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.math3.exception.MathIllegalStateException;
import org.apache.commons.math3.exception.MathInternalError;
import org.apache.commons.math3.geometry.enclosing.EnclosingBall;
import org.apache.commons.math3.geometry.enclosing.WelzlEncloser;
import org.apache.commons.math3.geometry.euclidean.threed.Rotation;
import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;
import org.apache.commons.math3.geometry.partitioning.AbstractRegion;
@ -410,4 +414,313 @@ public class SphericalPolygonsSet extends AbstractRegion<Sphere2D, Sphere1D> {
}
/** Get a spherical cap enclosing the polygon.
* <p>
* This method is intended as a first test to quickly identify points
* that are guaranteed to be outside of the region, hence performing a full
* {@link #checkPoint(org.apache.commons.math3.geometry.Vector) checkPoint}
* only if the point status remains undecided after the quick check. It is
* is therefore mostly useful to speed up computation for small polygons with
* complex shapes (say a country boundary on Earth), as the spherical cap will
* be small and hence will reliably identify a large part of the sphere as outside,
* whereas the full check can be more computing intensive. A typical use case is
* therefore:
* </p>
* <pre>
* // compute region, plus an enclosing spherical cap
* SphericalPolygonsSet complexShape = ...;
* EnclosingBall<Sphere2D, S2Point> cap = complexShape.getEnclosingCap();
*
* // check lots of points
* for (Vector3D p : points) {
*
* final Location l;
* if (cap.contains(p)) {
* // we cannot be sure where the point is
* // we need to perform the full computation
* l = complexShape.checkPoint(v);
* } else {
* // no need to do further computation,
* // we already know the point is outside
* l = Location.OUTSIDE;
* }
*
* // use l ...
*
* }
* </pre>
* <p>
* In the special cases of empty or whole sphere polygons, special
* spherical caps are returned, with angular radius set to negative
* or positive infinity so the {@link
* EnclosingBall#contains(org.apache.commons.math3.geometry.Point) ball.contains(point)}
* method return always false or true.
* </p>
* <p>
* This method is <em>not</em> guaranteed to return the smallest enclosing cap. It
* will do so for small polygons without holes, but may select a very large spherical
* cap in other cases.
* </p>
* @return a spherical cap enclosing the polygon
*/
public EnclosingBall<Sphere2D, S2Point> getEnclosingCap() {
// handle special cases first
if (isEmpty()) {
return new EnclosingBall<Sphere2D, S2Point>(S2Point.PLUS_K, Double.NEGATIVE_INFINITY);
}
if (isFull()) {
return new EnclosingBall<Sphere2D, S2Point>(S2Point.PLUS_K, Double.POSITIVE_INFINITY);
}
// as the polygons is neither empty nor full, it has some boundaries and cut hyperplanes
final BSPTree<Sphere2D> root = getTree(false);
if (isEmpty(root.getMinus()) && isFull(root.getPlus())) {
// the polygon covers an hemisphere, and its boundary is one long edge
final Circle circle = (Circle) root.getCut().getHyperplane();
return new EnclosingBall<Sphere2D, S2Point>(new S2Point(circle.getPole()).negate(),
0.5 * FastMath.PI);
}
if (isFull(root.getMinus()) && isEmpty(root.getPlus())) {
// the polygon covers an hemisphere, and its boundary is one long edge
final Circle circle = (Circle) root.getCut().getHyperplane();
return new EnclosingBall<Sphere2D, S2Point>(new S2Point(circle.getPole()),
0.5 * FastMath.PI);
}
// extract boundary
final List<Vertex> boundary = getBoundaryLoops();
final List<S2Point> points = new ArrayList<S2Point>();
for (final Vertex loopStart : boundary) {
// extract points from the boundary loops, to be used by the encloser
for (Vertex v = loopStart; points.isEmpty() || v != loopStart; v = v.getOutgoing().getEnd()) {
points.add(v.getLocation());
}
}
// find the smallest enclosing spherical cap
final SphericalCapGenerator capGenerator = new SphericalCapGenerator(getInsidePoint(boundary));
final WelzlEncloser<Sphere2D, S2Point> encloser =
new WelzlEncloser<Sphere2D, S2Point>(getTolerance(), capGenerator);
EnclosingBall<Sphere2D, S2Point> enclosing = encloser.enclose(points);
// ensure that the spherical cap (which was defined using only a few points)
// does not cross any edges
if (enclosing.getRadius() >= 0.5 * FastMath.PI) {
final List<Edge> notEnclosed = notEnclosed(boundary, enclosing);
if (!notEnclosed.isEmpty()) {
// the vertex-based spherical cap is too small
// it does not fully enclose some edges
// add the edges converging to the support points if any
for (final S2Point point : enclosing.getSupport()) {
final Vertex vertex = associatedVertex(point, boundary);
if (vertex != null) {
addEdge(vertex.getIncoming(), notEnclosed);
addEdge(vertex.getOutgoing(), notEnclosed);
}
}
if (notEnclosed.size() < 3) {
// this should never happen
throw new MathInternalError();
}
// enlarge the spherical cap so it encloses even the three farthest edges
Collections.sort(notEnclosed, new DistanceComparator(enclosing.getCenter().getVector()));
enclosing = capGenerator.ballOnSupport(notEnclosed.get(notEnclosed.size() - 1).getCircle(),
notEnclosed.get(notEnclosed.size() - 2).getCircle(),
notEnclosed.get(notEnclosed.size() - 3).getCircle());
}
}
// return the spherical cap found
return enclosing;
}
/** Get an inside point.
* @param boundary boundary loops (known to be non-empty)
* @return an inside point
*/
private Vector3D getInsidePoint(final List<Vertex> boundary) {
// search for a leaf inside cell bounded by the loop
final BSPTree<Sphere2D> leaf = getInsideLeaf(boundary.get(0).getOutgoing());
// build a convex region from this inside leaf
final SphericalPolygonsSet convex =
new SphericalPolygonsSet(leaf.pruneAroundConvexCell(Boolean.TRUE, Boolean.FALSE, null),
getTolerance());
// select the convex region barycenter as the inside point
return ((S2Point) convex.getBarycenter()).getVector();
}
/** Get an inside leaf cell relative to a single boundary loop.
* @param edge edge belonging to the loop
* @return an inside leaf cell
*/
private BSPTree<Sphere2D> getInsideLeaf(final Edge edge) {
// search for the node whose cut sub-hyperplane contains the edge
final S2Point middlePoint = new S2Point(edge.getPointAt(0.5 * edge.getLength()));
final BSPTree<Sphere2D> cuttingNode = getTree(true).getCell(middlePoint, getTolerance());
if (cuttingNode.getCut() == null) {
// this should never happen
throw new MathInternalError();
}
final Vector3D cutPole = ((Circle) cuttingNode.getCut().getHyperplane()).getPole();
final Vector3D edgePole = edge.getCircle().getPole();
if (Vector3D.dotProduct(cutPole, edgePole) > 0) {
// the inside part is on the minus part of the cut
return cuttingNode.getMinus().getCell(middlePoint, getTolerance());
} else {
// the inside part is on the plus part of the cut
return cuttingNode.getPlus().getCell(middlePoint, getTolerance());
}
}
/** Get the edges that fails to be in an enclosing spherical cap.
* <p>
* This method must be called <em>only</em> when the edges endpoints
* are already known to be all inside of the spherical cap.
* </p>
* @param boundary boundary loops
* @param cap spherical cap that already encloses all vertices
* @return the edges that are not fully in the cap, despite their endpoints are
*/
private List<Edge> notEnclosed(final List<Vertex> boundary,
final EnclosingBall<Sphere2D, S2Point> cap) {
final List<Edge> edges = new ArrayList<Edge>();
for (final Vertex loopStart : boundary) {
int count = 0;
Edge edge = null;
for (Vertex vertex = loopStart; count == 0 || vertex != loopStart; vertex = edge.getEnd()) {
++count;
edge = vertex.getOutgoing();
// find the intersection of the circle containing the edge and the cap boundary
// let (u, α) and (v, β) be the poles and angular radii of the two circles
// the intersection is computed as p = a u + b v + c u^v, with ||p|| = 1, so:
// a = (cos α - u.v cos β) / (1 - (u.v)²)
// b = (cos β - u.v cos α) / (1 - (u.v)²)
// c = ±[(1 - (a u + b v)²) / (1 - (u.v)²)]
// here, α = π/2, hence cos α = 0
// a u + b v is the part of the p vector in the (u, v) plane
// c u^v is the part of the p vector orthogonal to the (u, v) plane
final Vector3D u = edge.getCircle().getPole();
final Vector3D v = cap.getCenter().getVector();
final double cosBeta = FastMath.cos(cap.getRadius());
final double s = Vector3D.dotProduct(u, v);
final double f = 1.0 / ((1 - s) * (1 + s));
final double b = cosBeta * f;
final double a = -s * b;
final Vector3D inPlane = new Vector3D(a, u, b, v);
final double aubv2 = inPlane.getNormSq();
if (aubv2 < 1.0) {
// the two circles have two intersection points
final double c = FastMath.sqrt((1 - aubv2) * f);
final Vector3D crossPlane = Vector3D.crossProduct(u, v);
final Vector3D pPlus = new Vector3D(1, inPlane, +c, crossPlane);
final Vector3D pMinus = new Vector3D(1, inPlane, -c, crossPlane);
if (Vector3D.angle(pPlus, edge.getStart().getLocation().getVector()) <= getTolerance() ||
Vector3D.angle(pPlus, edge.getStart().getLocation().getVector()) <= getTolerance() ||
Vector3D.angle(pPlus, edge.getStart().getLocation().getVector()) <= getTolerance() ||
Vector3D.angle(pPlus, edge.getStart().getLocation().getVector()) <= getTolerance()) {
// the edge endpoints are really close, we select the edge
edges.add(edge);
} else {
// the edge is limited to a part of the circle, check its extension
final double startPhase = edge.getCircle().getPhase(edge.getStart().getLocation().getVector());
final double pPlusRelativePhase = MathUtils.normalizeAngle(edge.getCircle().getPhase(pPlus),
startPhase + FastMath.PI);
final double pMinusRelativePhase = MathUtils.normalizeAngle(edge.getCircle().getPhase(pMinus),
startPhase + FastMath.PI);
if (FastMath.min(pPlusRelativePhase, pMinusRelativePhase) < edge.getLength()) {
// the edge really goes out of the cap and enter it again,
// it is not entirely contained in the cap
edges.add(edge);
}
}
}
}
}
return edges;
}
/** Find the vertex associated to a point.
* @param point point to associate to a vertex
* @param boundary boundary loops
* @return vertex associated to point, or null if the point is not a vertex
*/
private Vertex associatedVertex(final S2Point point, final List<Vertex> boundary) {
for (final Vertex loopStart : boundary) {
int count = 0;
for (Vertex vertex = loopStart; count == 0 || vertex != loopStart; vertex = vertex.getOutgoing().getEnd()) {
++count;
if (point == vertex.getLocation()) {
return vertex;
}
}
}
return null;
}
/** Add an edge to a list if not already present.
* @param edge edge to add
* @param list to which edge must be added
*/
private void addEdge(final Edge edge, final List<Edge> list) {
for (final Edge present : list) {
if (present == edge) {
// the edge was already in the list
return;
}
}
list.add(edge);
}
/** Comparator for sorting edges according to distance wrt a spherical cap pole. */
private static class DistanceComparator implements Comparator<Edge> {
/** Pole of the spherical cap. */
private final Vector3D pole;
/** Simple constructor.
* @param pole pole of the spherical cap
*/
public DistanceComparator(final Vector3D pole) {
this.pole = pole;
}
/** {@inheritDoc} */
public int compare(final Edge o1, final Edge o2) {
return Double.compare(distance(o1), distance(o2));
}
/** Compute distance between edge and pole.
* @param edge edge
* @return distance between adged and pole
*/
private double distance(final Edge edge) {
final double alpha = edge.getCircle().getPole().distance(pole);
return FastMath.max(alpha + 0.5 * FastMath.PI, 1.5 * FastMath.PI - alpha);
}
}
}

View File

@ -36,7 +36,7 @@ public class SphericalCapGeneratorTest {
@Test
public void testSupport0Point() {
List<S2Point> support = Arrays.asList(new S2Point[0]);
EnclosingBall<Sphere2D, S2Point> cap = new SphericalCapGenerator(Vector3D.MINUS_K).ballOnSupport(support);
EnclosingBall<Sphere2D, S2Point> cap = new SphericalCapGenerator(Vector3D.PLUS_K).ballOnSupport(support);
Assert.assertTrue(cap.getRadius() < 0);
Assert.assertEquals(0, cap.getSupportSize());
Assert.assertEquals(0, cap.getSupport().length);
@ -45,7 +45,7 @@ public class SphericalCapGeneratorTest {
@Test
public void testSupport1Point() {
List<S2Point> support = Arrays.asList(new S2Point(1, 2));
EnclosingBall<Sphere2D, S2Point> cap = new SphericalCapGenerator(Vector3D.MINUS_K).ballOnSupport(support);
EnclosingBall<Sphere2D, S2Point> cap = new SphericalCapGenerator(Vector3D.PLUS_K).ballOnSupport(support);
Assert.assertEquals(0.0, cap.getRadius(), 1.0e-10);
Assert.assertTrue(cap.contains(support.get(0)));
Assert.assertTrue(cap.contains(support.get(0), 0.5));
@ -65,7 +65,7 @@ public class SphericalCapGeneratorTest {
double phi = 1.0;
List<S2Point> support = Arrays.asList(new S2Point(0, phi), new S2Point(0.5 * FastMath.PI, phi));
EnclosingBall<Sphere2D, S2Point> cap =
new SphericalCapGenerator(new Vector3D(-1, -1, -1)).ballOnSupport(support);
new SphericalCapGenerator(new Vector3D(1, 1, 1)).ballOnSupport(support);
double cosPhi = FastMath.cos(phi);
Assert.assertEquals(0.5 * FastMath.acos(cosPhi * cosPhi), cap.getRadius(), 1.0e-10);
int i = 0;
@ -87,7 +87,7 @@ public class SphericalCapGeneratorTest {
double phi = 1.0;
List<S2Point> support = Arrays.asList(new S2Point(0, phi), new S2Point(0.5 * FastMath.PI, phi));
EnclosingBall<Sphere2D, S2Point> cap =
new SphericalCapGenerator(new Vector3D(1, 1, 1)).ballOnSupport(support);
new SphericalCapGenerator(new Vector3D(-1, -1, -1)).ballOnSupport(support);
double cosPhi = FastMath.cos(phi);
Assert.assertEquals(FastMath.PI - 0.5 * FastMath.acos(cosPhi * cosPhi), cap.getRadius(), 1.0e-10);
int i = 0;
@ -108,7 +108,7 @@ public class SphericalCapGeneratorTest {
public void testSupport3Points() {
List<S2Point> support = Arrays.asList(new S2Point(0, 1), new S2Point(0, 1.6), new S2Point(2, 2));
EnclosingBall<Sphere2D, S2Point> cap =
new SphericalCapGenerator(Vector3D.MINUS_I).ballOnSupport(support);
new SphericalCapGenerator(Vector3D.PLUS_I).ballOnSupport(support);
// reference value computed using expression found in I. Todhunter book
// Spherical Trigonometry: For the Use of Colleges and Schools
@ -162,13 +162,13 @@ public class SphericalCapGeneratorTest {
sinR * FastMath.sin(alpha), y)));
}
EnclosingBall<Sphere2D, S2Point> cap =
new SphericalCapGenerator(Vector3D.MINUS_K).ballOnSupport(support);
if (refCenter.distance(S2Point.MINUS_K) > refRadius) {
Assert.assertEquals(0.0, refCenter.distance(cap.getCenter()), 3e-9 * refRadius);
Assert.assertEquals(refRadius, cap.getRadius(), 7e-10 * refRadius);
new SphericalCapGenerator(Vector3D.PLUS_K).ballOnSupport(support);
if (refCenter.distance(S2Point.PLUS_K) < refRadius) {
Assert.assertEquals(0.0, refCenter.distance(cap.getCenter()), 3e-9 * refRadius);
Assert.assertEquals(refRadius, cap.getRadius(), 7e-10 * refRadius);
} else {
Assert.assertEquals(FastMath.PI, refCenter.distance(cap.getCenter()), 3e-9 * refRadius);
Assert.assertEquals(FastMath.PI - refRadius, cap.getRadius(), 7e-10 * refRadius);
Assert.assertEquals(FastMath.PI - refRadius, cap.getRadius(), 7e-10 * refRadius);
}
}

View File

@ -19,6 +19,7 @@ package org.apache.commons.math3.geometry.spherical.twod;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.math3.geometry.enclosing.EnclosingBall;
import org.apache.commons.math3.geometry.euclidean.threed.Rotation;
import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;
import org.apache.commons.math3.geometry.partitioning.Region.Location;
@ -47,6 +48,25 @@ public class SphericalPolygonsSetTest {
Assert.assertEquals(4 * FastMath.PI, new SphericalPolygonsSet(0.01, new S2Point[0]).getSize(), 1.0e-10);
Assert.assertEquals(0, new SphericalPolygonsSet(0.01, new S2Point[0]).getBoundarySize(), 1.0e-10);
Assert.assertEquals(0, full.getBoundaryLoops().size());
Assert.assertTrue(full.getEnclosingCap().getRadius() > 0);
Assert.assertTrue(Double.isInfinite(full.getEnclosingCap().getRadius()));
}
@Test
public void testEmpty() {
SphericalPolygonsSet empty =
(SphericalPolygonsSet) new RegionFactory<Sphere2D>().getComplement(new SphericalPolygonsSet(1.0e-10));
UnitSphereRandomVectorGenerator random =
new UnitSphereRandomVectorGenerator(3, new Well1024a(0x76d9205d6167b6ddl));
for (int i = 0; i < 1000; ++i) {
Vector3D v = new Vector3D(random.nextVector());
Assert.assertEquals(Location.OUTSIDE, empty.checkPoint(new S2Point(v)));
}
Assert.assertEquals(0, empty.getSize(), 1.0e-10);
Assert.assertEquals(0, empty.getBoundarySize(), 1.0e-10);
Assert.assertEquals(0, empty.getBoundaryLoops().size());
Assert.assertTrue(empty.getEnclosingCap().getRadius() < 0);
Assert.assertTrue(Double.isInfinite(empty.getEnclosingCap().getRadius()));
}
@Test
@ -67,6 +87,16 @@ public class SphericalPolygonsSetTest {
}
}
Assert.assertEquals(1, south.getBoundaryLoops().size());
EnclosingBall<Sphere2D, S2Point> southCap = south.getEnclosingCap();
Assert.assertEquals(0.0, S2Point.MINUS_K.distance(southCap.getCenter()), 1.0e-10);
Assert.assertEquals(0.5 * FastMath.PI, southCap.getRadius(), 1.0e-10);
EnclosingBall<Sphere2D, S2Point> northCap =
((SphericalPolygonsSet) new RegionFactory<Sphere2D>().getComplement(south)).getEnclosingCap();
Assert.assertEquals(0.0, S2Point.PLUS_K.distance(northCap.getCenter()), 1.0e-10);
Assert.assertEquals(0.5 * FastMath.PI, northCap.getRadius(), 1.0e-10);
}
@Test
@ -123,10 +153,19 @@ public class SphericalPolygonsSetTest {
Assert.assertEquals(3, count);
Assert.assertEquals(0.0,
new Vector3D(1, 1, 1).normalize().distance(((S2Point) octant.getBarycenter()).getVector()),
((S2Point) octant.getBarycenter()).distance(new S2Point(new Vector3D(1, 1, 1))),
1.0e-10);
Assert.assertEquals(0.5 * FastMath.PI, octant.getSize(), 1.0e-10);
EnclosingBall<Sphere2D, S2Point> cap = octant.getEnclosingCap();
Assert.assertEquals(0.0, octant.getBarycenter().distance(cap.getCenter()), 1.0e-10);
Assert.assertEquals(FastMath.acos(1.0 / FastMath.sqrt(3)), cap.getRadius(), 1.0e-10);
EnclosingBall<Sphere2D, S2Point> reversedCap =
((SphericalPolygonsSet) factory.getComplement(octant)).getEnclosingCap();
Assert.assertEquals(0, reversedCap.getCenter().distance(new S2Point(new Vector3D(-1, -1, -1))), 1.0e-10);
Assert.assertEquals(FastMath.PI - FastMath.asin(1.0 / FastMath.sqrt(3)), reversedCap.getRadius(), 1.0e-10);
}
@Test
@ -393,6 +432,7 @@ public class SphericalPolygonsSetTest {
Assert.assertEquals(+0.027, concentric.projectToBoundary(new S2Point( 1.67, phi)).getOffset(), 0.01);
Assert.assertEquals(-0.044, concentric.projectToBoundary(new S2Point( 1.79, phi)).getOffset(), 0.01);
Assert.assertEquals(+0.201, concentric.projectToBoundary(new S2Point( 2.16, phi)).getOffset(), 0.01);
}
private SubCircle create(Vector3D pole, Vector3D x, Vector3D y,