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