[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:
parent
39430886ba
commit
e8d0d4c1dd
|
@ -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">
|
||||
|
|
|
@ -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}"),
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ public class AklToussaintHeuristicTest extends ConvexHullGenerator2DAbstractTest
|
|||
|
||||
@Override
|
||||
protected ConvexHullGenerator2D createConvexHullGenerator(boolean includeCollinearPoints) {
|
||||
return new GrahamScan(includeCollinearPoints);
|
||||
return new MonotoneChain(includeCollinearPoints);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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()) {
|
||||
|
|
Loading…
Reference in New Issue