[MATH-749] Improve robustness for convex hull algorithms in case of identical / collinear points, added flag whether to include only extreme points or all points on the hull, improve unit tests.
git-svn-id: https://svn.apache.org/repos/asf/commons/proper/math/trunk@1564934 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
e5002ce3f6
commit
e5dc3ad337
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
* 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.commons.math3.geometry.euclidean.twod.hull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.apache.commons.math3.exception.NullArgumentException;
|
||||
import org.apache.commons.math3.geometry.euclidean.twod.Vector2D;
|
||||
import org.apache.commons.math3.util.MathUtils;
|
||||
|
||||
/**
|
||||
* Abstract base class for convex hull generators in the two-dimensional euclidean space.
|
||||
*
|
||||
* @since 3.3
|
||||
* @version $Id$
|
||||
*/
|
||||
public abstract class AbstractConvexHullGenerator2D implements ConvexHullGenerator2D {
|
||||
|
||||
/** Default value for tolerance. */
|
||||
private static final double DEFAULT_TOLERANCE = 1e-10;
|
||||
|
||||
/** Tolerance below which points are considered identical. */
|
||||
private final double tolerance;
|
||||
|
||||
/**
|
||||
* Indicates if collinear points on the hull shall be present in the output.
|
||||
* If {@code false}, only the extreme points are added to the hull.
|
||||
*/
|
||||
private final boolean includeCollinearPoints;
|
||||
|
||||
/**
|
||||
* Simple constructor.
|
||||
* <p>
|
||||
* Collinear points on the hull will not be added to the hull vertices and
|
||||
* {@code 1e-10} will be used as tolerance criteria for identical points.
|
||||
*/
|
||||
protected AbstractConvexHullGenerator2D() {
|
||||
this(false, DEFAULT_TOLERANCE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple constructor.
|
||||
* <p>
|
||||
* The default tolerance (1e-10) will be used to determine identical points.
|
||||
*
|
||||
* @param includeCollinearPoints indicates if collinear points on the hull shall be
|
||||
* added as hull vertices
|
||||
*/
|
||||
protected AbstractConvexHullGenerator2D(final boolean includeCollinearPoints) {
|
||||
this(includeCollinearPoints, DEFAULT_TOLERANCE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple constructor.
|
||||
*
|
||||
* @param includeCollinearPoints indicates if collinear points on the hull shall be
|
||||
* added as hull vertices
|
||||
* @param tolerance tolerance below which points are considered identical
|
||||
*/
|
||||
protected AbstractConvexHullGenerator2D(final boolean includeCollinearPoints, final double tolerance) {
|
||||
this.includeCollinearPoints = includeCollinearPoints;
|
||||
this.tolerance = tolerance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the tolerance below which points are considered identical.
|
||||
* @return the tolerance below which points are considered identical
|
||||
*/
|
||||
public double getTolerance() {
|
||||
return tolerance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if collinear points on the hull will be added as hull vertices.
|
||||
* @return {@code true} if collinear points are added as hull vertices, or {@code false}
|
||||
* if only extreme points are present.
|
||||
*/
|
||||
public boolean isIncludeCollinearPoints() {
|
||||
return includeCollinearPoints;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
public ConvexHull2D generate(final Collection<Vector2D> points) throws NullArgumentException {
|
||||
// check for null points
|
||||
MathUtils.checkNotNull(points);
|
||||
|
||||
final int size = points.size();
|
||||
if (size == 2) {
|
||||
// special case: check that the two points are not identical
|
||||
final Iterator<Vector2D> it = points.iterator();
|
||||
final Vector2D firstPoint = it.next();
|
||||
final Vector2D secondPoint = it.next();
|
||||
if (firstPoint.distance(secondPoint) > tolerance) {
|
||||
return new ConvexHull2D(points, tolerance);
|
||||
} else {
|
||||
return new ConvexHull2D(Arrays.asList(firstPoint), tolerance);
|
||||
}
|
||||
} else if (size < 2) {
|
||||
return new ConvexHull2D(points, tolerance);
|
||||
}
|
||||
|
||||
final Collection<Vector2D> hullVertices = generateHull(points);
|
||||
return new ConvexHull2D(hullVertices, tolerance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the convex hull vertices from the set of input points.
|
||||
* @param points the set of input points
|
||||
* @return the convex hull vertices in CCW winding
|
||||
*/
|
||||
protected abstract Collection<Vector2D> generateHull(Collection<Vector2D> points);
|
||||
|
||||
}
|
|
@ -23,11 +23,8 @@ import java.util.Comparator;
|
|||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.math3.exception.NullArgumentException;
|
||||
import org.apache.commons.math3.geometry.euclidean.twod.Line;
|
||||
import org.apache.commons.math3.geometry.euclidean.twod.Vector2D;
|
||||
import org.apache.commons.math3.util.FastMath;
|
||||
import org.apache.commons.math3.util.MathUtils;
|
||||
|
||||
/**
|
||||
* Implements Graham's scan method to generate the convex hull of a finite set of
|
||||
|
@ -40,47 +37,50 @@ import org.apache.commons.math3.util.MathUtils;
|
|||
* @since 3.3
|
||||
* @version $Id$
|
||||
*/
|
||||
public class GrahamScan implements ConvexHullGenerator2D {
|
||||
|
||||
/** Default value for tolerance. */
|
||||
private static final double DEFAULT_TOLERANCE = 1e-10;
|
||||
public class GrahamScan extends AbstractConvexHullGenerator2D {
|
||||
|
||||
/** Vector representing the x-axis. */
|
||||
private static final Vector2D X_AXIS = new Vector2D(1.0, 0.0);
|
||||
|
||||
/** Tolerance below which points are considered identical. */
|
||||
private final double tolerance;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
* Create a new GrahamScan instance.
|
||||
* <p>
|
||||
* The default tolerance (1e-10) will be used to determine identical points.
|
||||
* Collinear points on the hull will not be added to the hull vertices and
|
||||
* {@code 1e-10} will be used as tolerance criteria for identical points.
|
||||
*/
|
||||
public GrahamScan() {
|
||||
this(DEFAULT_TOLERANCE);
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance with the given tolerance for determining identical points.
|
||||
* @param tolerance tolerance below which points are considered identical
|
||||
* Create a new GrahamScan instance.
|
||||
* <p>
|
||||
* The default tolerance (1e-10) will be used to determine identical points.
|
||||
*
|
||||
* @param includeCollinearPoints indicates if collinear points on the hull shall be
|
||||
* added as hull vertices
|
||||
*/
|
||||
public GrahamScan(final double tolerance) {
|
||||
this.tolerance = tolerance;
|
||||
public GrahamScan(final boolean includeCollinearPoints) {
|
||||
super(includeCollinearPoints);
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
public ConvexHull2D generate(final Collection<Vector2D> points) throws NullArgumentException {
|
||||
/**
|
||||
* Create a new GrahamScan instance.
|
||||
*
|
||||
* @param includeCollinearPoints indicates if collinear points on the hull shall be
|
||||
* added as hull vertices
|
||||
* @param tolerance tolerance below which points are considered identical
|
||||
*/
|
||||
public GrahamScan(final boolean includeCollinearPoints, final double tolerance) {
|
||||
super(includeCollinearPoints, tolerance);
|
||||
}
|
||||
|
||||
// check for null points
|
||||
MathUtils.checkNotNull(points);
|
||||
|
||||
if (points.size() < 3) {
|
||||
return new ConvexHull2D(points, tolerance);
|
||||
}
|
||||
@Override
|
||||
protected Collection<Vector2D> generateHull(final Collection<Vector2D> points) {
|
||||
|
||||
final Vector2D referencePoint = getReferencePoint(points);
|
||||
|
||||
final List<Vertex> pointsSortedByAngle = new ArrayList<Vertex>();
|
||||
final List<Vertex> pointsSortedByAngle = new ArrayList<Vertex>(points.size());
|
||||
for (final Vector2D p : points) {
|
||||
pointsSortedByAngle.add(new Vertex(p, getAngleBetweenPoints(p, referencePoint)));
|
||||
}
|
||||
|
@ -92,13 +92,22 @@ public class GrahamScan implements ConvexHullGenerator2D {
|
|||
}
|
||||
});
|
||||
|
||||
// list containing the vertices of the hull in ccw direction
|
||||
final List<Vector2D> hullVertices = new ArrayList<Vector2D>(points.size());
|
||||
// list containing the vertices of the hull in CCW direction
|
||||
final List<Vector2D> hullVertices = new ArrayList<Vector2D>();
|
||||
|
||||
// push the first two points on the stack
|
||||
final Iterator<Vertex> it = pointsSortedByAngle.iterator();
|
||||
hullVertices.add(it.next().point);
|
||||
hullVertices.add(it.next().point);
|
||||
final Vector2D firstPoint = it.next().point;
|
||||
hullVertices.add(firstPoint);
|
||||
|
||||
final double tolerance = getTolerance();
|
||||
// ensure that we do not add an identical point
|
||||
while(hullVertices.size() < 2 && it.hasNext()) {
|
||||
final Vector2D p = it.next().point;
|
||||
if (firstPoint.distance(p) >= tolerance) {
|
||||
hullVertices.add(p);
|
||||
}
|
||||
}
|
||||
|
||||
Vector2D currentPoint = null;
|
||||
while (it.hasNext() || currentPoint != null) {
|
||||
|
@ -113,27 +122,47 @@ public class GrahamScan implements ConvexHullGenerator2D {
|
|||
// get the last line segment of the current convex hull
|
||||
final Vector2D p1 = hullVertices.get(size - 2);
|
||||
final Vector2D p2 = hullVertices.get(size - 1);
|
||||
final Line line = new Line(p1, p2, tolerance);
|
||||
|
||||
if (currentPoint == null) {
|
||||
currentPoint = it.next().point;
|
||||
}
|
||||
|
||||
// test if the current point is to the left of the line
|
||||
final double offset = line.getOffset(currentPoint);
|
||||
final double offset = currentPoint.crossProduct(p1, p2);
|
||||
|
||||
if (offset < 0.0) {
|
||||
// the current point forms a convex section
|
||||
if (FastMath.abs(offset) < tolerance) {
|
||||
// the point is collinear to the line (p1, p2)
|
||||
|
||||
final double distanceToCurrent = p1.distance(currentPoint);
|
||||
if (distanceToCurrent < tolerance || p2.distance(currentPoint) < tolerance) {
|
||||
// the point is assumed to be identical to either p1 or p2
|
||||
currentPoint = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
final double distanceToLast = p1.distance(p2);
|
||||
if (isIncludeCollinearPoints()) {
|
||||
final int index = distanceToCurrent < distanceToLast ? size - 1 : size;
|
||||
hullVertices.add(index, currentPoint);
|
||||
currentPoint = null;
|
||||
} else {
|
||||
if (distanceToCurrent > distanceToLast) {
|
||||
hullVertices.remove(size - 1);
|
||||
} else {
|
||||
currentPoint = null;
|
||||
}
|
||||
}
|
||||
} else if (offset > 0.0) {
|
||||
// the current point forms a convex polygon
|
||||
hullVertices.add(currentPoint);
|
||||
currentPoint = null;
|
||||
} else {
|
||||
// otherwise, the point is either collinear or will create
|
||||
// a concave section, thus we need to remove the last point.
|
||||
// the current point creates a concave polygon, remove the last point
|
||||
hullVertices.remove(size - 1);
|
||||
}
|
||||
}
|
||||
|
||||
return new ConvexHull2D(hullVertices, tolerance);
|
||||
return hullVertices;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -145,7 +174,7 @@ public class GrahamScan implements ConvexHullGenerator2D {
|
|||
* @param points the point set
|
||||
* @return the point with the lowest y-coordinate
|
||||
*/
|
||||
private Vector2D getReferencePoint(final Collection<Vector2D> points) {
|
||||
private Vector2D getReferencePoint(final Iterable<Vector2D> points) {
|
||||
Vector2D minY = null;
|
||||
for (final Vector2D p : points) {
|
||||
if (minY == null) {
|
||||
|
|
|
@ -22,10 +22,8 @@ import java.util.Collections;
|
|||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.math3.exception.NullArgumentException;
|
||||
import org.apache.commons.math3.geometry.euclidean.twod.Vector2D;
|
||||
import org.apache.commons.math3.util.FastMath;
|
||||
import org.apache.commons.math3.util.MathUtils;
|
||||
|
||||
/**
|
||||
* Implements Andrew's monotone chain method to generate the convex hull of a finite set of
|
||||
|
@ -40,40 +38,43 @@ import org.apache.commons.math3.util.MathUtils;
|
|||
* @since 3.3
|
||||
* @version $Id$
|
||||
*/
|
||||
public class MonotoneChain implements ConvexHullGenerator2D {
|
||||
|
||||
/** Default value for tolerance. */
|
||||
private static final double DEFAULT_TOLERANCE = 1e-10;
|
||||
|
||||
/** Tolerance below which points are considered identical. */
|
||||
private final double tolerance;
|
||||
public class MonotoneChain extends AbstractConvexHullGenerator2D {
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
* Create a new MonotoneChain instance.
|
||||
* <p>
|
||||
* The default tolerance (1e-10) will be used to determine identical points.
|
||||
* Collinear points on the hull will not be added to the hull vertices and
|
||||
* {@code 1e-10} will be used as tolerance criteria for identical points.
|
||||
*/
|
||||
public MonotoneChain() {
|
||||
this(DEFAULT_TOLERANCE);
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance with the given tolerance for determining identical points.
|
||||
* @param tolerance tolerance below which points are considered identical
|
||||
* Create a new MonotoneChain instance.
|
||||
* <p>
|
||||
* The default tolerance (1e-10) will be used to determine identical points.
|
||||
*
|
||||
* @param includeCollinearPoints indicates if collinear points on the hull shall be
|
||||
* added as hull vertices
|
||||
*/
|
||||
public MonotoneChain(final double tolerance) {
|
||||
this.tolerance = tolerance;
|
||||
public MonotoneChain(final boolean includeCollinearPoints) {
|
||||
super(includeCollinearPoints);
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
public ConvexHull2D generate(final Collection<Vector2D> points) throws NullArgumentException {
|
||||
/**
|
||||
* Create a new MonotoneChain instance.
|
||||
*
|
||||
* @param includeCollinearPoints indicates if collinear points on the hull shall be
|
||||
* added as hull vertices
|
||||
* @param tolerance tolerance below which points are considered identical
|
||||
*/
|
||||
public MonotoneChain(final boolean includeCollinearPoints, final double tolerance) {
|
||||
super(includeCollinearPoints, tolerance);
|
||||
}
|
||||
|
||||
// check for null points
|
||||
MathUtils.checkNotNull(points);
|
||||
|
||||
if (points.size() < 3) {
|
||||
return new ConvexHull2D(points, tolerance);
|
||||
}
|
||||
@Override
|
||||
public Collection<Vector2D> generateHull(final Collection<Vector2D> points) {
|
||||
|
||||
final List<Vector2D> pointsSortedByXAxis = new ArrayList<Vector2D>(points);
|
||||
|
||||
|
@ -92,41 +93,19 @@ public class MonotoneChain implements ConvexHullGenerator2D {
|
|||
// build lower hull
|
||||
final List<Vector2D> lowerHull = new ArrayList<Vector2D>();
|
||||
for (Vector2D p : pointsSortedByXAxis) {
|
||||
while (lowerHull.size() >= 2) {
|
||||
final int size = lowerHull.size();
|
||||
final Vector2D p1 = lowerHull.get(size - 2);
|
||||
final Vector2D p2 = lowerHull.get(size - 1);
|
||||
|
||||
if (p.crossProduct(p1, p2) <= 0) {
|
||||
lowerHull.remove(size - 1);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
lowerHull.add(p);
|
||||
updateHull(p, lowerHull);
|
||||
}
|
||||
|
||||
// build upper hull
|
||||
final List<Vector2D> upperHull = new ArrayList<Vector2D>();
|
||||
for (int idx = pointsSortedByXAxis.size() - 1; idx >= 0; idx--) {
|
||||
final Vector2D p = pointsSortedByXAxis.get(idx);
|
||||
while (upperHull.size() >= 2) {
|
||||
final int size = upperHull.size();
|
||||
final Vector2D p1 = upperHull.get(size - 2);
|
||||
final Vector2D p2 = upperHull.get(size - 1);
|
||||
|
||||
if (p.crossProduct(p1, p2) <= 0) {
|
||||
upperHull.remove(size - 1);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
upperHull.add(p);
|
||||
updateHull(p, upperHull);
|
||||
}
|
||||
|
||||
// concatenate the lower and upper hulls
|
||||
// the last point of each list is omitted as it is repeated at the beginning of the other list
|
||||
List<Vector2D> hullVertices = new ArrayList<Vector2D>(lowerHull.size() + upperHull.size() - 2);
|
||||
final List<Vector2D> hullVertices = new ArrayList<Vector2D>(lowerHull.size() + upperHull.size() - 2);
|
||||
for (int idx = 0; idx < lowerHull.size() - 1; idx++) {
|
||||
hullVertices.add(lowerHull.get(idx));
|
||||
}
|
||||
|
@ -134,7 +113,60 @@ public class MonotoneChain implements ConvexHullGenerator2D {
|
|||
hullVertices.add(upperHull.get(idx));
|
||||
}
|
||||
|
||||
return new ConvexHull2D(hullVertices, tolerance);
|
||||
return hullVertices;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the partial hull with the current point.
|
||||
*
|
||||
* @param point the current point
|
||||
* @param hull the partial hull
|
||||
*/
|
||||
private void updateHull(final Vector2D point, final List<Vector2D> hull) {
|
||||
final double tolerance = getTolerance();
|
||||
|
||||
if (hull.size() == 1) {
|
||||
// ensure that we do not add an identical point
|
||||
final Vector2D p1 = hull.get(0);
|
||||
if (p1.distance(point) < tolerance) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
while (hull.size() >= 2) {
|
||||
final int size = hull.size();
|
||||
final Vector2D p1 = hull.get(size - 2);
|
||||
final Vector2D p2 = hull.get(size - 1);
|
||||
|
||||
final double offset = point.crossProduct(p1, p2);
|
||||
|
||||
if (FastMath.abs(offset) < tolerance) {
|
||||
// the point is collinear to the line (p1, p2)
|
||||
|
||||
final double distanceToCurrent = p1.distance(point);
|
||||
if (distanceToCurrent < tolerance || p2.distance(point) < tolerance) {
|
||||
// the point is assumed to be identical to either p1 or p2
|
||||
return;
|
||||
}
|
||||
|
||||
final double distanceToLast = p1.distance(p2);
|
||||
if (isIncludeCollinearPoints()) {
|
||||
final int index = distanceToCurrent < distanceToLast ? size - 1 : size;
|
||||
hull.add(index, point);
|
||||
} else {
|
||||
if (distanceToCurrent > distanceToLast) {
|
||||
hull.remove(size - 1);
|
||||
}
|
||||
hull.add(point);
|
||||
}
|
||||
return;
|
||||
} else if (offset < 0) {
|
||||
hull.remove(size - 1);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
hull.add(point);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -27,8 +27,8 @@ import org.apache.commons.math3.geometry.euclidean.twod.Vector2D;
|
|||
public class AklToussaintHeuristicTest extends ConvexHullGenerator2DAbstractTest {
|
||||
|
||||
@Override
|
||||
protected ConvexHullGenerator2D createConvexHullGenerator() {
|
||||
return new GrahamScan();
|
||||
protected ConvexHullGenerator2D createConvexHullGenerator(boolean includeCollinearPoints) {
|
||||
return new GrahamScan(includeCollinearPoints);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package org.apache.commons.math3.geometry.euclidean.twod.hull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
@ -43,7 +44,7 @@ public abstract class ConvexHullGenerator2DAbstractTest {
|
|||
protected ConvexHullGenerator2D generator;
|
||||
protected RandomGenerator random;
|
||||
|
||||
protected abstract ConvexHullGenerator2D createConvexHullGenerator();
|
||||
protected abstract ConvexHullGenerator2D createConvexHullGenerator(boolean includeCollinearPoints);
|
||||
|
||||
protected Collection<Vector2D> reducePoints(Collection<Vector2D> points) {
|
||||
// do nothing by default, may be overridden by other tests
|
||||
|
@ -52,7 +53,7 @@ public abstract class ConvexHullGenerator2DAbstractTest {
|
|||
|
||||
@Before
|
||||
public void setUp() {
|
||||
generator = createConvexHullGenerator();
|
||||
generator = createConvexHullGenerator(false);
|
||||
random = new MersenneTwister(10);
|
||||
}
|
||||
|
||||
|
@ -100,7 +101,7 @@ public abstract class ConvexHullGenerator2DAbstractTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testColinearPoints() {
|
||||
public void testCollinearPoints() {
|
||||
final Collection<Vector2D> points = new ArrayList<Vector2D>();
|
||||
points.add(new Vector2D(1, 1));
|
||||
points.add(new Vector2D(2, 2));
|
||||
|
@ -112,6 +113,84 @@ public abstract class ConvexHullGenerator2DAbstractTest {
|
|||
checkConvexHull(points, hull);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCollinearPointsReverse() {
|
||||
final Collection<Vector2D> points = new ArrayList<Vector2D>();
|
||||
points.add(new Vector2D(1, 1));
|
||||
points.add(new Vector2D(2, 2));
|
||||
points.add(new Vector2D(2, 4));
|
||||
points.add(new Vector2D(10, 1));
|
||||
points.add(new Vector2D(4, 1));
|
||||
|
||||
final ConvexHull2D hull = generator.generate(points);
|
||||
checkConvexHull(points, hull);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCollinearPointsIncluded() {
|
||||
final Collection<Vector2D> points = new ArrayList<Vector2D>();
|
||||
points.add(new Vector2D(1, 1));
|
||||
points.add(new Vector2D(2, 2));
|
||||
points.add(new Vector2D(2, 4));
|
||||
points.add(new Vector2D(4, 1));
|
||||
points.add(new Vector2D(10, 1));
|
||||
|
||||
final ConvexHull2D hull = createConvexHullGenerator(true).generate(points);
|
||||
checkConvexHull(points, hull, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCollinearPointsIncludedReverse() {
|
||||
final Collection<Vector2D> points = new ArrayList<Vector2D>();
|
||||
points.add(new Vector2D(1, 1));
|
||||
points.add(new Vector2D(2, 2));
|
||||
points.add(new Vector2D(2, 4));
|
||||
points.add(new Vector2D(10, 1));
|
||||
points.add(new Vector2D(4, 1));
|
||||
|
||||
final ConvexHull2D hull = createConvexHullGenerator(true).generate(points);
|
||||
checkConvexHull(points, hull, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIdenticalPoints() {
|
||||
final Collection<Vector2D> points = new ArrayList<Vector2D>();
|
||||
points.add(new Vector2D(1, 1));
|
||||
points.add(new Vector2D(2, 2));
|
||||
points.add(new Vector2D(2, 4));
|
||||
points.add(new Vector2D(4, 1));
|
||||
points.add(new Vector2D(1, 1));
|
||||
|
||||
final ConvexHull2D hull = generator.generate(points);
|
||||
checkConvexHull(points, hull);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIdenticalPoints2() {
|
||||
final Collection<Vector2D> points = new ArrayList<Vector2D>();
|
||||
points.add(new Vector2D(1, 1));
|
||||
points.add(new Vector2D(2, 2));
|
||||
points.add(new Vector2D(2, 4));
|
||||
points.add(new Vector2D(4, 1));
|
||||
points.add(new Vector2D(1, 1));
|
||||
|
||||
final ConvexHull2D hull = createConvexHullGenerator(true).generate(points);
|
||||
checkConvexHull(points, hull, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClosePoints() {
|
||||
final Collection<Vector2D> points = new ArrayList<Vector2D>();
|
||||
points.add(new Vector2D(1, 1));
|
||||
points.add(new Vector2D(2, 2));
|
||||
points.add(new Vector2D(2, 4));
|
||||
points.add(new Vector2D(4, 1));
|
||||
points.add(new Vector2D(1.00001, 1));
|
||||
|
||||
final ConvexHull2D hull = generator.generate(points);
|
||||
checkConvexHull(points, hull);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------
|
||||
|
||||
protected final List<Vector2D> createRandomPoints(int size) {
|
||||
|
@ -123,15 +202,20 @@ public abstract class ConvexHullGenerator2DAbstractTest {
|
|||
}
|
||||
return points;
|
||||
}
|
||||
|
||||
|
||||
protected final void checkConvexHull(final Collection<Vector2D> points, final ConvexHull2D hull) {
|
||||
checkConvexHull(points, hull, false);
|
||||
}
|
||||
|
||||
protected final void checkConvexHull(final Collection<Vector2D> points, final ConvexHull2D hull,
|
||||
final boolean includesCollinearPoints) {
|
||||
Assert.assertNotNull(hull);
|
||||
Assert.assertTrue(isConvex(hull));
|
||||
checkPointsInsideHullRegion(points, hull);
|
||||
Assert.assertTrue(isConvex(hull, includesCollinearPoints));
|
||||
checkPointsInsideHullRegion(points, hull, includesCollinearPoints);
|
||||
}
|
||||
|
||||
// verify that the constructed hull is really convex
|
||||
protected final boolean isConvex(final ConvexHull2D hull) {
|
||||
protected final boolean isConvex(final ConvexHull2D hull, final boolean includesCollinearPoints) {
|
||||
double sign = 0.0;
|
||||
|
||||
final Vector2D[] points = hull.getVertices();
|
||||
|
@ -142,11 +226,18 @@ public abstract class ConvexHullGenerator2DAbstractTest {
|
|||
|
||||
Vector2D d1 = p2.subtract(p1);
|
||||
Vector2D d2 = p3.subtract(p2);
|
||||
|
||||
|
||||
Assert.assertTrue(d1.getNorm() > 1e-10);
|
||||
Assert.assertTrue(d2.getNorm() > 1e-10);
|
||||
|
||||
double cross = FastMath.signum(d1.getX() * d2.getY() - d1.getY() * d2.getX());
|
||||
|
||||
if (sign != 0.0 && cross != sign) {
|
||||
return false;
|
||||
if (includesCollinearPoints && cross == 0.0) {
|
||||
// in case of collinear points the cross product will be zero
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
sign = cross;
|
||||
|
@ -157,13 +248,19 @@ public abstract class ConvexHullGenerator2DAbstractTest {
|
|||
|
||||
// verify that all points are inside the convex hull region
|
||||
protected final void checkPointsInsideHullRegion(final Collection<Vector2D> points,
|
||||
final ConvexHull2D hull) {
|
||||
final ConvexHull2D hull,
|
||||
final boolean includesCollinearPoints) {
|
||||
|
||||
Region<Euclidean2D> region = hull.createRegion();
|
||||
final Collection<Vector2D> hullVertices = Arrays.asList(hull.getVertices());
|
||||
final Region<Euclidean2D> region = hull.createRegion();
|
||||
|
||||
for (final Vector2D p : points) {
|
||||
Location location = region.checkPoint(p);
|
||||
Assert.assertTrue(location != Location.OUTSIDE);
|
||||
|
||||
if (location == Location.BOUNDARY && includesCollinearPoints) {
|
||||
Assert.assertTrue(hullVertices.contains(p));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package org.apache.commons.math3.geometry.euclidean.twod.hull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.math3.geometry.euclidean.twod.Vector2D;
|
||||
|
@ -30,14 +31,14 @@ import org.junit.Test;
|
|||
public class GrahamScanTest extends ConvexHullGenerator2DAbstractTest {
|
||||
|
||||
@Override
|
||||
protected ConvexHullGenerator2D createConvexHullGenerator() {
|
||||
return new GrahamScan();
|
||||
protected ConvexHullGenerator2D createConvexHullGenerator(boolean includeCollinearPoints) {
|
||||
return new GrahamScan(includeCollinearPoints);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
public void testIdenticalPoints() {
|
||||
public void testIdenticalPointsRandom() {
|
||||
final List<Vector2D> points = new ArrayList<Vector2D>();
|
||||
|
||||
points.add(new Vector2D(0.7886552422, 0.8629523066));
|
||||
|
@ -62,5 +63,18 @@ public class GrahamScanTest extends ConvexHullGenerator2DAbstractTest {
|
|||
final ConvexHull2D hull = generator.generate(points);
|
||||
checkConvexHull(points, hull);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testIdenticalPointsAtStart() {
|
||||
final Collection<Vector2D> points = new ArrayList<Vector2D>();
|
||||
points.add(new Vector2D(1, 1));
|
||||
points.add(new Vector2D(2, 2));
|
||||
points.add(new Vector2D(2, 4));
|
||||
points.add(new Vector2D(4, 1.5));
|
||||
points.add(new Vector2D(1, 1));
|
||||
|
||||
final ConvexHull2D hull = createConvexHullGenerator(true).generate(points);
|
||||
checkConvexHull(points, hull, true);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -23,8 +23,8 @@ package org.apache.commons.math3.geometry.euclidean.twod.hull;
|
|||
public class MonotoneChainTest extends ConvexHullGenerator2DAbstractTest {
|
||||
|
||||
@Override
|
||||
protected ConvexHullGenerator2D createConvexHullGenerator() {
|
||||
return new MonotoneChain();
|
||||
protected ConvexHullGenerator2D createConvexHullGenerator(boolean includeCollinearPoints) {
|
||||
return new MonotoneChain(includeCollinearPoints);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------
|
||||
|
|
Loading…
Reference in New Issue