All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.djutils.draw.line.ConvexHull Maven / Gradle / Ivy

The newest version!
package org.djutils.draw.line;

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.djutils.draw.DrawRuntimeException;
import org.djutils.draw.Drawable2d;
import org.djutils.draw.bounds.Bounds2d;
import org.djutils.draw.point.Point2d;
import org.djutils.exceptions.Throw;

/**
 * ConvexHull.java. Compute the convex hull of a collection of Point2d or Drawable2d. All implementations here return a
 * Polygon2d object. If the convex hull of the input would be a single point, the implementations will throw a
 * DrawRuntimeException because a single point does not make a valid Polygon2d.
 * 

* Copyright (c) 2020-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
* BSD-style license. See DJUTILS License. *

* @author Alexander Verbraeck * @author Peter Knoppers */ public final class ConvexHull { /** * Do not instantiate. */ private ConvexHull() { // Do not instantiate } /** * Compute the convex hull of a collection of Point2d objects. * @param iterator Iterator<Point2d>; iterator that shall return all the points for which the convex hull is to be * computed * @return Polygon2d; the convex hull of the points */ public static Polygon2d convexHull(final Iterator iterator) { List list = new ArrayList<>(); iterator.forEachRemaining(list::add); return convexHullAlshamrani(list); } /** * Compute the convex hull of one or more Drawable2d objects. * @param drawable2d Drawable2d...; the Drawable2d objects * @return Polygon2d; the convex hull of the Drawable2d objects * @throws NullPointerException when any of the drawable2d object is null * @throws IllegalArgumentException when zero arguments are provided */ public static Polygon2d convexHull(final Drawable2d... drawable2d) throws NullPointerException, IllegalArgumentException { return convexHull(Bounds2d.pointsOf(drawable2d)); } /** * Construct a Bounds2d for a Collection of Drawable2d objects. * @param drawableCollection Collection<Drawable2d>; the collection * @return Polygon2d; the convex hull of the Drawable2d objects * @throws NullPointerException when the collection is null, or contains null values * @throws IllegalArgumentException when the collection is empty */ public static Polygon2d convexHull(final Collection drawableCollection) throws NullPointerException, IllegalArgumentException { Throw.whenNull(drawableCollection, "drawableCollection"); Throw.when(drawableCollection.isEmpty(), DrawRuntimeException.class, "drawableCollection may not be empty"); return convexHull(Bounds2d.pointsOf(drawableCollection)); } /** * Compute the convex hull of a list of Point2d objects. The input list will not be modified. * @param list List<Point2d>; the list of Point2d objects * @return Polygon2d; the convex hull of the points */ public static Polygon2d convexHull(final List list) { return convexHullAlshamrani(list); } /** * Return whether moving from a through b to c, the turn at b is counter-clockwise. * @param a Point2d; point a * @param b Point2d; point b * @param c Point2d; point c * @return boolean; true if the turn at b is counter clockwise; false if there is not turn; or it is clockwise */ private static boolean ccw(final Point2d a, final Point2d b, final Point2d c) { // System.out.println("left " + ((b.x - a.x) * (c.y - a.y)) + ", right " + ((b.y - a.y) * (c.x - a.x))); return ((b.x - a.x) * (c.y - a.y)) > ((b.y - a.y) * (c.x - a.x)); } /** * Repeatedly remove the last point if not counter clockwise with new point; then add the new point. If the new point is * equal to the last point in the list; do nothing. * @param list List<Point2d>; the list of points * @param newPoint Point2d; the point that will be added. */ private static void cleanAndAppend(final List list, final Point2d newPoint) { Point2d last = list.get(list.size() - 1); if (last.x == newPoint.x && last.y == newPoint.y) { return; } while (list.size() >= 2 && !ccw(list.get(list.size() - 2), list.get(list.size() - 1), newPoint)) { list.remove(list.size() - 1); } list.add(newPoint); } /** * Implementation of the convex hull algorithm by Reham Alshamrani c.s.; see * A Preprocessing Technique for Fast Convex * Hull Computation. * @param list List<Point2d>; list of the points (will not be modified) * @return Polygon2d; the convex hull of the points * @throws NullPointerException when the list is null * @throws DrawRuntimeException when the list contains too few points */ public static Polygon2d convexHullAlshamrani(final List list) throws NullPointerException, DrawRuntimeException { // Find the four extreme points Throw.whenNull(list, "list"); Throw.when(list.size() < 1, DrawRuntimeException.class, "Too few points in list"); Point2d minX = list.get(0); // Initialize to the first point in list to avoid checking for null on each iteration Point2d minY = list.get(0); Point2d maxX = list.get(0); Point2d maxY = list.get(0); for (Point2d point : list) { if (minX.x > point.x || minX.x == point.x && minX.y > point.y) { minX = point; } if (minY.y > point.y || minY.y == point.y && minY.x < point.x) { minY = point; } if (maxX.x < point.x || maxX.x == point.x && maxX.y > point.y) { maxX = point; } if (maxY.y < point.y || maxY.y == point.y && maxY.x < point.x) { maxY = point; } } // Filter and group the points into priority queues that order by x value (tie breaker is y value) // Alshamrani does not show how he tests that a point is outside each edge of the four extreme points. We use ccw. // Alshamrani poorly documents the ordering method for the four queues when the primary component values are the same. // Testing has shown that sorting a filled ArrayList is faster than putting the same elements one by one in a TreeSet. Comparator forwardComparator = new Comparator() { @Override public int compare(final Point2d point1, final Point2d point2) { if (point1.x == point2.x) { return (int) Math.signum(point1.y - point2.y); } return (int) Math.signum(point1.x - point2.x); } }; Comparator reverseComparator = new Comparator() { @Override public int compare(final Point2d point1, final Point2d point2) { if (point1.x == point2.x) { return (int) Math.signum(point1.y - point2.y); } return (int) Math.signum(point2.x - point1.x); } }; List lowerLeft = new ArrayList<>(); List lowerRight = new ArrayList<>(); List upperRight = new ArrayList<>(); List upperLeft = new ArrayList<>(); for (Point2d point : list) { if (point.x <= minY.x && point.y <= minX.y) { if (ccw(minX, point, minY)) { lowerLeft.add(point); } } else if (point.x >= minY.x && point.y <= maxX.y) { if (ccw(minY, point, maxX)) { lowerRight.add(point); } } else if (point.x >= maxY.x && point.y >= maxX.y) { if (ccw(maxX, point, maxY)) { upperRight.add(point); } } else if (point.x <= maxY.x && point.y >= minX.y) { if (ccw(maxY, point, minX)) { upperLeft.add(point); } } } // System.out.println(String.format("minX %s, minY %s, maxX %s, maxY %s", minX, minY, maxX, maxY)); // System.out.println(String.format("total: %d, ll: %d (%.0f%%), lr: %d (%.0f%%), ur: %d (%.0f%%), ul: %d (%.0f%%)", // list.size(), lowerLeft.size(), 100.0 * lowerLeft.size() / list.size(), lowerRight.size(), // 100.0 * lowerRight.size() / list.size(), upperRight.size(), 100.0 * upperRight.size() / list.size(), // upperLeft.size(), 100.0 * upperLeft.size() / list.size())); Collections.sort(lowerLeft, forwardComparator); Collections.sort(lowerRight, forwardComparator); Collections.sort(upperRight, reverseComparator); Collections.sort(upperLeft, reverseComparator); // Construct the convex hull List result = new ArrayList<>(); result.add(minX); for (Point2d point : lowerLeft) { cleanAndAppend(result, point); } cleanAndAppend(result, minY); for (Point2d point : lowerRight) { cleanAndAppend(result, point); } cleanAndAppend(result, maxX); for (Point2d point : upperRight) { cleanAndAppend(result, point); } cleanAndAppend(result, maxY); for (Point2d point : upperLeft) { cleanAndAppend(result, point); } return new Polygon2d(result); } /** * Implementation of Andrew's Monotone Chain convex hull algorithm. This implementation (sorts) modifies the provided list * of points! * @param list List<Point2d>; list of the points (will be modified) * @return Polygon2d; the convex hull of the points * @throws NullPointerException when the list is null * @throws DrawRuntimeException when the list contains too few points */ public static Polygon2d convexHullMonotone(final List list) throws NullPointerException, DrawRuntimeException { Collections.sort(list, new Comparator() { @Override public int compare(final Point2d point1, final Point2d point2) { if (point1.x == point2.x) { return (int) Math.signum(point1.y - point2.y); } return (int) Math.signum(point1.x - point2.x); } }); // That sort operation was O(N log (N)); the remainder is O(N) List result = new ArrayList<>(); // Lower hull for (Point2d p : list) { while (result.size() >= 2 && !ccw(result.get(result.size() - 2), result.get(result.size() - 1), p)) { result.remove(result.size() - 1); } result.add(p); } // Upper hull int lowLimit = result.size() + 1; for (int i = list.size() - 1; i >= 0; i--) { Point2d p = list.get(i); while (result.size() >= lowLimit && !ccw(result.get(result.size() - 2), result.get(result.size() - 1), p)) { result.remove(result.size() - 1); } result.add(p); } if (result.size() > 0) { result.remove(result.size() - 1); } // else; zero points; the constructor of the Polygon2d will throw a DrawRuntimeException return new Polygon2d(result); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy