1 package org.djutils.draw.line;
2
3 import static org.junit.Assert.assertEquals;
4 import static org.junit.Assert.fail;
5
6 import java.io.IOException;
7 import java.util.ArrayList;
8 import java.util.Arrays;
9 import java.util.Collection;
10 import java.util.Collections;
11 import java.util.LinkedHashMap;
12 import java.util.LinkedHashSet;
13 import java.util.List;
14 import java.util.Map;
15 import java.util.Random;
16
17 import org.djutils.draw.DrawRuntimeException;
18 import org.djutils.draw.Drawable2d;
19 import org.djutils.draw.point.Point2d;
20 import org.junit.Test;
21
22
23
24
25
26
27
28
29
30
31 public class ConvexHullTest
32 {
33
34
35
36
37
38 public static Map<String, ConvexHullImplementation> getImplementations()
39 {
40 Map<String, ConvexHullImplementation> implementations = new LinkedHashMap<>();
41 implementations.put("Monotone", new ConvexHullImplementation()
42 {
43 @Override
44 public Polygon2d run(final List<Point2d> points) throws NullPointerException, DrawRuntimeException
45 {
46 return ConvexHull.convexHullMonotone(points);
47 }
48 });
49 implementations.put("Alshamrani", new ConvexHullImplementation()
50 {
51 @Override
52 public Polygon2d run(final List<Point2d> points) throws NullPointerException, DrawRuntimeException
53 {
54 return ConvexHull.convexHullAlshamrani(points);
55 }
56 });
57 implementations.put("Default", new ConvexHullImplementation()
58 {
59 @Override
60 public Polygon2d run(final List<Point2d> points) throws NullPointerException, DrawRuntimeException
61 {
62 return ConvexHull.convexHull(points);
63 }
64 });
65 return implementations;
66 }
67
68
69
70
71 @Test
72 public void testConvexHull()
73 {
74 Map<String, ConvexHullImplementation> implementations = getImplementations();
75 for (String name : implementations.keySet())
76 {
77 ConvexHullImplementation chi = implementations.get(name);
78 try
79 {
80 chi.run(null);
81 fail("should have thrown a NullPointerException");
82 }
83 catch (NullPointerException npe)
84 {
85
86 }
87
88 try
89 {
90 chi.run(new ArrayList<>());
91 fail("Empty list should have thrown a DrawRuntimeException");
92 }
93 catch (DrawRuntimeException dre)
94 {
95
96 }
97
98 Point2d testPoint = new Point2d(2, 3);
99 List<Point2d> points = Arrays.asList(testPoint);
100 try
101 {
102 chi.run(points);
103 fail("List with only one point should have thrown a DrawRuntimeException");
104 }
105 catch (DrawRuntimeException dre)
106 {
107
108 }
109
110
111 assertEquals("points still contains one point", 1, points.size());
112 assertEquals("points still contains testPoint", testPoint, points.get(0));
113
114 points = new ArrayList<>();
115 points.add(testPoint);
116 points.add(testPoint);
117 try
118 {
119 chi.run(points);
120 fail("List with only two identical should have thrown a DrawRuntimeException");
121 }
122 catch (DrawRuntimeException dre)
123 {
124
125 }
126
127
128 assertEquals("points still contains one point", 2, points.size());
129 assertEquals("first points is testPoint", testPoint, points.get(0));
130 assertEquals("second points is testPoint", testPoint, points.get(1));
131 }
132 try
133 {
134 ConvexHull.convexHull(new LinkedHashSet<Drawable2d>());
135 fail("empty collection should have thrown a DrawRuntimeException");
136 }
137 catch (DrawRuntimeException dre)
138 {
139
140 }
141
142 try
143 {
144 ConvexHull.convexHull((Collection<Drawable2d>) null);
145 fail("null collection should have thrown a NullPointerException");
146 }
147 catch (NullPointerException npe)
148 {
149
150 }
151
152
153 List<Point2d> points = Arrays.asList(new Point2d(16, 3), new Point2d(12, 17), new Point2d(0, 6), new Point2d(-4, -6),
154 new Point2d(16, 6), new Point2d(16, -7), new Point2d(16, -3), new Point2d(17, -4), new Point2d(5, 19),
155 new Point2d(19, -8), new Point2d(3, 16), new Point2d(12, 13), new Point2d(3, -4), new Point2d(17, 5),
156 new Point2d(-3, 15), new Point2d(-3, -9), new Point2d(0, 11), new Point2d(-9, -3), new Point2d(-4, -2),
157 new Point2d(12, 10));
158 Polygon2d expectedResult = new Polygon2d(new Point2d(-9, -3), new Point2d(-3, -9), new Point2d(19, -8),
159 new Point2d(17, 5), new Point2d(12, 17), new Point2d(5, 19), new Point2d(-3, 15));
160 checkImplementations(implementations, points, expectedResult);
161 Collections.shuffle(points, new Random(123));
162 checkImplementations(implementations, points, expectedResult);
163
164 assertEquals("convex hull of iterator", expectedResult, ConvexHull.convexHull(points.iterator()));
165
166 assertEquals("convex hull of one drawable", expectedResult, ConvexHull.convexHull(new PolyLine2d(points)));
167
168 assertEquals("convex hull of two drawables", expectedResult,
169 ConvexHull.convexHull(new PolyLine2d(points), new Point2d(1, 2)));
170 assertEquals("convex hull of two drawables", expectedResult,
171 ConvexHull.convexHull(new Point2d(1, 2), new PolyLine2d(points)));
172
173 Collection<Drawable2d> collection = new LinkedHashSet<>();
174 collection.add(new PolyLine2d(points));
175 assertEquals("convex hull of collection of one Drawable2d object", expectedResult, ConvexHull.convexHull(collection));
176
177 collection.add(new Point2d(1, 2));
178 assertEquals("convex hull of collection of two Drawable2d objects", expectedResult, ConvexHull.convexHull(collection));
179
180 points = new ArrayList<>();
181 for (int x = -1; x <= 1; x++)
182 {
183 for (int y = -2; y <= 2; y++)
184 {
185 points.add(new Point2d(x, y));
186 }
187 }
188 expectedResult = new Polygon2d(new Point2d(-1, -2), new Point2d(1, -2), new Point2d(1, 2), new Point2d(-1, 2));
189 checkImplementations(implementations, points, expectedResult);
190 Collections.shuffle(points, new Random(234));
191 checkImplementations(implementations, points, expectedResult);
192
193 points.add(new Point2d(-1.1, 0));
194 points.add(new Point2d(0, -2.1));
195 points.add(new Point2d(1.1, 0));
196 points.add(new Point2d(0, 2.1));
197 expectedResult = new Polygon2d(new Point2d(-1.1, 0), new Point2d(-1, -2), new Point2d(0, -2.1), new Point2d(1, -2),
198 new Point2d(1.1, 0), new Point2d(1, 2), new Point2d(0, 2.1), new Point2d(-1, 2));
199 checkImplementations(implementations, points, expectedResult);
200 Collections.shuffle(points, new Random(345));
201 checkImplementations(implementations, points, expectedResult);
202
203 points.clear();
204 double radius = 5000.0 / 64;
205 double centerX = 1.5;
206 double centerY = 10.5;
207
208 for (double x = centerX - radius; x <= centerX + radius; x += 1)
209 {
210 for (double y = centerY - radius; y <= centerY + radius; y += 1)
211 {
212 double distance = Math.hypot(x - centerX, y - centerY);
213 if (distance <= radius)
214 {
215 points.add(new Point2d(x, y));
216 }
217 }
218 }
219
220
221 expectedResult = ConvexHull.convexHullMonotone(new ArrayList<>(points));
222 checkImplementations(implementations, points, expectedResult);
223 Collections.shuffle(points, new Random(456));
224 checkImplementations(implementations, points, expectedResult);
225 }
226
227
228
229
230
231
232 public static void main(final String[] args) throws IOException
233 {
234 Map<String, ConvexHullImplementation> implementations = getImplementations();
235 List<Point2d> points = new ArrayList<>();
236 double centerX = 1.5;
237 double centerY = 10.5;
238 System.out.println("type return when the profiler is ready");
239 System.in.read();
240 for (double radius = 5000.0 / 64; radius <= 6000; radius *= 2)
241 {
242
243 points.clear();
244 for (double x = centerX - radius; x <= centerX + radius; x += 1)
245 {
246 for (double y = centerY - radius; y <= centerY + radius; y += 1)
247 {
248 double distance = Math.hypot(x - centerX, y - centerY);
249 if (distance <= radius)
250 {
251 points.add(new Point2d(x, y));
252 }
253 }
254 }
255 System.out.print("radius=" + radius + "; ordered input data\n");
256 for (String name : implementations.keySet())
257 {
258 ConvexHullImplementation implementation = implementations.get(name);
259 List<Point2d> workList = new ArrayList<>(points);
260 System.gc();
261 long startNanos = System.nanoTime();
262 implementation.run(workList);
263 long endNanos = System.nanoTime();
264 double duration = (endNanos - startNanos) / 1e9;
265 System.out.println(String.format("%d points %s: %.3f s", points.size(), name, duration));
266 }
267 Collections.shuffle(points, new Random(876));
268 System.out.print("Radius=" + radius + "; scrambled input data\n");
269 for (String name : implementations.keySet())
270 {
271 ConvexHullImplementation implementation = implementations.get(name);
272 List<Point2d> workList = new ArrayList<>(points);
273 System.gc();
274 long startNanos = System.nanoTime();
275 implementation.run(workList);
276 long endNanos = System.nanoTime();
277 double duration = (endNanos - startNanos) / 1e9;
278 System.out.println(String.format("%d points %s: %.3f s", points.size(), name, duration));
279 }
280 }
281 }
282
283
284
285
286
287
288
289 public static void checkImplementations(final Map<String, ConvexHullImplementation> implementations, final List<Point2d> in,
290 final Polygon2d expectedResult)
291 {
292 for (String name : implementations.keySet())
293 {
294 Polygon2d result = implementations.get(name).run(new ArrayList<>(in));
295 if (!result.equals(expectedResult))
296 {
297 System.err.println("Discrepancy for " + name);
298 System.err.println(" result: " + result.toPlot());
299 System.err.println("expectedResult: " + expectedResult.toPlot());
300 System.err.println(" input: " + in);
301 implementations.get(name).run(new ArrayList<>(in));
302 }
303 assertEquals(name, expectedResult, result);
304 }
305
306 }
307
308
309
310
311 interface ConvexHullImplementation
312 {
313
314
315
316
317
318
319
320 Polygon2d run(List<Point2d> points) throws NullPointerException, DrawRuntimeException;
321 }
322
323 }