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