diff --git a/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/hull/AbstractConvexHullGenerator2D.java b/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/hull/AbstractConvexHullGenerator2D.java
new file mode 100644
index 000000000..84ea2bc6b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/hull/AbstractConvexHullGenerator2D.java
@@ -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.
+ *
+ * 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.
+ *
+ * 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 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 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 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 generateHull(Collection points);
+
+}
diff --git a/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/hull/GrahamScan.java b/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/hull/GrahamScan.java
index dc14f0d87..e8ff892e1 100644
--- a/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/hull/GrahamScan.java
+++ b/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/hull/GrahamScan.java
@@ -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.
*
- * 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.
+ *
+ * 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 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 generateHull(final Collection points) {
final Vector2D referencePoint = getReferencePoint(points);
- final List pointsSortedByAngle = new ArrayList();
+ final List pointsSortedByAngle = new ArrayList(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 hullVertices = new ArrayList(points.size());
+ // list containing the vertices of the hull in CCW direction
+ final List hullVertices = new ArrayList();
// push the first two points on the stack
final Iterator 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 points) {
+ private Vector2D getReferencePoint(final Iterable points) {
Vector2D minY = null;
for (final Vector2D p : points) {
if (minY == null) {
diff --git a/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/hull/MonotoneChain.java b/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/hull/MonotoneChain.java
index cfd2f49c7..0e092d875 100644
--- a/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/hull/MonotoneChain.java
+++ b/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/hull/MonotoneChain.java
@@ -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.
*
- * 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.
+ *
+ * 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 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 generateHull(final Collection points) {
final List pointsSortedByXAxis = new ArrayList(points);
@@ -92,41 +93,19 @@ public class MonotoneChain implements ConvexHullGenerator2D {
// build lower hull
final List lowerHull = new ArrayList();
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 upperHull = new ArrayList();
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 hullVertices = new ArrayList(lowerHull.size() + upperHull.size() - 2);
+ final List hullVertices = new ArrayList(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 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);
}
}
diff --git a/src/test/java/org/apache/commons/math3/geometry/euclidean/twod/hull/AklToussaintHeuristicTest.java b/src/test/java/org/apache/commons/math3/geometry/euclidean/twod/hull/AklToussaintHeuristicTest.java
index d5cfa27e2..e2968732f 100644
--- a/src/test/java/org/apache/commons/math3/geometry/euclidean/twod/hull/AklToussaintHeuristicTest.java
+++ b/src/test/java/org/apache/commons/math3/geometry/euclidean/twod/hull/AklToussaintHeuristicTest.java
@@ -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
diff --git a/src/test/java/org/apache/commons/math3/geometry/euclidean/twod/hull/ConvexHullGenerator2DAbstractTest.java b/src/test/java/org/apache/commons/math3/geometry/euclidean/twod/hull/ConvexHullGenerator2DAbstractTest.java
index 8c9d7a2a3..94557be95 100644
--- a/src/test/java/org/apache/commons/math3/geometry/euclidean/twod/hull/ConvexHullGenerator2DAbstractTest.java
+++ b/src/test/java/org/apache/commons/math3/geometry/euclidean/twod/hull/ConvexHullGenerator2DAbstractTest.java
@@ -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 reducePoints(Collection 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 points = new ArrayList();
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 points = new ArrayList();
+ 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 points = new ArrayList();
+ 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 points = new ArrayList();
+ 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 points = new ArrayList();
+ 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 points = new ArrayList();
+ 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 points = new ArrayList();
+ 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 createRandomPoints(int size) {
@@ -123,15 +202,20 @@ public abstract class ConvexHullGenerator2DAbstractTest {
}
return points;
}
-
+
protected final void checkConvexHull(final Collection points, final ConvexHull2D hull) {
+ checkConvexHull(points, hull, false);
+ }
+
+ protected final void checkConvexHull(final Collection 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 points,
- final ConvexHull2D hull) {
+ final ConvexHull2D hull,
+ final boolean includesCollinearPoints) {
- Region region = hull.createRegion();
+ final Collection hullVertices = Arrays.asList(hull.getVertices());
+ final Region 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));
+ }
}
}
}
diff --git a/src/test/java/org/apache/commons/math3/geometry/euclidean/twod/hull/GrahamScanTest.java b/src/test/java/org/apache/commons/math3/geometry/euclidean/twod/hull/GrahamScanTest.java
index f0a4fe7c1..f8b868340 100644
--- a/src/test/java/org/apache/commons/math3/geometry/euclidean/twod/hull/GrahamScanTest.java
+++ b/src/test/java/org/apache/commons/math3/geometry/euclidean/twod/hull/GrahamScanTest.java
@@ -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 points = new ArrayList();
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 points = new ArrayList();
+ 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);
+ }
+
}
diff --git a/src/test/java/org/apache/commons/math3/geometry/euclidean/twod/hull/MonotoneChainTest.java b/src/test/java/org/apache/commons/math3/geometry/euclidean/twod/hull/MonotoneChainTest.java
index 78f6c0eff..ecab68253 100644
--- a/src/test/java/org/apache/commons/math3/geometry/euclidean/twod/hull/MonotoneChainTest.java
+++ b/src/test/java/org/apache/commons/math3/geometry/euclidean/twod/hull/MonotoneChainTest.java
@@ -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);
}
// ------------------------------------------------------------------------------