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