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.  *      https://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. /*
  18.  * This is not the original file distributed by the Apache Software Foundation
  19.  * It has been modified by the Hipparchus project
  20.  */
  21. package org.hipparchus.geometry.euclidean.twod.hull;

  22. import java.util.ArrayList;
  23. import java.util.Collection;
  24. import java.util.Comparator;
  25. import java.util.List;

  26. import org.hipparchus.geometry.euclidean.twod.Line;
  27. import org.hipparchus.geometry.euclidean.twod.Vector2D;
  28. import org.hipparchus.util.FastMath;
  29. import org.hipparchus.util.Precision;

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

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

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

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

  71.     /** {@inheritDoc} */
  72.     @Override
  73.     public Collection<Vector2D> findHullVertices(final Collection<Vector2D> points) {

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

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

  91.         // build lower hull
  92.         final List<Vector2D> lowerHull = new ArrayList<>();
  93.         for (Vector2D p : pointsSortedByXAxis) {
  94.             updateHull(p, lowerHull);
  95.         }

  96.         // build upper hull
  97.         final List<Vector2D> upperHull = new ArrayList<>();
  98.         for (int idx = pointsSortedByXAxis.size() - 1; idx >= 0; idx--) {
  99.             final Vector2D p = pointsSortedByXAxis.get(idx);
  100.             updateHull(p, upperHull);
  101.         }

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

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

  115.         return hullVertices;
  116.     }

  117.     /**
  118.      * Update the partial hull with the current point.
  119.      *
  120.      * @param point the current point
  121.      * @param hull the partial hull
  122.      */
  123.     private void updateHull(final Vector2D point, final List<Vector2D> hull) {
  124.         final double tolerance = getTolerance();

  125.         if (hull.size() == 1) {
  126.             // ensure that we do not add an identical point
  127.             final Vector2D p1 = hull.get(0);
  128.             if (p1.distance(point) < tolerance) {
  129.                 return;
  130.             }
  131.         }

  132.         while (hull.size() >= 2) {
  133.             final int size = hull.size();
  134.             final Vector2D p1 = hull.get(size - 2);
  135.             final Vector2D p2 = hull.get(size - 1);

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

  139.                 final double distanceToCurrent = p1.distance(point);
  140.                 if (distanceToCurrent < tolerance || p2.distance(point) < tolerance) {
  141.                     // the point is assumed to be identical to either p1 or p2
  142.                     return;
  143.                 }

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

  163. }