blob: 15be6624821e07ffcc2bf7227056b912efd59b0a [file] [log] [blame]
/*
* 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.geometry.hull.euclidean.twod;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.apache.commons.geometry.euclidean.twod.Vector2D;
/**
* A simple heuristic to improve the performance of convex hull algorithms.
* <p>
* The heuristic is based on the idea of a convex quadrilateral, which is formed by
* four points with the lowest and highest x / y coordinates. Any point that lies inside
* this quadrilateral can not be part of the convex hull and can thus be safely discarded
* before generating the convex hull itself.
* <p>
* The complexity of the operation is O(n), and may greatly improve the time it takes to
* construct the convex hull afterwards, depending on the point distribution.
*
* @see <a href="http://en.wikipedia.org/wiki/Convex_hull_algorithms#Akl-Toussaint_heuristic">
* Akl-Toussaint heuristic (Wikipedia)</a>
*/
public final class AklToussaintHeuristic {
/** Hide utility constructor. */
private AklToussaintHeuristic() {
}
/**
* Returns a point set that is reduced by all points for which it is safe to assume
* that they are not part of the convex hull.
*
* @param points the original point set
* @return a reduced point set, useful as input for convex hull algorithms
*/
public static Collection<Vector2D> reducePoints(final Collection<Vector2D> points) {
// find the leftmost point
int size = 0;
Vector2D minX = null;
Vector2D maxX = null;
Vector2D minY = null;
Vector2D maxY = null;
for (final Vector2D p : points) {
if (minX == null || p.getX() < minX.getX()) {
minX = p;
}
if (maxX == null || p.getX() > maxX.getX()) {
maxX = p;
}
if (minY == null || p.getY() < minY.getY()) {
minY = p;
}
if (maxY == null || p.getY() > maxY.getY()) {
maxY = p;
}
size++;
}
if (size < 4) {
return points;
}
final List<Vector2D> quadrilateral = buildQuadrilateral(minY, maxX, maxY, minX);
// if the quadrilateral is not well formed, e.g. only 2 points, do not attempt to reduce
if (quadrilateral.size() < 3) {
return points;
}
final List<Vector2D> reducedPoints = new ArrayList<>(quadrilateral);
for (final Vector2D p : points) {
// check all points if they are within the quadrilateral
// in which case they can not be part of the convex hull
if (!insideQuadrilateral(p, quadrilateral)) {
reducedPoints.add(p);
}
}
return reducedPoints;
}
/**
* Build the convex quadrilateral with the found corner points (with min/max x/y coordinates).
*
* @param points the respective points with min/max x/y coordinate
* @return the quadrilateral
*/
private static List<Vector2D> buildQuadrilateral(final Vector2D... points) {
final List<Vector2D> quadrilateral = new ArrayList<>();
for (final Vector2D p : points) {
if (!quadrilateral.contains(p)) {
quadrilateral.add(p);
}
}
return quadrilateral;
}
/**
* Checks if the given point is located within the convex quadrilateral.
* @param point the point to check
* @param quadrilateralPoints the convex quadrilateral, represented by 4 points
* @return {@code true} if the point is inside the quadrilateral, {@code false} otherwise
*/
private static boolean insideQuadrilateral(final Vector2D point,
final List<? extends Vector2D> quadrilateralPoints) {
Vector2D v1 = quadrilateralPoints.get(0);
Vector2D v2 = quadrilateralPoints.get(1);
if (point.equals(v1) || point.equals(v2)) {
return true;
}
// get the location of the point relative to the first two vertices
final double last = signedAreaPoints(v1, v2, point);
final int size = quadrilateralPoints.size();
// loop through the rest of the vertices
for (int i = 1; i < size; i++) {
v1 = v2;
v2 = quadrilateralPoints.get((i + 1) == size ? 0 : i + 1);
if (point.equals(v2)) {
return true;
}
// do side of line test: multiply the last location with this location
// if they are the same sign then the operation will yield a positive result
// -x * -y = +xy, x * y = +xy, -x * y = -xy, x * -y = -xy
if (last * signedAreaPoints(v1, v2, point) < 0) {
return false;
}
}
return true;
}
/** Compute the signed area of the parallelogram formed by vectors between the given points. The first
* vector points from {@code p0} to {@code p1} and the second from {@code p0} to {@code p3}.
* @param p0 first point
* @param p1 second point
* @param p2 third point
* @return signed area of parallelogram formed by vectors between the given points
*/
private static double signedAreaPoints(final Vector2D p0, final Vector2D p1, final Vector2D p2) {
return p0.vectorTo(p1).signedArea(p0.vectorTo(p2));
}
}