1 package org.djutils.draw.bounds;
2
3 import java.awt.geom.Rectangle2D;
4 import java.util.Arrays;
5 import java.util.Collection;
6 import java.util.Iterator;
7 import java.util.Locale;
8
9 import org.djutils.draw.Drawable2d;
10 import org.djutils.draw.point.Point2d;
11 import org.djutils.exceptions.Throw;
12
13
14
15
16
17
18
19
20
21
22
23 public class Bounds2d implements Drawable2d, Bounds<Bounds2d, Point2d, Drawable2d>
24 {
25
26 private static final long serialVersionUID = 20200829L;
27
28
29 private final double minX;
30
31
32 private final double minY;
33
34
35 private final double maxX;
36
37
38 private final double maxY;
39
40
41
42
43
44
45
46
47
48
49 public Bounds2d(final double minX, final double maxX, final double minY, final double maxY) throws IllegalArgumentException
50 {
51 Throw.when(Double.isNaN(minX) || Double.isNaN(maxX) || Double.isNaN(minY) || Double.isNaN(maxY),
52 IllegalArgumentException.class, "bounds must be numbers (not NaN)");
53 Throw.when(minX > maxX || minY > maxY, IllegalArgumentException.class,
54 "lower bound for each dimension should be less than or equal to its upper bound");
55 this.minX = minX;
56 this.minY = minY;
57 this.maxX = maxX;
58 this.maxY = maxY;
59 }
60
61
62
63
64
65
66
67 public Bounds2d(final double deltaX, final double deltaY)
68 {
69 this(-0.5 * deltaX, 0.5 * deltaX, -0.5 * deltaY, 0.5 * deltaY);
70 }
71
72
73
74
75
76
77
78
79 public Bounds2d(final Iterator<? extends Point2d> points)
80 {
81 Throw.whenNull(points, "points may not be null");
82 Throw.when(!points.hasNext(), IllegalArgumentException.class, "need at least one point");
83 Point2d point = points.next();
84 double tempMinX = point.x;
85 double tempMaxX = point.x;
86 double tempMinY = point.y;
87 double tempMaxY = point.y;
88 while (points.hasNext())
89 {
90 point = points.next();
91 tempMinX = Math.min(tempMinX, point.x);
92 tempMaxX = Math.max(tempMaxX, point.x);
93 tempMinY = Math.min(tempMinY, point.y);
94 tempMaxY = Math.max(tempMaxY, point.y);
95 }
96 this.minX = tempMinX;
97 this.maxX = tempMaxX;
98 this.minY = tempMinY;
99 this.maxY = tempMaxY;
100 }
101
102
103
104
105
106
107
108 public Bounds2d(final Point2d[] points) throws NullPointerException, IllegalArgumentException
109 {
110 this(Arrays.stream(Throw.whenNull(points, "points may not be null")).iterator());
111 }
112
113
114
115
116
117
118 public Bounds2d(final Drawable2d drawable2d) throws NullPointerException
119 {
120 this(Throw.whenNull(drawable2d, "drawable2d may not be null").getPoints());
121 }
122
123
124
125
126
127
128
129 public Bounds2d(final Drawable2d... drawable2d) throws NullPointerException, IllegalArgumentException
130 {
131 this(pointsOf(drawable2d));
132 }
133
134
135
136
137
138
139
140
141 static Drawable2d[] ensureHasOne(final Drawable2d[] drawable2dArray) throws NullPointerException, IllegalArgumentException
142 {
143 Throw.whenNull(drawable2dArray, "Array may not be null");
144 Throw.when(drawable2dArray.length == 0, IllegalArgumentException.class, "Array must contain at least one value");
145 return drawable2dArray;
146 }
147
148
149
150
151
152
153
154
155 public static Iterator<Point2d> pointsOf(final Drawable2d... drawable2d)
156 {
157 return new Iterator<Point2d>()
158 {
159
160 private int nextArgument = 0;
161
162
163 private Iterator<? extends Point2d> currentIterator = ensureHasOne(drawable2d)[0].getPoints();
164
165 @Override
166 public boolean hasNext()
167 {
168 return this.nextArgument < drawable2d.length - 1 || this.currentIterator.hasNext();
169 }
170
171 @Override
172 public Point2d next()
173 {
174 if (this.currentIterator.hasNext())
175 {
176 return this.currentIterator.next();
177 }
178
179 this.nextArgument++;
180 this.currentIterator = drawable2d[this.nextArgument].getPoints();
181 return this.currentIterator.next();
182 }
183 };
184 }
185
186
187
188
189
190
191
192 public Bounds2d(final Collection<Drawable2d> drawableCollection) throws NullPointerException, IllegalArgumentException
193 {
194 this(pointsOf(drawableCollection));
195 }
196
197
198
199
200
201
202
203
204 public static Iterator<Point2d> pointsOf(final Collection<Drawable2d> drawableCollection)
205 throws NullPointerException, IllegalArgumentException
206 {
207 return new Iterator<Point2d>()
208 {
209
210 private Iterator<Drawable2d> collectionIterator = ensureHasOne(drawableCollection.iterator());
211
212
213 private Iterator<? extends Point2d> currentIterator = this.collectionIterator.next().getPoints();
214
215 @Override
216 public boolean hasNext()
217 {
218 if (this.currentIterator == null)
219 {
220 return false;
221 }
222 return this.currentIterator.hasNext();
223 }
224
225 @Override
226 public Point2d next()
227 {
228 Point2d result = this.currentIterator.next();
229 if (!this.currentIterator.hasNext())
230 {
231 if (this.collectionIterator.hasNext())
232 {
233 this.currentIterator = this.collectionIterator.next().getPoints();
234 }
235 else
236 {
237 this.currentIterator = null;
238 }
239 }
240 return result;
241 }
242 };
243 }
244
245
246
247
248
249
250
251
252 public static Iterator<Drawable2d> ensureHasOne(final Iterator<Drawable2d> iterator)
253 throws NullPointerException, IllegalArgumentException
254 {
255 Throw.when(!iterator.hasNext(), IllegalArgumentException.class, "Collection may not be empty");
256 return iterator;
257 }
258
259
260 @Override
261 public Iterator<Point2d> getPoints()
262 {
263 Point2d[] array = new Point2d[] { new Point2d(this.minX, this.minY), new Point2d(this.minX, this.maxY),
264 new Point2d(this.maxX, this.minY), new Point2d(this.maxX, this.maxY) };
265 return Arrays.stream(array).iterator();
266 }
267
268
269 @Override
270 public int size()
271 {
272 return 4;
273 }
274
275
276
277
278
279
280
281
282 public boolean contains(final double x, final double y) throws IllegalArgumentException
283 {
284 Throw.when(Double.isNaN(x) || Double.isNaN(y), IllegalArgumentException.class, "coordinates must be numbers (not NaN)");
285 return x > this.minX && x < this.maxX && y > this.minY && y < this.maxY;
286 }
287
288
289 @Override
290 public boolean contains(final Point2d point)
291 {
292 Throw.whenNull(point, "point cannot be null");
293 return contains(point.x, point.y);
294 }
295
296
297 @Override
298 public boolean contains(final Drawable2d drawable) throws NullPointerException
299 {
300 Throw.whenNull(drawable, "drawable cannot be null");
301 Bounds2d bounds = drawable.getBounds();
302 return contains(bounds.minX, bounds.minY) && contains(bounds.maxX, bounds.maxY);
303 }
304
305
306
307
308
309
310
311 public boolean covers(final double x, final double y)
312 {
313 Throw.when(Double.isNaN(x) || Double.isNaN(y), IllegalArgumentException.class, "coordinates must be numbers (not NaN)");
314 return x >= this.minX && x <= this.maxX && y >= this.minY && y <= this.maxY;
315 }
316
317
318 @Override
319 public boolean covers(final Point2d point)
320 {
321 Throw.whenNull(point, "point cannot be null");
322 return covers(point.x, point.y);
323 }
324
325
326 @Override
327 public boolean covers(final Drawable2d drawable) throws NullPointerException
328 {
329 Throw.whenNull(drawable, "drawable cannot be null");
330 Bounds2d bounds = drawable.getBounds();
331 return covers(bounds.minX, bounds.minY) && covers(bounds.maxX, bounds.maxY);
332 }
333
334
335 @Override
336 public boolean disjoint(final Drawable2d drawable) throws NullPointerException
337 {
338 Throw.whenNull(drawable, "drawable cannot be null");
339 Bounds2d bounds = drawable.getBounds();
340 return bounds.minX >= this.maxX || bounds.maxX <= this.minX || bounds.minY >= this.maxY || bounds.maxY <= this.minY;
341 }
342
343
344 @Override
345 public boolean intersects(final Bounds2d otherBounds2d) throws NullPointerException
346 {
347 return !disjoint(otherBounds2d);
348 }
349
350
351 @Override
352 public Bounds2d intersection(final Bounds2d otherBounds2d)
353 {
354 Throw.whenNull(otherBounds2d, "otherBounds2d cannot be null");
355 if (disjoint(otherBounds2d))
356 {
357 return null;
358 }
359 return new Bounds2d(Math.max(this.minX, otherBounds2d.minX), Math.min(this.maxX, otherBounds2d.maxX),
360 Math.max(this.minY, otherBounds2d.minY), Math.min(this.maxY, otherBounds2d.maxY));
361 }
362
363
364
365
366
367 public Rectangle2D toRectangle2D()
368 {
369 return new Rectangle2D.Double(this.minX, this.minY, this.maxX - this.minX, this.maxY - this.minY);
370 }
371
372
373 @Override
374 public double getMinX()
375 {
376 return this.minX;
377 }
378
379
380 @Override
381 public double getMaxX()
382 {
383 return this.maxX;
384 }
385
386
387 @Override
388 public double getMinY()
389 {
390 return this.minY;
391 }
392
393
394 @Override
395 public double getMaxY()
396 {
397 return this.maxY;
398 }
399
400
401 @Override
402 public Point2d midPoint()
403 {
404 return new Point2d((this.minX + this.maxX) / 2, (this.minY + this.maxY) / 2);
405 }
406
407
408
409
410
411 public double getArea()
412 {
413 return getDeltaX() * getDeltaY();
414 }
415
416
417 @Override
418 public Bounds2d getBounds()
419 {
420 return this;
421 }
422
423
424 @Override
425 public String toString()
426 {
427 return toString("%f");
428 }
429
430
431 @Override
432 public String toString(final String doubleFormat, final boolean doNotIncludeClassName)
433 {
434 String format =
435 String.format("%1$s[x[%2$s : %2$s], y[%2$s : %2$s]]", doNotIncludeClassName ? "" : "Bounds2d ", doubleFormat);
436 return String.format(Locale.US, format, this.minX, this.maxX, this.minY, this.maxY);
437 }
438
439
440 @Override
441 public int hashCode()
442 {
443 final int prime = 31;
444 int result = 1;
445 long temp;
446 temp = Double.doubleToLongBits(this.maxX);
447 result = prime * result + (int) (temp ^ (temp >>> 32));
448 temp = Double.doubleToLongBits(this.maxY);
449 result = prime * result + (int) (temp ^ (temp >>> 32));
450 temp = Double.doubleToLongBits(this.minX);
451 result = prime * result + (int) (temp ^ (temp >>> 32));
452 temp = Double.doubleToLongBits(this.minY);
453 result = prime * result + (int) (temp ^ (temp >>> 32));
454 return result;
455 }
456
457
458 @SuppressWarnings("checkstyle:needbraces")
459 @Override
460 public boolean equals(final Object obj)
461 {
462 if (this == obj)
463 return true;
464 if (obj == null)
465 return false;
466 if (getClass() != obj.getClass())
467 return false;
468 Bounds2d other = (Bounds2d) obj;
469 if (Double.doubleToLongBits(this.maxX) != Double.doubleToLongBits(other.maxX))
470 return false;
471 if (Double.doubleToLongBits(this.maxY) != Double.doubleToLongBits(other.maxY))
472 return false;
473 if (Double.doubleToLongBits(this.minX) != Double.doubleToLongBits(other.minX))
474 return false;
475 if (Double.doubleToLongBits(this.minY) != Double.doubleToLongBits(other.minY))
476 return false;
477 return true;
478 }
479
480 }