View Javadoc
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  /*
19   * This is not the original file distributed by the Apache Software Foundation
20   * It has been modified by the Hipparchus project
21   */
22  package org.hipparchus.geometry.euclidean.twod.hull;
23  
24  import java.util.ArrayList;
25  import java.util.Collection;
26  import java.util.Collections;
27  import java.util.Comparator;
28  import java.util.List;
29  
30  import org.hipparchus.geometry.euclidean.twod.Line;
31  import org.hipparchus.geometry.euclidean.twod.Vector2D;
32  import org.hipparchus.util.FastMath;
33  import org.hipparchus.util.Precision;
34  
35  /**
36   * Implements Andrew's monotone chain method to generate the convex hull of a finite set of
37   * points in the two-dimensional euclidean space.
38   * <p>
39   * The runtime complexity is O(n log n), with n being the number of input points. If the
40   * point set is already sorted (by x-coordinate), the runtime complexity is O(n).
41   * <p>
42   * The implementation is not sensitive to collinear points on the hull. The parameter
43   * {@code includeCollinearPoints} allows to control the behavior with regard to collinear points.
44   * If {@code true}, all points on the boundary of the hull will be added to the hull vertices,
45   * otherwise only the extreme points will be present. By default, collinear points are not added
46   * as hull vertices.
47   * <p>
48   * The {@code tolerance} parameter (default: 1e-10) is used as epsilon criteria to determine
49   * identical and collinear points.
50   *
51   * @see <a href="http://en.wikibooks.org/wiki/Algorithm_Implementation/Geometry/Convex_hull/Monotone_chain">
52   * Andrew's monotone chain algorithm (Wikibooks)</a>
53   */
54  public class MonotoneChain extends AbstractConvexHullGenerator2D {
55  
56      /**
57       * Create a new MonotoneChain instance.
58       */
59      public MonotoneChain() {
60          this(false);
61      }
62  
63      /**
64       * Create a new MonotoneChain instance.
65       * @param includeCollinearPoints whether collinear points shall be added as hull vertices
66       */
67      public MonotoneChain(final boolean includeCollinearPoints) {
68          super(includeCollinearPoints);
69      }
70  
71      /**
72       * Create a new MonotoneChain instance.
73       * @param includeCollinearPoints whether collinear points shall be added as hull vertices
74       * @param tolerance tolerance below which points are considered identical
75       */
76      public MonotoneChain(final boolean includeCollinearPoints, final double tolerance) {
77          super(includeCollinearPoints, tolerance);
78      }
79  
80      /** {@inheritDoc} */
81      @Override
82      public Collection<Vector2D> findHullVertices(final Collection<Vector2D> points) {
83  
84          final List<Vector2D> pointsSortedByXAxis = new ArrayList<>(points);
85  
86          // sort the points in increasing order on the x-axis
87          Collections.sort(pointsSortedByXAxis, new Comparator<Vector2D>() {
88              /** {@inheritDoc} */
89              @Override
90              public int compare(final Vector2D o1, final Vector2D o2) {
91                  final double tolerance = getTolerance();
92                  // need to take the tolerance value into account, otherwise collinear points
93                  // will not be handled correctly when building the upper/lower hull
94                  final int diff = Precision.compareTo(o1.getX(), o2.getX(), tolerance);
95                  if (diff == 0) {
96                      return Precision.compareTo(o1.getY(), o2.getY(), tolerance);
97                  } else {
98                      return diff;
99                  }
100             }
101         });
102 
103         // build lower hull
104         final List<Vector2D> lowerHull = new ArrayList<>();
105         for (Vector2D p : pointsSortedByXAxis) {
106             updateHull(p, lowerHull);
107         }
108 
109         // build upper hull
110         final List<Vector2D> upperHull = new ArrayList<>();
111         for (int idx = pointsSortedByXAxis.size() - 1; idx >= 0; idx--) {
112             final Vector2D p = pointsSortedByXAxis.get(idx);
113             updateHull(p, upperHull);
114         }
115 
116         // concatenate the lower and upper hulls
117         // the last point of each list is omitted as it is repeated at the beginning of the other list
118         final List<Vector2D> hullVertices = new ArrayList<>(lowerHull.size() + upperHull.size() - 2);
119         for (int idx = 0; idx < lowerHull.size() - 1; idx++) {
120             hullVertices.add(lowerHull.get(idx));
121         }
122         for (int idx = 0; idx < upperHull.size() - 1; idx++) {
123             hullVertices.add(upperHull.get(idx));
124         }
125 
126         // special case: if the lower and upper hull may contain only 1 point if all are identical
127         if (hullVertices.isEmpty() && ! lowerHull.isEmpty()) {
128             hullVertices.add(lowerHull.get(0));
129         }
130 
131         return hullVertices;
132     }
133 
134     /**
135      * Update the partial hull with the current point.
136      *
137      * @param point the current point
138      * @param hull the partial hull
139      */
140     private void updateHull(final Vector2D point, final List<Vector2D> hull) {
141         final double tolerance = getTolerance();
142 
143         if (hull.size() == 1) {
144             // ensure that we do not add an identical point
145             final Vector2D p1 = hull.get(0);
146             if (p1.distance(point) < tolerance) {
147                 return;
148             }
149         }
150 
151         while (hull.size() >= 2) {
152             final int size = hull.size();
153             final Vector2D p1 = hull.get(size - 2);
154             final Vector2D p2 = hull.get(size - 1);
155 
156             final double offset = new Line(p1, p2, tolerance).getOffset(point);
157             if (FastMath.abs(offset) < tolerance) {
158                 // the point is collinear to the line (p1, p2)
159 
160                 final double distanceToCurrent = p1.distance(point);
161                 if (distanceToCurrent < tolerance || p2.distance(point) < tolerance) {
162                     // the point is assumed to be identical to either p1 or p2
163                     return;
164                 }
165 
166                 final double distanceToLast = p1.distance(p2);
167                 if (isIncludeCollinearPoints()) {
168                     final int index = distanceToCurrent < distanceToLast ? size - 1 : size;
169                     hull.add(index, point);
170                 } else {
171                     if (distanceToCurrent > distanceToLast) {
172                         hull.remove(size - 1);
173                         hull.add(point);
174                     }
175                 }
176                 return;
177             } else if (offset > 0) {
178                 hull.remove(size - 1);
179             } else {
180                 break;
181             }
182         }
183         hull.add(point);
184     }
185 
186 }