1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.geometry.euclidean.twod;
18
19 import java.util.ArrayList;
20 import java.util.Arrays;
21 import java.util.Collections;
22 import java.util.function.BiFunction;
23 import java.util.function.ToDoubleFunction;
24 import java.util.regex.Pattern;
25
26 import org.apache.commons.geometry.core.GeometryTestUtils;
27 import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
28 import org.apache.commons.geometry.core.precision.EpsilonDoublePrecisionContext;
29 import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
30 import org.apache.commons.geometry.euclidean.twod.shape.Parallelogram;
31 import org.junit.Assert;
32 import org.junit.Test;
33
34 public class Bounds2DTest {
35
36 private static final double TEST_EPS = 1e-10;
37
38 private static final DoublePrecisionContext TEST_PRECISION =
39 new EpsilonDoublePrecisionContext(TEST_EPS);
40
41 private static final String NO_POINTS_MESSAGE = "Cannot construct bounds: no points given";
42
43 private static final Pattern INVALID_BOUNDS_PATTERN =
44 Pattern.compile("^Invalid bounds: min= \\([^\\)]+\\), max= \\([^\\)]+\\)");
45
46 @Test
47 public void testFrom_varargs_singlePoint() {
48
49 final Vector2D p1 = Vector2D.of(-1, 2);
50
51
52 final Bounds2D b = Bounds2D.from(p1);
53
54
55 EuclideanTestUtils.assertCoordinatesEqual(p1, b.getMin(), TEST_EPS);
56 EuclideanTestUtils.assertCoordinatesEqual(p1, b.getMax(), TEST_EPS);
57 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.ZERO, b.getDiagonal(), TEST_EPS);
58 EuclideanTestUtils.assertCoordinatesEqual(p1, b.getCentroid(), TEST_EPS);
59 }
60
61 @Test
62 public void testFrom_varargs_multiplePoints() {
63
64 final Vector2D p1 = Vector2D.of(1, 6);
65 final Vector2D p2 = Vector2D.of(0, 5);
66 final Vector2D p3 = Vector2D.of(3, 6);
67
68
69 final Bounds2D b = Bounds2D.from(p1, p2, p3);
70
71
72 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0, 5), b.getMin(), TEST_EPS);
73 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(3, 6), b.getMax(), TEST_EPS);
74 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(3, 1), b.getDiagonal(), TEST_EPS);
75 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1.5, 5.5), b.getCentroid(), TEST_EPS);
76 }
77
78 @Test
79 public void testFrom_iterable_singlePoint() {
80
81 final Vector2D p1 = Vector2D.of(-1, 2);
82
83
84 final Bounds2D b = Bounds2D.from(Collections.singletonList(p1));
85
86
87 EuclideanTestUtils.assertCoordinatesEqual(p1, b.getMin(), TEST_EPS);
88 EuclideanTestUtils.assertCoordinatesEqual(p1, b.getMax(), TEST_EPS);
89 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.ZERO, b.getDiagonal(), TEST_EPS);
90 EuclideanTestUtils.assertCoordinatesEqual(p1, b.getCentroid(), TEST_EPS);
91 }
92
93 @Test
94 public void testFrom_iterable_multiplePoints() {
95
96 final Vector2D p1 = Vector2D.of(1, 6);
97 final Vector2D p2 = Vector2D.of(2, 5);
98 final Vector2D p3 = Vector2D.of(3, 4);
99
100
101 final Bounds2D b = Bounds2D.from(Arrays.asList(p1, p2, p3));
102
103
104 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 4), b.getMin(), TEST_EPS);
105 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(3, 6), b.getMax(), TEST_EPS);
106 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(2, 2), b.getDiagonal(), TEST_EPS);
107 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(2, 5), b.getCentroid(), TEST_EPS);
108 }
109
110 @Test
111 public void testFrom_iterable_noPoints() {
112
113 GeometryTestUtils.assertThrows(() -> {
114 Bounds2D.from(new ArrayList<>());
115 }, IllegalStateException.class, NO_POINTS_MESSAGE);
116 }
117
118 @Test
119 public void testFrom_invalidBounds() {
120
121 final Vector2D good = Vector2D.of(1, 1);
122
123 final Vector2D nan = Vector2D.of(Double.NaN, 1);
124 final Vector2D posInf = Vector2D.of(1, Double.POSITIVE_INFINITY);
125 final Vector2D negInf = Vector2D.of(1, Double.NEGATIVE_INFINITY);
126
127
128 GeometryTestUtils.assertThrows(() -> {
129 Bounds2D.from(Vector2D.NaN);
130 }, IllegalStateException.class, INVALID_BOUNDS_PATTERN);
131
132 GeometryTestUtils.assertThrows(() -> {
133 Bounds2D.from(Vector2D.POSITIVE_INFINITY);
134 }, IllegalStateException.class, INVALID_BOUNDS_PATTERN);
135
136 GeometryTestUtils.assertThrows(() -> {
137 Bounds2D.from(Vector2D.NEGATIVE_INFINITY);
138 }, IllegalStateException.class, INVALID_BOUNDS_PATTERN);
139
140 GeometryTestUtils.assertThrows(() -> {
141 Bounds2D.from(good, nan);
142 }, IllegalStateException.class, INVALID_BOUNDS_PATTERN);
143
144 GeometryTestUtils.assertThrows(() -> {
145 Bounds2D.from(posInf, good);
146 }, IllegalStateException.class, INVALID_BOUNDS_PATTERN);
147
148 GeometryTestUtils.assertThrows(() -> {
149 Bounds2D.from(good, negInf, good);
150 }, IllegalStateException.class, INVALID_BOUNDS_PATTERN);
151 }
152
153 @Test
154 public void testHasSize() {
155
156 final DoublePrecisionContext low = new EpsilonDoublePrecisionContext(1e-2);
157 final DoublePrecisionContext high = new EpsilonDoublePrecisionContext(1e-10);
158
159 final Vector2D p1 = Vector2D.ZERO;
160
161 final Vector2D p2 = Vector2D.of(1e-5, 1);
162 final Vector2D p3 = Vector2D.of(1, 1e-5);
163
164 final Vector2D p4 = Vector2D.of(1, 1);
165
166
167 Assert.assertFalse(Bounds2D.from(p1).hasSize(high));
168 Assert.assertFalse(Bounds2D.from(p1).hasSize(low));
169
170 Assert.assertTrue(Bounds2D.from(p1, p2).hasSize(high));
171 Assert.assertFalse(Bounds2D.from(p1, p2).hasSize(low));
172
173 Assert.assertTrue(Bounds2D.from(p1, p3).hasSize(high));
174 Assert.assertFalse(Bounds2D.from(p1, p3).hasSize(low));
175
176 Assert.assertTrue(Bounds2D.from(p1, p4).hasSize(high));
177 Assert.assertTrue(Bounds2D.from(p1, p4).hasSize(low));
178 }
179
180 @Test
181 public void testContains_strict() {
182
183 final Bounds2D b = Bounds2D.from(
184 Vector2D.of(0, 4),
185 Vector2D.of(2, 6));
186
187
188 assertContainsStrict(b, true,
189 b.getCentroid(),
190 Vector2D.of(0, 4), Vector2D.of(2, 6),
191 Vector2D.of(1, 5),
192 Vector2D.of(0, 5), Vector2D.of(2, 5),
193 Vector2D.of(1, 4), Vector2D.of(1, 6));
194
195 assertContainsStrict(b, false,
196 Vector2D.ZERO,
197 Vector2D.of(-1, 5), Vector2D.of(3, 5),
198 Vector2D.of(1, 3), Vector2D.of(1, 7),
199 Vector2D.of(-1e-15, 4), Vector2D.of(2, 6 + 1e-15));
200 }
201
202 @Test
203 public void testContains_precision() {
204
205 final Bounds2D b = Bounds2D.from(
206 Vector2D.of(0, 4),
207 Vector2D.of(2, 6));
208
209
210 assertContainsWithPrecision(b, true,
211 b.getCentroid(),
212 Vector2D.of(1, 5), Vector2D.of(0, 4), Vector2D.of(2, 6),
213 Vector2D.of(0, 5), Vector2D.of(2, 5),
214 Vector2D.of(1, 4), Vector2D.of(1, 6),
215 Vector2D.of(-1e-15, 4), Vector2D.of(2, 6 + 1e-15));
216
217 assertContainsWithPrecision(b, false,
218 Vector2D.ZERO,
219 Vector2D.of(-1, 5), Vector2D.of(3, 5),
220 Vector2D.of(1, 3), Vector2D.of(1, 7));
221 }
222
223 @Test
224 public void testIntersects() {
225
226 final Bounds2D b = Bounds2D.from(Vector2D.ZERO, Vector2D.of(1, 1));
227
228
229 checkIntersects(b, Vector2D::getX, (v, x) -> Vector2D.of(x, v.getY()));
230 checkIntersects(b, Vector2D::getY, (v, y) -> Vector2D.of(v.getX(), y));
231 }
232
233 private void checkIntersects(final Bounds2D b, final ToDoubleFunction<Vector2D> getter,
234 final BiFunction<Vector2D, Double, Vector2D> setter) {
235
236 final Vector2D min = b.getMin();
237 final Vector2D max = b.getMax();
238
239 final double minValue = getter.applyAsDouble(min);
240 final double maxValue = getter.applyAsDouble(max);
241 final double midValue = (0.5 * (maxValue - minValue)) + minValue;
242
243
244
245
246 Assert.assertFalse(b.intersects(Bounds2D.from(
247 setter.apply(min, minValue - 2), setter.apply(max, minValue - 1))));
248
249 Assert.assertTrue(b.intersects(Bounds2D.from(
250 setter.apply(min, minValue - 2), setter.apply(max, minValue))));
251 Assert.assertTrue(b.intersects(Bounds2D.from(
252 setter.apply(min, minValue - 2), setter.apply(max, midValue))));
253 Assert.assertTrue(b.intersects(Bounds2D.from(
254 setter.apply(min, minValue - 2), setter.apply(max, maxValue))));
255 Assert.assertTrue(b.intersects(Bounds2D.from(
256 setter.apply(min, minValue - 2), setter.apply(max, maxValue + 1))));
257
258
259 Assert.assertTrue(b.intersects(Bounds2D.from(
260 setter.apply(min, minValue), setter.apply(max, minValue))));
261 Assert.assertTrue(b.intersects(Bounds2D.from(
262 setter.apply(min, minValue), setter.apply(max, midValue))));
263 Assert.assertTrue(b.intersects(Bounds2D.from(
264 setter.apply(min, minValue), setter.apply(max, maxValue))));
265 Assert.assertTrue(b.intersects(Bounds2D.from(
266 setter.apply(min, minValue), setter.apply(max, maxValue + 1))));
267
268
269 Assert.assertTrue(b.intersects(Bounds2D.from(
270 setter.apply(min, midValue), setter.apply(max, midValue))));
271 Assert.assertTrue(b.intersects(Bounds2D.from(
272 setter.apply(min, midValue), setter.apply(max, maxValue))));
273 Assert.assertTrue(b.intersects(Bounds2D.from(
274 setter.apply(min, midValue), setter.apply(max, maxValue + 1))));
275
276
277 Assert.assertTrue(b.intersects(Bounds2D.from(
278 setter.apply(min, maxValue), setter.apply(max, maxValue))));
279 Assert.assertTrue(b.intersects(Bounds2D.from(
280 setter.apply(min, maxValue), setter.apply(max, maxValue + 1))));
281
282
283 Assert.assertFalse(b.intersects(Bounds2D.from(
284 setter.apply(min, maxValue + 1), setter.apply(max, maxValue + 2))));
285 }
286
287 @Test
288 public void testIntersection() {
289
290 final Bounds2D b = Bounds2D.from(Vector2D.ZERO, Vector2D.of(1, 1));
291
292
293
294
295 Assert.assertNull(b.intersection(Bounds2D.from(Vector2D.of(-2, 0), Vector2D.of(-1, 1))));
296 checkIntersection(b, Vector2D.of(-1, 0), Vector2D.of(0, 1),
297 Vector2D.of(0, 0), Vector2D.of(0, 1));
298 checkIntersection(b, Vector2D.of(-1, 0), Vector2D.of(0.5, 1),
299 Vector2D.of(0, 0), Vector2D.of(0.5, 1));
300 checkIntersection(b, Vector2D.of(-1, 0), Vector2D.of(1, 1),
301 Vector2D.of(0, 0), Vector2D.of(1, 1));
302 checkIntersection(b, Vector2D.of(-1, 0), Vector2D.of(2, 1),
303 Vector2D.of(0, 0), Vector2D.of(1, 1));
304 checkIntersection(b, Vector2D.of(0, 0), Vector2D.of(2, 1),
305 Vector2D.of(0, 0), Vector2D.of(1, 1));
306 checkIntersection(b, Vector2D.of(0.5, 0), Vector2D.of(2, 1),
307 Vector2D.of(0.5, 0), Vector2D.of(1, 1));
308 checkIntersection(b, Vector2D.of(1, 0), Vector2D.of(2, 1),
309 Vector2D.of(1, 0), Vector2D.of(1, 1));
310 Assert.assertNull(b.intersection(Bounds2D.from(Vector2D.of(2, 0), Vector2D.of(3, 1))));
311
312
313 Assert.assertNull(b.intersection(Bounds2D.from(Vector2D.of(0, -2), Vector2D.of(1, -1))));
314 checkIntersection(b, Vector2D.of(0, -1), Vector2D.of(1, 0),
315 Vector2D.of(0, 0), Vector2D.of(1, 0));
316 checkIntersection(b, Vector2D.of(0, -1), Vector2D.of(1, 0.5),
317 Vector2D.of(0, 0), Vector2D.of(1, 0.5));
318 checkIntersection(b, Vector2D.of(0, -1), Vector2D.of(1, 1),
319 Vector2D.of(0, 0), Vector2D.of(1, 1));
320 checkIntersection(b, Vector2D.of(0, -1), Vector2D.of(1, 2),
321 Vector2D.of(0, 0), Vector2D.of(1, 1));
322 checkIntersection(b, Vector2D.of(0, 0), Vector2D.of(1, 2),
323 Vector2D.of(0, 0), Vector2D.of(1, 1));
324 checkIntersection(b, Vector2D.of(0, 0.5), Vector2D.of(1, 2),
325 Vector2D.of(0, 0.5), Vector2D.of(1, 1));
326 checkIntersection(b, Vector2D.of(0, 1), Vector2D.of(1, 2),
327 Vector2D.of(0, 1), Vector2D.of(1, 1));
328 Assert.assertNull(b.intersection(Bounds2D.from(Vector2D.of(0, 2), Vector2D.of(1, 3))));
329 }
330
331 private void checkIntersection(final Bounds2D b, final Vector2D a1, final Vector2D a2, final Vector2D r1, final Vector2D r2) {
332 final Bounds2D a = Bounds2D.from(a1, a2);
333 final Bounds2D result = b.intersection(a);
334
335 checkBounds(result, r1, r2);
336 }
337
338 @Test
339 public void toRegion() {
340
341 final Bounds2D b = Bounds2D.from(
342 Vector2D.of(0, 4),
343 Vector2D.of(2, 6));
344
345
346 final Parallelogram p = b.toRegion(TEST_PRECISION);
347
348
349 Assert.assertEquals(4, p.getSize(), TEST_EPS);
350 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 5), p.getCentroid(), TEST_EPS);
351 }
352
353 @Test
354 public void toRegion_boundingBoxTooSmall() {
355
356 GeometryTestUtils.assertThrows(() -> {
357 Bounds2D.from(Vector2D.ZERO, Vector2D.of(1e-12, 1e-12))
358 .toRegion(TEST_PRECISION);
359 }, IllegalArgumentException.class);
360 }
361
362 @Test
363 public void testEq() {
364
365 final DoublePrecisionContext low = new EpsilonDoublePrecisionContext(1e-2);
366 final DoublePrecisionContext high = new EpsilonDoublePrecisionContext(1e-10);
367
368 final Bounds2D b1 = Bounds2D.from(Vector2D.of(1, 1), Vector2D.of(2, 2));
369
370 final Bounds2D b2 = Bounds2D.from(Vector2D.of(1.1, 1), Vector2D.of(2, 2));
371 final Bounds2D b3 = Bounds2D.from(Vector2D.of(1, 1), Vector2D.of(1.9, 2));
372
373 final Bounds2D b4 = Bounds2D.from(Vector2D.of(1.001, 1.001), Vector2D.of(2.001, 2.001));
374
375
376 Assert.assertTrue(b1.eq(b1, low));
377
378 Assert.assertFalse(b1.eq(b2, low));
379 Assert.assertFalse(b1.eq(b3, low));
380
381 Assert.assertTrue(b1.eq(b4, low));
382 Assert.assertTrue(b4.eq(b1, low));
383
384 Assert.assertFalse(b1.eq(b4, high));
385 Assert.assertFalse(b4.eq(b1, high));
386 }
387
388 @Test
389 public void testHashCode() {
390
391 final Bounds2D b1 = Bounds2D.from(Vector2D.of(1, 1), Vector2D.of(2, 2));
392
393 final Bounds2D b2 = Bounds2D.from(Vector2D.of(-2, 1), Vector2D.of(2, 2));
394 final Bounds2D b3 = Bounds2D.from(Vector2D.of(1, 1), Vector2D.of(3, 2));
395 final Bounds2D b4 = Bounds2D.from(Vector2D.of(1 + 1e-15, 1), Vector2D.of(2, 2));
396 final Bounds2D b5 = Bounds2D.from(Vector2D.of(1, 1), Vector2D.of(2 + 1e-15, 2));
397
398 final Bounds2D b6 = Bounds2D.from(Vector2D.of(1, 1), Vector2D.of(2, 2));
399
400
401 final int hash = b1.hashCode();
402
403
404 Assert.assertEquals(hash, b1.hashCode());
405
406 Assert.assertNotEquals(hash, b2.hashCode());
407 Assert.assertNotEquals(hash, b3.hashCode());
408 Assert.assertNotEquals(hash, b4.hashCode());
409 Assert.assertNotEquals(hash, b5.hashCode());
410
411 Assert.assertEquals(hash, b6.hashCode());
412 }
413
414 @Test
415 public void testEquals() {
416
417 final Bounds2D b1 = Bounds2D.from(Vector2D.of(1, 1), Vector2D.of(2, 2));
418
419 final Bounds2D b2 = Bounds2D.from(Vector2D.of(-1, 1), Vector2D.of(2, 2));
420 final Bounds2D b3 = Bounds2D.from(Vector2D.of(1, 1), Vector2D.of(3, 2));
421 final Bounds2D b4 = Bounds2D.from(Vector2D.of(1 + 1e-15, 1), Vector2D.of(2, 2));
422 final Bounds2D b5 = Bounds2D.from(Vector2D.of(1, 1), Vector2D.of(2 + 1e-15, 2));
423
424 final Bounds2D b6 = Bounds2D.from(Vector2D.of(1, 1), Vector2D.of(2, 2));
425
426
427 Assert.assertEquals(b1, b1);
428
429 Assert.assertFalse(b1.equals(null));
430 Assert.assertFalse(b1.equals(new Object()));
431
432 Assert.assertNotEquals(b1, b2);
433 Assert.assertNotEquals(b1, b3);
434 Assert.assertNotEquals(b1, b4);
435 Assert.assertNotEquals(b1, b5);
436
437 Assert.assertEquals(b1, b6);
438 }
439
440 @Test
441 public void testToString() {
442
443 final Bounds2D b = Bounds2D.from(Vector2D.of(1, 1), Vector2D.of(2, 2));
444
445
446 final String str = b.toString();
447
448
449 GeometryTestUtils.assertContains("Bounds2D[min= (1", str);
450 GeometryTestUtils.assertContains(", max= (2", str);
451 }
452
453 @Test
454 public void testBuilder_addMethods() {
455
456 final Vector2D p1 = Vector2D.of(1, 10);
457 final Vector2D p2 = Vector2D.of(2, 9);
458 final Vector2D p3 = Vector2D.of(3, 8);
459 final Vector2D p4 = Vector2D.of(4, 7);
460 final Vector2D p5 = Vector2D.of(5, 6);
461
462
463 final Bounds2D b = Bounds2D.builder()
464 .add(p1)
465 .addAll(Arrays.asList(p2, p3))
466 .add(Bounds2D.from(p4, p5))
467 .build();
468
469
470 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 6), b.getMin(), TEST_EPS);
471 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(5, 10), b.getMax(), TEST_EPS);
472 EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(3, 8), b.getCentroid(), TEST_EPS);
473 }
474
475 @Test
476 public void testBuilder_hasBounds() {
477
478 Assert.assertFalse(Bounds2D.builder().hasBounds());
479
480 Assert.assertFalse(Bounds2D.builder().add(Vector2D.of(Double.NaN, 1)).hasBounds());
481 Assert.assertFalse(Bounds2D.builder().add(Vector2D.of(1, Double.NaN)).hasBounds());
482
483 Assert.assertFalse(Bounds2D.builder().add(Vector2D.of(Double.POSITIVE_INFINITY, 1)).hasBounds());
484 Assert.assertFalse(Bounds2D.builder().add(Vector2D.of(1, Double.POSITIVE_INFINITY)).hasBounds());
485
486 Assert.assertFalse(Bounds2D.builder().add(Vector2D.of(Double.NEGATIVE_INFINITY, 1)).hasBounds());
487 Assert.assertFalse(Bounds2D.builder().add(Vector2D.of(1, Double.NEGATIVE_INFINITY)).hasBounds());
488
489 Assert.assertTrue(Bounds2D.builder().add(Vector2D.ZERO).hasBounds());
490 }
491
492 private static void checkBounds(final Bounds2D b, final Vector2D min, final Vector2D max) {
493 EuclideanTestUtils.assertCoordinatesEqual(min, b.getMin(), TEST_EPS);
494 EuclideanTestUtils.assertCoordinatesEqual(max, b.getMax(), TEST_EPS);
495 }
496
497 private static void assertContainsStrict(final Bounds2D bounds, final boolean contains, final Vector2D... pts) {
498 for (final Vector2D pt : pts) {
499 Assert.assertEquals("Unexpected location for point " + pt, contains, bounds.contains(pt));
500 }
501 }
502
503 private static void assertContainsWithPrecision(final Bounds2D bounds, final boolean contains, final Vector2D... pts) {
504 for (final Vector2D pt : pts) {
505 Assert.assertEquals("Unexpected location for point " + pt, contains, bounds.contains(pt, TEST_PRECISION));
506 }
507 }
508 }