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