MonotoneChain.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.apache.commons.math3.geometry.euclidean.twod.hull;

  18. import java.util.ArrayList;
  19. import java.util.Collection;
  20. import java.util.Collections;
  21. import java.util.Comparator;
  22. import java.util.List;

  23. import org.apache.commons.math3.geometry.euclidean.twod.Line;
  24. import org.apache.commons.math3.geometry.euclidean.twod.Vector2D;
  25. import org.apache.commons.math3.util.FastMath;
  26. import org.apache.commons.math3.util.Precision;

  27. /**
  28.  * Implements Andrew's monotone chain method to generate the convex hull of a finite set of
  29.  * points in the two-dimensional euclidean space.
  30.  * <p>
  31.  * The runtime complexity is O(n log n), with n being the number of input points. If the
  32.  * point set is already sorted (by x-coordinate), the runtime complexity is O(n).
  33.  * <p>
  34.  * The implementation is not sensitive to collinear points on the hull. The parameter
  35.  * {@code includeCollinearPoints} allows to control the behavior with regard to collinear points.
  36.  * If {@code true}, all points on the boundary of the hull will be added to the hull vertices,
  37.  * otherwise only the extreme points will be present. By default, collinear points are not added
  38.  * as hull vertices.
  39.  * <p>
  40.  * The {@code tolerance} parameter (default: 1e-10) is used as epsilon criteria to determine
  41.  * identical and collinear points.
  42.  *
  43.  * @see <a href="http://en.wikibooks.org/wiki/Algorithm_Implementation/Geometry/Convex_hull/Monotone_chain">
  44.  * Andrew's monotone chain algorithm (Wikibooks)</a>
  45.  * @since 3.3
  46.  */
  47. public class MonotoneChain extends AbstractConvexHullGenerator2D {

  48.     /**
  49.      * Create a new MonotoneChain instance.
  50.      */
  51.     public MonotoneChain() {
  52.         this(false);
  53.     }

  54.     /**
  55.      * Create a new MonotoneChain instance.
  56.      * @param includeCollinearPoints whether collinear points shall be added as hull vertices
  57.      */
  58.     public MonotoneChain(final boolean includeCollinearPoints) {
  59.         super(includeCollinearPoints);
  60.     }

  61.     /**
  62.      * Create a new MonotoneChain instance.
  63.      * @param includeCollinearPoints whether collinear points shall be added as hull vertices
  64.      * @param tolerance tolerance below which points are considered identical
  65.      */
  66.     public MonotoneChain(final boolean includeCollinearPoints, final double tolerance) {
  67.         super(includeCollinearPoints, tolerance);
  68.     }

  69.     @Override
  70.     public Collection<Vector2D> findHullVertices(final Collection<Vector2D> points) {

  71.         final List<Vector2D> pointsSortedByXAxis = new ArrayList<Vector2D>(points);

  72.         // sort the points in increasing order on the x-axis
  73.         Collections.sort(pointsSortedByXAxis, new Comparator<Vector2D>() {
  74.             public int compare(final Vector2D o1, final Vector2D o2) {
  75.                 final double tolerance = getTolerance();
  76.                 // need to take the tolerance value into account, otherwise collinear points
  77.                 // will not be handled correctly when building the upper/lower hull
  78.                 final int diff = Precision.compareTo(o1.getX(), o2.getX(), tolerance);
  79.                 if (diff == 0) {
  80.                     return Precision.compareTo(o1.getY(), o2.getY(), tolerance);
  81.                 } else {
  82.                     return diff;
  83.                 }
  84.             }
  85.         });

  86.         // build lower hull
  87.         final List<Vector2D> lowerHull = new ArrayList<Vector2D>();
  88.         for (Vector2D p : pointsSortedByXAxis) {
  89.             updateHull(p, lowerHull);
  90.         }

  91.         // build upper hull
  92.         final List<Vector2D> upperHull = new ArrayList<Vector2D>();
  93.         for (int idx = pointsSortedByXAxis.size() - 1; idx >= 0; idx--) {
  94.             final Vector2D p = pointsSortedByXAxis.get(idx);
  95.             updateHull(p, upperHull);
  96.         }

  97.         // concatenate the lower and upper hulls
  98.         // the last point of each list is omitted as it is repeated at the beginning of the other list
  99.         final List<Vector2D> hullVertices = new ArrayList<Vector2D>(lowerHull.size() + upperHull.size() - 2);
  100.         for (int idx = 0; idx < lowerHull.size() - 1; idx++) {
  101.             hullVertices.add(lowerHull.get(idx));
  102.         }
  103.         for (int idx = 0; idx < upperHull.size() - 1; idx++) {
  104.             hullVertices.add(upperHull.get(idx));
  105.         }

  106.         // special case: if the lower and upper hull may contain only 1 point if all are identical
  107.         if (hullVertices.isEmpty() && ! lowerHull.isEmpty()) {
  108.             hullVertices.add(lowerHull.get(0));
  109.         }

  110.         return hullVertices;
  111.     }

  112.     /**
  113.      * Update the partial hull with the current point.
  114.      *
  115.      * @param point the current point
  116.      * @param hull the partial hull
  117.      */
  118.     private void updateHull(final Vector2D point, final List<Vector2D> hull) {
  119.         final double tolerance = getTolerance();

  120.         if (hull.size() == 1) {
  121.             // ensure that we do not add an identical point
  122.             final Vector2D p1 = hull.get(0);
  123.             if (p1.distance(point) < tolerance) {
  124.                 return;
  125.             }
  126.         }

  127.         while (hull.size() >= 2) {
  128.             final int size = hull.size();
  129.             final Vector2D p1 = hull.get(size - 2);
  130.             final Vector2D p2 = hull.get(size - 1);

  131.             final double offset = new Line(p1, p2, tolerance).getOffset(point);
  132.             if (FastMath.abs(offset) < tolerance) {
  133.                 // the point is collinear to the line (p1, p2)

  134.                 final double distanceToCurrent = p1.distance(point);
  135.                 if (distanceToCurrent < tolerance || p2.distance(point) < tolerance) {
  136.                     // the point is assumed to be identical to either p1 or p2
  137.                     return;
  138.                 }

  139.                 final double distanceToLast = p1.distance(p2);
  140.                 if (isIncludeCollinearPoints()) {
  141.                     final int index = distanceToCurrent < distanceToLast ? size - 1 : size;
  142.                     hull.add(index, point);
  143.                 } else {
  144.                     if (distanceToCurrent > distanceToLast) {
  145.                         hull.remove(size - 1);
  146.                         hull.add(point);
  147.                     }
  148.                 }
  149.                 return;
  150.             } else if (offset > 0) {
  151.                 hull.remove(size - 1);
  152.             } else {
  153.                 break;
  154.             }
  155.         }
  156.         hull.add(point);
  157.     }

  158. }