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