View Javadoc
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   
8   import org.djutils.draw.Drawable2d;
9   import org.djutils.draw.Space2d;
10  import org.djutils.draw.point.Point2d;
11  import org.djutils.exceptions.Throw;
12  
13  /**
14   * A Bounds2d stores the rectangular 2D bounds of a 2d object, or a collection of 2d objects. The Bounds2d is an immutable
15   * object.
16   * <p>
17   * Copyright (c) 2020-2021 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
18   * BSD-style license. See <a href="https://djutils.org/docs/current/djutils/licenses.html">DJUTILS License</a>.
19   * </p>
20   * @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
21   * @author <a href="https://www.tudelft.nl/pknoppers">Peter Knoppers</a>
22   */
23  public class Bounds2d implements Drawable2d, Bounds<Bounds2d, Space2d>
24  {
25      /** */
26      private static final long serialVersionUID = 20200829L;
27  
28      /** The lower bound for x. */
29      private final double minX;
30  
31      /** The lower bound for y. */
32      private final double minY;
33  
34      /** The upper bound for x. */
35      private final double maxX;
36  
37      /** The upper bound for y. */
38      private final double maxY;
39  
40      /**
41       * Construct a Bounds2d by providing its lower and upper bounds in both dimensions.
42       * @param minX double; the lower bound for x
43       * @param maxX double; the upper bound for x
44       * @param minY double; the lower bound for y
45       * @param maxY double; the upper bound for y
46       * @throws IllegalArgumentException when a lower bound is larger than the corresponding upper bound, or any of the bounds is
47       *             NaN
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       * Constructs a new Bounds2d around the origin (0, 0).
63       * @param deltaX double; the deltaX value around the origin
64       * @param deltaY double; the deltaY value around the origin
65       * @throws IllegalArgumentException when one of the delta values is less than zero
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       * Construct a Bounds2d from some collection of points, finding the lowest and highest x and y coordinates.
74       * @param points Iterator&lt;? extends Point2d&gt;; Iterator that will generate all the points for which to construct a
75       *            Bounds2d
76       * @throws NullPointerException when points is null
77       * @throws IllegalArgumentException when the iterator provides zero points
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      * Construct a Bounds2d from an array of Point2d, finding the lowest and highest x and y coordinates.
104      * @param points Point2d[]; the points to construct a Bounds2d from
105      * @throws NullPointerException when points is null
106      * @throws IllegalArgumentException when zero points are provided
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      * Construct a Bounds2d for a Drawable2d.
115      * @param drawable2d Drawable2d; any object that implements the Drawable2d interface
116      * @throws NullPointerException when drawable2d is null
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      * Construct a Bounds2d from a collection of Point2d, finding the lowest and highest x and y coordinates.
125      * @param points Collection&lt;Point2d&gt;; the collection of points to construct a Bounds2d from
126      * @throws NullPointerException when points is null
127      * @throws IllegalArgumentException when the collection is empty
128      */
129     public Bounds2d(final Collection<Point2d> points)
130     {
131         this(Throw.whenNull(points, "points may not be null").iterator());
132     }
133 
134     /** {@inheritDoc} */
135     @Override
136     public Iterator<Point2d> getPoints()
137     {
138         Point2d">Point2dPoint2d.html#Point2d">Point2d.html#Point2d">Point2d[] array = new Point2d">Point2dPoint2d.html#Point2d">Point2d[] {new Point2d">Point2d(getMinX(), getMinY()), new Point2d(getMinX(), getMaxY()),
139                 new Point2d">Point2d(getMaxX(), getMinY()), new Point2d(getMaxX(), getMaxY())};
140         return Arrays.stream(array).iterator();
141     }
142 
143     /** {@inheritDoc} */
144     @Override
145     public int size()
146     {
147         return 4;
148     }
149 
150     /**
151      * Check if this Bounds2d contains a given point. Contains considers a point <b>on</b> the border of this Bounds2d to be
152      * outside.
153      * @param point Point2d; the point
154      * @return boolean; true this Bounds2d contains the point; false if this Bounds2d does <b>not</b> contain the point
155      * @throws NullPointerException when point is null
156      */
157     public boolean contains(final Point2d point)
158     {
159         Throw.whenNull(point, "point cannot be null");
160         return contains(point.x, point.y);
161     }
162 
163     /**
164      * Check if this Bounds2d contains a point. Contains considers a point <b>on</b> the border of this Bounds2d to be outside.
165      * @param x double; the x-coordinate of the point
166      * @param y double; the y-coordinate of the point
167      * @return boolean; whether this Bounds2d contains the point
168      * @throws IllegalArgumentException when any of the coordinates is NaN
169      */
170     public boolean contains(final double x, final double y) throws IllegalArgumentException
171     {
172         Throw.when(Double.isNaN(x) || Double.isNaN(y), IllegalArgumentException.class, "coordinates must be numbers (not NaN)");
173         return x > this.minX && x < this.maxX && y > this.minY && y < this.maxY;
174     }
175 
176     /**
177      * Check if this Bounds2d completely contains a Drawable2d.
178      * @param drawable Drawable2d; the object for which to check if it is completely contained within this Bounds2d.
179      * @return boolean; false if any point of the Drawable2d is on or outside one of the borders of this Bounds2d; true when all
180      *         points of the Drawable2d are contained within this Bounds2d.
181      * @throws NullPointerException when drawable2d is null
182      */
183     public boolean contains(final Drawable2d drawable) throws NullPointerException
184     {
185         Throw.whenNull(drawable, "drawable cannot be null");
186         for (Iterator<? extends Point2d> iterator = drawable.getPoints(); iterator.hasNext();)
187         {
188             if (!contains(iterator.next()))
189             {
190                 return false;
191             }
192         }
193         return true;
194     }
195 
196     /**
197      * Check if this Bounds2d contains a point. Covers returns true when the point is on, or within the border of this Bounds2d.
198      * @param x double; the x-coordinate of the point
199      * @param y double; the y-coordinate of the point
200      * @return boolean; whether this Bounds2d, including its borders, contains the point
201      */
202     public boolean covers(final double x, final double y)
203     {
204         Throw.when(Double.isNaN(x) || Double.isNaN(y), IllegalArgumentException.class, "coordinates must be numbers (not NaN)");
205         return x >= this.minX && x <= this.maxX && y >= this.minY && y <= this.maxY;
206     }
207 
208     /**
209      * Check if this Bounds2d contains a point. Covers returns true when the point is on, or within the border of this Bounds2d.
210      * @param point Point2d; the point
211      * @return boolean; whether this Bounds2d, including its borders, contains the point
212      * @throws NullPointerException when point is null
213      */
214     public boolean covers(final Point2d point)
215     {
216         Throw.whenNull(point, "point cannot be null");
217         return covers(point.x, point.y);
218     }
219 
220     /** {@inheritDoc} */
221     @Override
222     public boolean covers(final Bounds2d otherBounds2d) throws NullPointerException
223     {
224         Throw.whenNull(otherBounds2d, "otherBounds2d cannot be null");
225         return covers(otherBounds2d.minX, otherBounds2d.minY) && covers(otherBounds2d.maxX, otherBounds2d.maxY);
226     }
227 
228     /** {@inheritDoc} */
229     @Override
230     public boolean disjoint(final Bounds2d otherBounds2d) throws NullPointerException
231     {
232         Throw.whenNull(otherBounds2d, "otherBounds2d cannot be null");
233         return otherBounds2d.minX >= this.maxX || otherBounds2d.maxX <= this.minX || otherBounds2d.minY >= this.maxY
234                 || otherBounds2d.maxY <= this.minY;
235     }
236 
237     /** {@inheritDoc} */
238     @Override
239     public boolean intersects(final Bounds2d otherBounds2d) throws NullPointerException
240     {
241         return !disjoint(otherBounds2d);
242     }
243 
244     /** {@inheritDoc} */
245     @Override
246     public Bounds2dl#Bounds2d">Bounds2d intersection(final Bounds2d otherBounds2d)
247     {
248         Throw.whenNull(otherBounds2d, "otherBounds2d cannot be null");
249         if (disjoint(otherBounds2d))
250         {
251             return null;
252         }
253         return new Bounds2d(Math.max(this.getMinX(), otherBounds2d.getMinX()),
254                 Math.min(this.getMaxX(), otherBounds2d.getMaxX()), Math.max(this.getMinY(), otherBounds2d.getMinY()),
255                 Math.min(this.getMaxY(), otherBounds2d.getMaxY()));
256     }
257 
258     /**
259      * Return an AWT Rectangle2D that covers the same area as this Bounds2d.
260      * @return Rectangle2D; the rectangle that covers the same area as this Bounds2d
261      */
262     public Rectangle2D toRectangle2D()
263     {
264         return new Rectangle2D.Double(this.minX, this.minY, this.maxX - this.minX, this.maxY - this.minY);
265     }
266 
267     /** {@inheritDoc} */
268     @Override
269     public double getMinX()
270     {
271         return this.minX;
272     }
273 
274     /** {@inheritDoc} */
275     @Override
276     public double getMaxX()
277     {
278         return this.maxX;
279     }
280 
281     /** {@inheritDoc} */
282     @Override
283     public double getMinY()
284     {
285         return this.minY;
286     }
287 
288     /** {@inheritDoc} */
289     @Override
290     public double getMaxY()
291     {
292         return this.maxY;
293     }
294 
295     /**
296      * Return the mid point of this Bounds2d.
297      * @return Point2d; the mid point of this Bounds2d
298      */
299     public Point2d midPoint()
300     {
301         return new Point2d((this.minX + this.maxX) / 2, (this.minY + this.maxY) / 2);
302     }
303 
304     /**
305      * Return the area of this Bounds2d.
306      * @return double; the area of this Bounds2d
307      */
308     public double getArea()
309     {
310         return getDeltaX() * getDeltaY();
311     }
312 
313     /** {@inheritDoc} */
314     @Override
315     public Bounds2d getBounds()
316     {
317         return this;
318     }
319 
320     /** {@inheritDoc} */
321     @Override
322     public String toString()
323     {
324         return "Bounds2d [x[" + this.minX + " : " + this.maxX + "], y[" + this.minY + " : " + this.maxY + "]]";
325     }
326 
327     /** {@inheritDoc} */
328     @Override
329     public int hashCode()
330     {
331         final int prime = 31;
332         int result = 1;
333         long temp;
334         temp = Double.doubleToLongBits(this.maxX);
335         result = prime * result + (int) (temp ^ (temp >>> 32));
336         temp = Double.doubleToLongBits(this.maxY);
337         result = prime * result + (int) (temp ^ (temp >>> 32));
338         temp = Double.doubleToLongBits(this.minX);
339         result = prime * result + (int) (temp ^ (temp >>> 32));
340         temp = Double.doubleToLongBits(this.minY);
341         result = prime * result + (int) (temp ^ (temp >>> 32));
342         return result;
343     }
344 
345     /** {@inheritDoc} */
346     @SuppressWarnings("checkstyle:needbraces")
347     @Override
348     public boolean equals(final Object obj)
349     {
350         if (this == obj)
351             return true;
352         if (obj == null)
353             return false;
354         if (getClass() != obj.getClass())
355             return false;
356         Bounds2d./../../../org/djutils/draw/bounds/Bounds2d.html#Bounds2d">Bounds2d other = (Bounds2d) obj;
357         if (Double.doubleToLongBits(this.maxX) != Double.doubleToLongBits(other.maxX))
358             return false;
359         if (Double.doubleToLongBits(this.maxY) != Double.doubleToLongBits(other.maxY))
360             return false;
361         if (Double.doubleToLongBits(this.minX) != Double.doubleToLongBits(other.minX))
362             return false;
363         if (Double.doubleToLongBits(this.minY) != Double.doubleToLongBits(other.minY))
364             return false;
365         return true;
366     }
367 
368 }