[MATH-749] Remove GrahamScan, GiftWrap, make MonotoneChain more robust wrt collinear points, add ConvergenceException in case the specified tolerance results in a non-convex hull.

git-svn-id: https://svn.apache.org/repos/asf/commons/proper/math/trunk@1568752 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Thomas Neidhart 2014-02-16 12:19:51 +00:00
parent 39430886ba
commit e8d0d4c1dd
15 changed files with 162 additions and 509 deletions

View File

@ -52,10 +52,8 @@ If the output is not quite correct, check for invisible trailing spaces!
<body>
<release version="3.3" date="TBD" description="TBD">
<action dev="tn" type="add" issue="MATH-749">
Added algorithms to compute the convex hull of a collection of points in 2D.
The implemented algorithms can be found in package
o.a.c.m.geometry.euclidean.twod.hull and include GrahamScan, MonotoneChain
and GiftWrap. Additionally, the AklToussaintHeuristic can be used to speed up
Added MonotoneChain algorithm to compute the convex hull of a collection of
points in 2D. Additionally, the AklToussaintHeuristic can be used to speed up
the generation.
</action>
<action dev="tn" type="fix" issue="MATH-1065" due-to="matteodg">

View File

@ -176,6 +176,7 @@ public enum LocalizedFormats implements Localizable {
NEGATIVE_NUMBER_OF_TRIALS("number of trials must be non-negative ({0})"),
NUMBER_OF_INTERPOLATION_POINTS("number of interpolation points ({0})"), /* keep */
NUMBER_OF_TRIALS("number of trials ({0})"),
NOT_CONVEX("vertices do not form a convex hull in CCW winding"),
ROBUSTNESS_ITERATIONS("number of robustness iterations ({0})"),
START_POSITION("start position ({0})"), /* keep */
NON_CONVERGENT_CONTINUED_FRACTION("Continued fraction convergents failed to converge (in less than {0} iterations) for value {1}"),

View File

@ -18,6 +18,8 @@ package org.apache.commons.math3.geometry.euclidean.twod.hull;
import java.util.Collection;
import org.apache.commons.math3.exception.ConvergenceException;
import org.apache.commons.math3.exception.MathIllegalArgumentException;
import org.apache.commons.math3.exception.NullArgumentException;
import org.apache.commons.math3.geometry.euclidean.twod.Vector2D;
import org.apache.commons.math3.util.MathUtils;
@ -28,7 +30,7 @@ import org.apache.commons.math3.util.MathUtils;
* @since 3.3
* @version $Id$
*/
public abstract class AbstractConvexHullGenerator2D implements ConvexHullGenerator2D {
abstract class AbstractConvexHullGenerator2D implements ConvexHullGenerator2D {
/** Default value for tolerance. */
private static final double DEFAULT_TOLERANCE = 1e-10;
@ -84,16 +86,25 @@ public abstract class AbstractConvexHullGenerator2D implements ConvexHullGenerat
}
/** {@inheritDoc} */
public ConvexHull2D generate(final Collection<Vector2D> points) throws NullArgumentException {
public ConvexHull2D generate(final Collection<Vector2D> points)
throws NullArgumentException, ConvergenceException {
// check for null points
MathUtils.checkNotNull(points);
Collection<Vector2D> hullVertices = null;
if (points.size() < 2) {
return new ConvexHull2D(points, tolerance);
hullVertices = points;
} else {
hullVertices = findHullVertices(points);
}
final Collection<Vector2D> hullVertices = findHullVertices(points);
return new ConvexHull2D(hullVertices, tolerance);
try {
return new ConvexHull2D(hullVertices.toArray(new Vector2D[hullVertices.size()]),
tolerance);
} catch (MathIllegalArgumentException e) {
// the hull vertices may not form a convex hull if the tolerance value is to large
throw new ConvergenceException();
}
}
/**

View File

@ -17,9 +17,10 @@
package org.apache.commons.math3.geometry.euclidean.twod.hull;
import java.io.Serializable;
import java.util.Collection;
import org.apache.commons.math3.exception.InsufficientDataException;
import org.apache.commons.math3.exception.MathIllegalArgumentException;
import org.apache.commons.math3.exception.util.LocalizedFormats;
import org.apache.commons.math3.geometry.euclidean.twod.Euclidean2D;
import org.apache.commons.math3.geometry.euclidean.twod.Line;
import org.apache.commons.math3.geometry.euclidean.twod.Segment;
@ -27,6 +28,8 @@ import org.apache.commons.math3.geometry.euclidean.twod.Vector2D;
import org.apache.commons.math3.geometry.hull.ConvexHull;
import org.apache.commons.math3.geometry.partitioning.Region;
import org.apache.commons.math3.geometry.partitioning.RegionFactory;
import org.apache.commons.math3.util.FastMath;
import org.apache.commons.math3.util.MathArrays;
/**
* This class represents a convex hull in an two-dimensional euclidean space.
@ -53,21 +56,61 @@ public class ConvexHull2D implements ConvexHull<Euclidean2D, Vector2D>, Serializ
/**
* Simple constructor.
* @param vertices the vertices of the convex hull, must be ordered in CCW winding
* @param vertices the vertices of the convex hull, must be ordered
* @param tolerance tolerance below which points are considered identical
* @throws MathIllegalArgumentException if the vertices do not form a convex hull
*/
ConvexHull2D(final Collection<Vector2D> vertices, final double tolerance) {
this.vertices = vertices.toArray(new Vector2D[vertices.size()]);
public ConvexHull2D(final Vector2D[] vertices, final double tolerance)
throws MathIllegalArgumentException {
if (!isConvex(vertices)) {
throw new MathIllegalArgumentException(LocalizedFormats.NOT_CONVEX);
}
this.vertices = vertices.clone();
this.tolerance = tolerance;
}
/**
* Checks whether the given hull vertices form a convex hull.
* @param hullVertices the hull vertices
* @return {@code true} if the vertices form a convex hull, {@code false} otherwise
*/
private boolean isConvex(final Vector2D[] hullVertices) {
if (hullVertices.length < 3) {
return true;
}
double sign = 0.0;
for (int i = 0; i < hullVertices.length; i++) {
final Vector2D p1 = hullVertices[i == 0 ? hullVertices.length - 1 : i - 1];
final Vector2D p2 = hullVertices[i];
final Vector2D p3 = hullVertices[i == hullVertices.length - 1 ? 0 : i + 1];
final Vector2D d1 = p2.subtract(p1);
final Vector2D d2 = p3.subtract(p2);
final double cross = FastMath.signum(MathArrays.linearCombination( d1.getX(), d2.getY(),
-d1.getY(), d2.getX()));
// in case of collinear points the cross product will be zero
if (cross != 0.0) {
if (sign != 0.0 && cross != sign) {
return false;
}
sign = cross;
}
}
return true;
}
/** {@inheritDoc} */
public Vector2D[] getVertices() {
return vertices.clone();
}
/**
* Get the line segments of the convex hull, ordered in CCW winding.
* Get the line segments of the convex hull, ordered.
* @return the line segments of the convex hull
*/
public Segment[] getLineSegments() {

View File

@ -18,6 +18,7 @@ package org.apache.commons.math3.geometry.euclidean.twod.hull;
import java.util.Collection;
import org.apache.commons.math3.exception.ConvergenceException;
import org.apache.commons.math3.exception.NullArgumentException;
import org.apache.commons.math3.geometry.euclidean.twod.Euclidean2D;
import org.apache.commons.math3.geometry.euclidean.twod.Vector2D;
@ -32,6 +33,6 @@ import org.apache.commons.math3.geometry.hull.ConvexHullGenerator;
public interface ConvexHullGenerator2D extends ConvexHullGenerator<Euclidean2D, Vector2D> {
/** {@inheritDoc} */
ConvexHull2D generate(Collection<Vector2D> points) throws NullArgumentException;
ConvexHull2D generate(Collection<Vector2D> points) throws NullArgumentException, ConvergenceException;
}

View File

@ -1,138 +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.commons.math3.geometry.euclidean.twod.hull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.math3.geometry.euclidean.twod.Vector2D;
import org.apache.commons.math3.util.FastMath;
/**
* Implements the Gift wrapping algorithm to generate the convex hull of a finite set of
* points in the two-dimensional euclidean space.
* <p>
* The runtime complexity is O(nh), with n being the number of input points and h the number
* of points on the convex hull.
* <p>
* The implementation is not sensitive to collinear points on the hull. The parameter
* {@code includeCollinearPoints} allows to control the behavior with regard to collinear points.
* If {@code true}, all points on the boundary of the hull will be added to the hull vertices,
* otherwise only the extreme points will be present. By default, collinear points are not added
* as hull vertices.
* <p>
* The {@code tolerance} parameter (default: 1e-10) is used as epsilon criteria to determine
* identical and collinear points.
*
* @see <a href="http://en.wikipedia.org/wiki/Gift_wrapping_algorithm">Gift wrapping algorithm (Wikipedia)</a>
* @since 3.3
* @version $Id$
*/
public class GiftWrap extends AbstractConvexHullGenerator2D {
/**
* Create a new GiftWrap instance.
*/
public GiftWrap() {
this(false);
}
/**
* Create a new GiftWrap instance.
* @param includeCollinearPoints whether collinear points shall be added as hull vertices
*/
public GiftWrap(final boolean includeCollinearPoints) {
super(includeCollinearPoints);
}
/**
* Create a new GiftWrap instance.
* @param includeCollinearPoints whether collinear points shall be added as hull vertices
* @param tolerance tolerance below which points are considered identical
*/
public GiftWrap(final boolean includeCollinearPoints, final double tolerance) {
super(includeCollinearPoints, tolerance);
}
@Override
public Collection<Vector2D> findHullVertices(final Collection<Vector2D> points) {
final double tolerance = getTolerance();
final List<Vector2D> hullVertices = new ArrayList<Vector2D>();
Vector2D pointOnHull = selectLeftMostPoint(points);
Vector2D nextPoint;
do {
hullVertices.add(pointOnHull);
final Iterator<Vector2D> it = points.iterator();
nextPoint = null;
while(it.hasNext() && nextPoint == null) {
final Vector2D point = it.next();
if (pointOnHull.distance(point) >= tolerance) {
nextPoint = point;
}
}
while (it.hasNext()) {
final Vector2D p = it.next();
final double location = p.crossProduct(pointOnHull, nextPoint);
if (FastMath.abs(location) < tolerance) {
final double distanceToCurrent = pointOnHull.distance(p);
if (distanceToCurrent < tolerance || nextPoint.distance(p) < tolerance) {
// the point is assumed to be identical to either of pointOnHull or nextPoint
continue;
}
final double distanceToNext = pointOnHull.distance(nextPoint);
if (isIncludeCollinearPoints()) {
if (distanceToCurrent < distanceToNext) {
nextPoint = p;
}
} else {
if (distanceToCurrent > distanceToNext) {
nextPoint = p;
}
}
} else if (location < 0.0) {
nextPoint = p;
}
}
pointOnHull = nextPoint;
} while (nextPoint != hullVertices.get(0) && nextPoint != null);
return hullVertices;
}
/**
* Selects the left-most point (minimum x-coordinate) from the set of points.
* @param points the set of points
* @return the left-most point
*/
private Vector2D selectLeftMostPoint(final Collection<Vector2D> points) {
Vector2D leftMost = null;
for (final Vector2D p : points) {
if (leftMost == null || p.getX() < leftMost.getX()) {
leftMost = p;
}
}
return leftMost;
}
}

View File

@ -1,224 +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.commons.math3.geometry.euclidean.twod.hull;
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.geometry.euclidean.twod.Vector2D;
import org.apache.commons.math3.util.FastMath;
/**
* Implements Graham's scan method to generate the convex hull of a finite set of
* points in the two-dimensional euclidean space.
* <p>
* The runtime complexity is O(n log h), with n being the number of input points.
* <p>
* The implementation is not sensitive to collinear points on the hull. The parameter
* {@code includeCollinearPoints} allows to control the behavior with regard to collinear points.
* If {@code true}, all points on the boundary of the hull will be added to the hull vertices,
* otherwise only the extreme points will be present. By default, collinear points are not added
* as hull vertices.
* <p>
* The {@code tolerance} parameter (default: 1e-10) is used as epsilon criteria to determine
* identical and collinear points.
*
* @see <a href="http://en.wikipedia.org/wiki/Graham_scan">Graham's scan algorithm (Wikipedia)</a>
* @since 3.3
* @version $Id$
*/
public class GrahamScan extends AbstractConvexHullGenerator2D {
/** Vector representing the x-axis. */
private static final Vector2D X_AXIS = new Vector2D(1.0, 0.0);
/**
* Create a new GrahamScan instance.
*/
public GrahamScan() {
this(false);
}
/**
* Create a new GrahamScan instance.
* @param includeCollinearPoints whether collinear points shall be added as hull vertices
*/
public GrahamScan(final boolean includeCollinearPoints) {
super(includeCollinearPoints);
}
/**
* Create a new GrahamScan instance.
* @param includeCollinearPoints whether collinear points 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);
}
@Override
protected Collection<Vector2D> findHullVertices(final Collection<Vector2D> points) {
final Vector2D referencePoint = getReferencePoint(points);
final List<Vertex> pointsSortedByAngle = new ArrayList<Vertex>(points.size());
for (final Vector2D p : points) {
pointsSortedByAngle.add(new Vertex(p, getAngleBetweenPoints(p, referencePoint)));
}
// sort the points in increasing order of their angles
Collections.sort(pointsSortedByAngle, new Comparator<Vertex>() {
public int compare(final Vertex o1, final Vertex o2) {
return (int) FastMath.signum(o2.angle - o1.angle);
}
});
// 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();
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) {
// push the current point to form a line segment if there is only one element
final int size = hullVertices.size();
if (size == 1) {
hullVertices.add(currentPoint != null ? currentPoint : it.next().point);
currentPoint = null;
continue;
}
// get the last line segment of the current convex hull
final Vector2D p1 = hullVertices.get(size - 2);
final Vector2D p2 = hullVertices.get(size - 1);
if (currentPoint == null) {
currentPoint = it.next().point;
}
// test if the current point is to the left of the line
final double offset = currentPoint.crossProduct(p1, p2);
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 {
// the current point creates a concave polygon, remove the last point
hullVertices.remove(size - 1);
}
}
return hullVertices;
}
/**
* Returns the point with the lowest y-coordinate.
* <p>
* In case of a tie, the point with the lowest x-coordinate from the set of
* candidates is chosen.
*
* @param points the point set
* @return the point with the lowest y-coordinate
*/
private Vector2D getReferencePoint(final Iterable<Vector2D> points) {
Vector2D minY = null;
for (final Vector2D p : points) {
if (minY == null) {
minY = p;
} else if (p.getY() < minY.getY()) {
minY = p;
} else if (p.getY() == minY.getY() && p.getX() < minY.getX()) {
minY = p;
}
}
return minY;
}
/**
* Returns the smallest angle between the given two vectors along the x-axis.
* @param v1 the first vector
* @param v2 the second vector
* @return angle in radians in the range [-pi, pi]
*/
private double getAngleBetweenPoints(final Vector2D v1, final Vector2D v2) {
final Vector2D p1 = v1.subtract(v2);
final Vector2D p2 = X_AXIS;
return FastMath.atan2(p2.getY(), p2.getX()) - FastMath.atan2(p1.getY(), p1.getX());
}
/**
* A helper class used for sorting the points.
*/
private static class Vertex {
/** the point */
private final Vector2D point;
/** the angle */
private final double angle;
/**
* Create a new Vertex.
* @param point the point
* @param angle the angle
*/
public Vertex(final Vector2D point, final double angle) {
this.point = point;
this.angle = angle;
}
}
}

View File

@ -22,6 +22,7 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.List;
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;
@ -142,8 +143,7 @@ public class MonotoneChain extends AbstractConvexHullGenerator2D {
final Vector2D p1 = hull.get(size - 2);
final Vector2D p2 = hull.get(size - 1);
final double offset = point.crossProduct(p1, p2);
final double offset = new Line(p1, p2, tolerance).getOffset(point);
if (FastMath.abs(offset) < tolerance) {
// the point is collinear to the line (p1, p2)
@ -164,7 +164,7 @@ public class MonotoneChain extends AbstractConvexHullGenerator2D {
hull.add(point);
}
return;
} else if (offset < 0) {
} else if (offset > 0) {
hull.remove(size - 1);
} else {
break;

View File

@ -18,6 +18,7 @@ package org.apache.commons.math3.geometry.hull;
import java.util.Collection;
import org.apache.commons.math3.exception.ConvergenceException;
import org.apache.commons.math3.exception.NullArgumentException;
import org.apache.commons.math3.geometry.Point;
import org.apache.commons.math3.geometry.Space;
@ -42,6 +43,8 @@ public interface ConvexHullGenerator<S extends Space, P extends Point<S>> {
* @param points the set of input points
* @return the convex hull
* @throws NullArgumentException if the input collection is {@code null}
* @throws ConvergenceException if generator fails to generate a convex hull for
* the given set of input points
*/
ConvexHull<S, P> generate(Collection<P> points) throws NullArgumentException;
ConvexHull<S, P> generate(Collection<P> points) throws NullArgumentException, ConvergenceException;
}

View File

@ -28,7 +28,7 @@ public class AklToussaintHeuristicTest extends ConvexHullGenerator2DAbstractTest
@Override
protected ConvexHullGenerator2D createConvexHullGenerator(boolean includeCollinearPoints) {
return new GrahamScan(includeCollinearPoints);
return new MonotoneChain(includeCollinearPoints);
}
@Override

View File

@ -231,9 +231,6 @@ public abstract class ConvexHullGenerator2DAbstractTest {
double sign = 0.0;
final Vector2D[] points = hull.getVertices();
// if (points.length < 3) {
// return true;
// }
for (int i = 0; i < points.length; i++) {
Vector2D p1 = points[i == 0 ? points.length - 1 : i - 1];
@ -268,9 +265,6 @@ public abstract class ConvexHullGenerator2DAbstractTest {
final boolean includesCollinearPoints) {
final Collection<Vector2D> hullVertices = Arrays.asList(hull.getVertices());
// if (hullVertices.size() < 3) {
// return;
// }
final Region<Euclidean2D> region = hull.createRegion();
for (final Vector2D p : points) {

View File

@ -1,32 +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.commons.math3.geometry.euclidean.twod.hull;
/**
* Test class for MonotoneChain.
* @version $Id$
*/
public class GiftWrapTest extends ConvexHullGenerator2DAbstractTest {
@Override
protected ConvexHullGenerator2D createConvexHullGenerator(boolean includeCollinearPoints) {
return new GiftWrap(includeCollinearPoints);
}
// ------------------------------------------------------------------------------
}

View File

@ -1,80 +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.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;
import org.apache.commons.math3.geometry.euclidean.twod.hull.GrahamScan;
import org.junit.Test;
/**
* Test class for GrahamScan2D.
* @version $Id$
*/
public class GrahamScanTest extends ConvexHullGenerator2DAbstractTest {
@Override
protected ConvexHullGenerator2D createConvexHullGenerator(boolean includeCollinearPoints) {
return new GrahamScan(includeCollinearPoints);
}
// ------------------------------------------------------------------------------
@Test
public void testIdenticalPointsRandom() {
final List<Vector2D> points = new ArrayList<Vector2D>();
points.add(new Vector2D(0.7886552422, 0.8629523066));
points.add(new Vector2D(-0.477657659, -0.818633147));
points.add(new Vector2D(-0.9778256822, 0.4459975439));
points.add(new Vector2D(0.9967680357, -0.7956341096));
points.add(new Vector2D(-0.6644522529, 0.5722968681));
points.add(new Vector2D(-0.9769155504, 0.2676854695));
points.add(new Vector2D(0.2378256814, -0.0546758337));
points.add(new Vector2D(-0.3094786038, -0.4877828777));
points.add(new Vector2D(0.4700714363, 0.2338673804));
points.add(new Vector2D(0.1172690966, -0.5931228134));
points.add(new Vector2D(-0.8863820898, 0.630175898));
points.add(new Vector2D(0.9967680357, -0.7956341096));
points.add(new Vector2D(0.5974682835, 0.5581237347));
points.add(new Vector2D(0.0670101247, 0.523515029));
points.add(new Vector2D(-0.0534546034, -0.608353757));
points.add(new Vector2D(-0.3527909285, 0.4330755698));
points.add(new Vector2D(0.6524149298, -0.6353437037));
points.add(new Vector2D(-0.7189115058, -0.3074849638));
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);
}
}

View File

@ -16,6 +16,13 @@
*/
package org.apache.commons.math3.geometry.euclidean.twod.hull;
import java.util.ArrayList;
import java.util.Collection;
import org.apache.commons.math3.exception.ConvergenceException;
import org.apache.commons.math3.geometry.euclidean.twod.Vector2D;
import org.junit.Test;
/**
* Test class for MonotoneChain.
* @version $Id$
@ -29,4 +36,21 @@ public class MonotoneChainTest extends ConvexHullGenerator2DAbstractTest {
// ------------------------------------------------------------------------------
@Test(expected=ConvergenceException.class)
public void testConvergenceException() {
final Collection<Vector2D> points = new ArrayList<Vector2D>();
points.add(new Vector2D(1, 1));
points.add(new Vector2D(1, 5));
points.add(new Vector2D(0, 7));
points.add(new Vector2D(1, 10));
points.add(new Vector2D(1, 20));
points.add(new Vector2D(20, 20));
points.add(new Vector2D(20, 40));
points.add(new Vector2D(40, 1));
@SuppressWarnings("unused")
final ConvexHull2D hull = new MonotoneChain(true, 1).generate(points);
}
}

View File

@ -38,7 +38,6 @@ import org.apache.commons.math3.geometry.euclidean.twod.DiskGenerator;
import org.apache.commons.math3.geometry.euclidean.twod.Euclidean2D;
import org.apache.commons.math3.geometry.euclidean.twod.Segment;
import org.apache.commons.math3.geometry.euclidean.twod.Vector2D;
import org.apache.commons.math3.geometry.euclidean.twod.hull.AklToussaintHeuristic;
import org.apache.commons.math3.geometry.euclidean.twod.hull.ConvexHull2D;
import org.apache.commons.math3.geometry.euclidean.twod.hull.ConvexHullGenerator2D;
import org.apache.commons.math3.geometry.euclidean.twod.hull.MonotoneChain;
@ -60,11 +59,8 @@ import org.piccolo2d.nodes.PText;
* Simple example illustrating some parts of the geometry package.
*
* TODO:
* - add user interface to re-generate points
* - select convex hull algorithm
* - select tolerance level
* - allow editing of the point set
* - pre-defined shapes, e.g. circle, cross, ...
*/
public class GeometryExample {
@ -78,6 +74,31 @@ public class GeometryExample {
points.add(new Vector2D(FastMath.round(random.nextDouble() * 400 + 100),
FastMath.round(random.nextDouble() * 400 + 100)));
}
return points;
}
public static List<Vector2D> createCircle(int samples) {
List<Vector2D> points = new ArrayList<Vector2D>();
final Vector2D center = new Vector2D(300, 300);
double range = 2.0 * FastMath.PI;
double step = range / (samples + 1);
for (double angle = 0; angle < range; angle += step) {
Vector2D circle = new Vector2D(FastMath.cos(angle), FastMath.sin(angle));
points.add(circle.scalarMultiply(200).add(center));
}
return points;
}
public static List<Vector2D> createCross() {
List<Vector2D> points = new ArrayList<Vector2D>();
for (int i = 100; i < 500; i += 10) {
points.add(new Vector2D(300, i));
points.add(new Vector2D(i, 300));
}
return points;
}
@ -128,6 +149,7 @@ public class GeometryExample {
@SuppressWarnings("serial")
public static class Display extends ExampleFrame {
private List<Vector2D> points;
private PCanvas canvas;
private JComponent container;
private JComponent controlPanel;
@ -150,7 +172,37 @@ public class GeometryExample {
@Override
public void actionPerformed(ActionEvent e) {
canvas.getLayer().removeAllChildren();
createAndPaintRandomCloud();
points = createRandomPoints(1000);
paintConvexHull();
}
});
JButton circle = new JButton("Circle");
controlPanel.add(circle);
circle.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
canvas.getLayer().removeAllChildren();
points = createCircle(100);
paintConvexHull();
}
});
JButton cross = new JButton("Cross");
controlPanel.add(cross);
cross.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
canvas.getLayer().removeAllChildren();
points = createCross();
paintConvexHull();
}
});
@ -159,7 +211,8 @@ public class GeometryExample {
add(splitpane);
createAndPaintRandomCloud();
points = createRandomPoints(1000);
paintConvexHull();
}
@Override
@ -167,8 +220,7 @@ public class GeometryExample {
return container;
}
public void createAndPaintRandomCloud() {
List<Vector2D> points = createRandomPoints(1000);
public void paintConvexHull() {
PNode pointSet = new PNode();
for (Vector2D point : points) {
final PNode node = PPath.createEllipse(point.getX() - 1, point.getY() - 1, 2, 2);
@ -180,7 +232,7 @@ public class GeometryExample {
canvas.getLayer().addChild(pointSet);
ConvexHullGenerator2D generator = new MonotoneChain(true, 1e-6);
ConvexHull2D hull = generator.generate(AklToussaintHeuristic.reducePoints(points));
ConvexHull2D hull = generator.generate(points); //AklToussaintHeuristic.reducePoints(points));
PNode hullNode = new PNode();
for (Vector2D vertex : hull.getVertices()) {