1 package org.djutils.draw.line;
2
3 import java.util.ArrayList;
4 import java.util.Collection;
5 import java.util.Collections;
6 import java.util.Comparator;
7 import java.util.Iterator;
8 import java.util.List;
9
10 import org.djutils.draw.DrawRuntimeException;
11 import org.djutils.draw.Drawable2d;
12 import org.djutils.draw.bounds.Bounds2d;
13 import org.djutils.draw.point.Point2d;
14 import org.djutils.exceptions.Throw;
15
16
17
18
19
20
21
22
23
24
25
26
27 public final class ConvexHull
28 {
29
30
31
32 private ConvexHull()
33 {
34
35 }
36
37
38
39
40
41
42
43 public static Polygon2d convexHull(final Iterator<Point2d> iterator)
44 {
45 List<Point2d> list = new ArrayList<>();
46 iterator.forEachRemaining(list::add);
47 return convexHullAlshamrani(list);
48 }
49
50
51
52
53
54
55
56
57 public static Polygon2d convexHull(final Drawable2d... drawable2d) throws NullPointerException, IllegalArgumentException
58 {
59 return convexHull(Bounds2d.pointsOf(drawable2d));
60 }
61
62
63
64
65
66
67
68
69 public static Polygon2d convexHull(final Collection<Drawable2d> drawableCollection)
70 throws NullPointerException, IllegalArgumentException
71 {
72 Throw.whenNull(drawableCollection, "drawableCollection");
73 Throw.when(drawableCollection.isEmpty(), DrawRuntimeException.class, "drawableCollection may not be empty");
74 return convexHull(Bounds2d.pointsOf(drawableCollection));
75 }
76
77
78
79
80
81
82 public static Polygon2d convexHull(final List<Point2d> list)
83 {
84 return convexHullAlshamrani(list);
85 }
86
87
88
89
90
91
92
93
94 private static boolean ccw(final Point2d a, final Point2d b, final Point2d c)
95 {
96
97 return ((b.x - a.x) * (c.y - a.y)) > ((b.y - a.y) * (c.x - a.x));
98 }
99
100
101
102
103
104
105
106 private static void cleanAndAppend(final List<Point2d> list, final Point2d newPoint)
107 {
108 Point2d last = list.get(list.size() - 1);
109 if (last.x == newPoint.x && last.y == newPoint.y)
110 {
111 return;
112 }
113 while (list.size() >= 2 && !ccw(list.get(list.size() - 2), list.get(list.size() - 1), newPoint))
114 {
115 list.remove(list.size() - 1);
116 }
117 list.add(newPoint);
118 }
119
120
121
122
123
124
125
126
127
128
129 public static Polygon2d convexHullAlshamrani(final List<Point2d> list) throws NullPointerException, DrawRuntimeException
130 {
131
132 Throw.whenNull(list, "list");
133 Throw.when(list.size() < 1, DrawRuntimeException.class, "Too few points in list");
134 Point2d minX = list.get(0);
135 Point2d minY = list.get(0);
136 Point2d maxX = list.get(0);
137 Point2d maxY = list.get(0);
138 for (Point2d point : list)
139 {
140 if (minX.x > point.x || minX.x == point.x && minX.y > point.y)
141 {
142 minX = point;
143 }
144 if (minY.y > point.y || minY.y == point.y && minY.x < point.x)
145 {
146 minY = point;
147 }
148 if (maxX.x < point.x || maxX.x == point.x && maxX.y > point.y)
149 {
150 maxX = point;
151 }
152 if (maxY.y < point.y || maxY.y == point.y && maxY.x < point.x)
153 {
154 maxY = point;
155 }
156 }
157
158
159
160
161 Comparator<Point2d> forwardComparator = new Comparator<Point2d>()
162 {
163 @Override
164 public int compare(final Point2d point1, final Point2d point2)
165 {
166 if (point1.x == point2.x)
167 {
168 return (int) Math.signum(point1.y - point2.y);
169 }
170 return (int) Math.signum(point1.x - point2.x);
171 }
172 };
173 Comparator<Point2d> reverseComparator = new Comparator<Point2d>()
174 {
175 @Override
176 public int compare(final Point2d point1, final Point2d point2)
177 {
178 if (point1.x == point2.x)
179 {
180 return (int) Math.signum(point1.y - point2.y);
181 }
182 return (int) Math.signum(point2.x - point1.x);
183 }
184 };
185 List<Point2d> lowerLeft = new ArrayList<>();
186 List<Point2d> lowerRight = new ArrayList<>();
187 List<Point2d> upperRight = new ArrayList<>();
188 List<Point2d> upperLeft = new ArrayList<>();
189
190 for (Point2d point : list)
191 {
192 if (point.x <= minY.x && point.y <= minX.y)
193 {
194 if (ccw(minX, point, minY))
195 {
196 lowerLeft.add(point);
197 }
198 }
199 else if (point.x >= minY.x && point.y <= maxX.y)
200 {
201 if (ccw(minY, point, maxX))
202 {
203 lowerRight.add(point);
204 }
205 }
206 else if (point.x >= maxY.x && point.y >= maxX.y)
207 {
208 if (ccw(maxX, point, maxY))
209 {
210 upperRight.add(point);
211 }
212 }
213 else if (point.x <= maxY.x && point.y >= minX.y)
214 {
215 if (ccw(maxY, point, minX))
216 {
217 upperLeft.add(point);
218 }
219 }
220 }
221
222
223
224
225
226 Collections.sort(lowerLeft, forwardComparator);
227 Collections.sort(lowerRight, forwardComparator);
228 Collections.sort(upperRight, reverseComparator);
229 Collections.sort(upperLeft, reverseComparator);
230
231 List<Point2d> result = new ArrayList<>();
232 result.add(minX);
233 for (Point2d point : lowerLeft)
234 {
235 cleanAndAppend(result, point);
236 }
237 cleanAndAppend(result, minY);
238 for (Point2d point : lowerRight)
239 {
240 cleanAndAppend(result, point);
241 }
242 cleanAndAppend(result, maxX);
243 for (Point2d point : upperRight)
244 {
245 cleanAndAppend(result, point);
246 }
247 cleanAndAppend(result, maxY);
248 for (Point2d point : upperLeft)
249 {
250 cleanAndAppend(result, point);
251 }
252 return new Polygon2d(result);
253 }
254
255
256
257
258
259
260
261
262
263 public static Polygon2d convexHullMonotone(final List<Point2d> list) throws NullPointerException, DrawRuntimeException
264 {
265 Collections.sort(list, new Comparator<Point2d>()
266 {
267 @Override
268 public int compare(final Point2d point1, final Point2d point2)
269 {
270 if (point1.x == point2.x)
271 {
272 return (int) Math.signum(point1.y - point2.y);
273 }
274 return (int) Math.signum(point1.x - point2.x);
275 }
276 });
277
278 List<Point2d> result = new ArrayList<>();
279
280 for (Point2d p : list)
281 {
282 while (result.size() >= 2 && !ccw(result.get(result.size() - 2), result.get(result.size() - 1), p))
283 {
284 result.remove(result.size() - 1);
285 }
286 result.add(p);
287 }
288
289 int lowLimit = result.size() + 1;
290 for (int i = list.size() - 1; i >= 0; i--)
291 {
292 Point2d p = list.get(i);
293 while (result.size() >= lowLimit && !ccw(result.get(result.size() - 2), result.get(result.size() - 1), p))
294 {
295 result.remove(result.size() - 1);
296 }
297 result.add(p);
298 }
299 if (result.size() > 0)
300 {
301 result.remove(result.size() - 1);
302 }
303
304 return new Polygon2d(result);
305 }
306
307 }