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.geometry.hull.euclidean.twod;
18
19 import java.util.ArrayList;
20 import java.util.Collection;
21 import java.util.List;
22
23 import org.apache.commons.geometry.euclidean.twod.Vector2D;
24
25 /**
26 * A simple heuristic to improve the performance of convex hull algorithms.
27 * <p>
28 * The heuristic is based on the idea of a convex quadrilateral, which is formed by
29 * four points with the lowest and highest x / y coordinates. Any point that lies inside
30 * this quadrilateral can not be part of the convex hull and can thus be safely discarded
31 * before generating the convex hull itself.
32 * <p>
33 * The complexity of the operation is O(n), and may greatly improve the time it takes to
34 * construct the convex hull afterwards, depending on the point distribution.
35 *
36 * @see <a href="http://en.wikipedia.org/wiki/Convex_hull_algorithms#Akl-Toussaint_heuristic">
37 * Akl-Toussaint heuristic (Wikipedia)</a>
38 */
39 public final class AklToussaintHeuristic {
40
41 /** Hide utility constructor. */
42 private AklToussaintHeuristic() {
43 }
44
45 /**
46 * Returns a point set that is reduced by all points for which it is safe to assume
47 * that they are not part of the convex hull.
48 *
49 * @param points the original point set
50 * @return a reduced point set, useful as input for convex hull algorithms
51 */
52 public static Collection<Vector2D> reducePoints(final Collection<Vector2D> points) {
53
54 // find the leftmost point
55 int size = 0;
56 Vector2D minX = null;
57 Vector2D maxX = null;
58 Vector2D minY = null;
59 Vector2D maxY = null;
60 for (final Vector2D p : points) {
61 if (minX == null || p.getX() < minX.getX()) {
62 minX = p;
63 }
64 if (maxX == null || p.getX() > maxX.getX()) {
65 maxX = p;
66 }
67 if (minY == null || p.getY() < minY.getY()) {
68 minY = p;
69 }
70 if (maxY == null || p.getY() > maxY.getY()) {
71 maxY = p;
72 }
73 size++;
74 }
75
76 if (size < 4) {
77 return points;
78 }
79
80 final List<Vector2D> quadrilateral = buildQuadrilateral(minY, maxX, maxY, minX);
81 // if the quadrilateral is not well formed, e.g. only 2 points, do not attempt to reduce
82 if (quadrilateral.size() < 3) {
83 return points;
84 }
85
86 final List<Vector2D> reducedPoints = new ArrayList<>(quadrilateral);
87 for (final Vector2D p : points) {
88 // check all points if they are within the quadrilateral
89 // in which case they can not be part of the convex hull
90 if (!insideQuadrilateral(p, quadrilateral)) {
91 reducedPoints.add(p);
92 }
93 }
94
95 return reducedPoints;
96 }
97
98 /**
99 * Build the convex quadrilateral with the found corner points (with min/max x/y coordinates).
100 *
101 * @param points the respective points with min/max x/y coordinate
102 * @return the quadrilateral
103 */
104 private static List<Vector2D> buildQuadrilateral(final Vector2D... points) {
105 final List<Vector2D> quadrilateral = new ArrayList<>();
106 for (final Vector2D p : points) {
107 if (!quadrilateral.contains(p)) {
108 quadrilateral.add(p);
109 }
110 }
111 return quadrilateral;
112 }
113
114 /**
115 * Checks if the given point is located within the convex quadrilateral.
116 * @param point the point to check
117 * @param quadrilateralPoints the convex quadrilateral, represented by 4 points
118 * @return {@code true} if the point is inside the quadrilateral, {@code false} otherwise
119 */
120 private static boolean insideQuadrilateral(final Vector2D point,
121 final List<Vector2D> quadrilateralPoints) {
122
123 Vector2D v1 = quadrilateralPoints.get(0);
124 Vector2D v2 = quadrilateralPoints.get(1);
125
126 if (point.equals(v1) || point.equals(v2)) {
127 return true;
128 }
129
130 // get the location of the point relative to the first two vertices
131 final double last = signedAreaPoints(v1, v2, point);
132 final int size = quadrilateralPoints.size();
133 // loop through the rest of the vertices
134 for (int i = 1; i < size; i++) {
135 v1 = v2;
136 v2 = quadrilateralPoints.get((i + 1) == size ? 0 : i + 1);
137
138 if (point.equals(v1) || point.equals(v2)) {
139 return true;
140 }
141
142 // do side of line test: multiply the last location with this location
143 // if they are the same sign then the operation will yield a positive result
144 // -x * -y = +xy, x * y = +xy, -x * y = -xy, x * -y = -xy
145 if (last * signedAreaPoints(v1, v2, point) < 0) {
146 return false;
147 }
148 }
149 return true;
150 }
151
152 /** Compute the signed area of the parallelogram formed by vectors between the given points. The first
153 * vector points from {@code p0} to {@code p1} and the second from {@code p0} to {@code p3}.
154 * @param p0 first point
155 * @param p1 second point
156 * @param p2 third point
157 * @return signed area of parallelogram formed by vectors between the given points
158 */
159 private static double signedAreaPoints(final Vector2D p0, final Vector2D p1, final Vector2D p2) {
160 return p0.vectorTo(p1).signedArea(p0.vectorTo(p2));
161 }
162
163 }