View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.geometry.euclidean.twod;
18  
19  import java.util.Arrays;
20  import java.util.Objects;
21  
22  import org.apache.commons.geometry.euclidean.AbstractBounds;
23  import org.apache.commons.geometry.euclidean.twod.shape.Parallelogram;
24  import org.apache.commons.numbers.core.Precision;
25  
26  /** Class containing minimum and maximum points defining a 2D axis-aligned bounding box. Unless otherwise
27   * noted, floating point comparisons used in this class are strict, meaning that values are considered equal
28   * if and only if they match exactly.
29   *
30   * <p>Instances of this class are guaranteed to be immutable.</p>
31   */
32  public final class Bounds2D extends AbstractBounds<Vector2D, Bounds2D> {
33  
34      /** Simple constructor. Callers are responsible for ensuring the min is not greater than max.
35       * @param min minimum point
36       * @param max maximum point
37       */
38      private Bounds2D(final Vector2D min, final Vector2D max) {
39          super(min, max);
40      }
41  
42      /** {@inheritDoc} */
43      @Override
44      public boolean hasSize(final Precision.DoubleEquivalence precision) {
45          final Vector2D diag = getDiagonal();
46  
47          return !precision.eqZero(diag.getX()) &&
48                  !precision.eqZero(diag.getY());
49      }
50  
51      /** {@inheritDoc} */
52      @Override
53      public boolean contains(final Vector2D pt) {
54          final double x = pt.getX();
55          final double y = pt.getY();
56  
57          final Vector2D min = getMin();
58          final Vector2D max = getMax();
59  
60          return x >= min.getX() && x <= max.getX() &&
61                  y >= min.getY() && y <= max.getY();
62      }
63  
64      /** {@inheritDoc} */
65      @Override
66      public boolean contains(final Vector2D pt, final Precision.DoubleEquivalence precision) {
67          final double x = pt.getX();
68          final double y = pt.getY();
69  
70          final Vector2D min = getMin();
71          final Vector2D max = getMax();
72  
73          return precision.gte(x, min.getX()) && precision.lte(x, max.getX()) &&
74                  precision.gte(y, min.getY()) && precision.lte(y, max.getY());
75      }
76  
77      /** {@inheritDoc} */
78      @Override
79      public boolean intersects(final Bounds2D other) {
80          final Vector2D aMin = getMin();
81          final Vector2D aMax = getMax();
82  
83          final Vector2D bMin = other.getMin();
84          final Vector2D bMax = other.getMax();
85  
86          return aMin.getX() <= bMax.getX() && aMax.getX() >= bMin.getX() &&
87                  aMin.getY() <= bMax.getY() && aMax.getY() >= bMin.getY();
88      }
89  
90      /** {@inheritDoc} */
91      @Override
92      public Bounds2D intersection(final Bounds2D other) {
93          if (intersects(other)) {
94              final Vector2D aMin = getMin();
95              final Vector2D aMax = getMax();
96  
97              final Vector2D bMin = other.getMin();
98              final Vector2D bMax = other.getMax();
99  
100             // get the max of the mins and the mins of the maxes
101             final double minX = Math.max(aMin.getX(), bMin.getX());
102             final double minY = Math.max(aMin.getY(), bMin.getY());
103 
104             final double maxX = Math.min(aMax.getX(), bMax.getX());
105             final double maxY = Math.min(aMax.getY(), bMax.getY());
106 
107             return new Bounds2D(
108                     Vector2D.of(minX, minY),
109                     Vector2D.of(maxX, maxY));
110         }
111 
112         return null; // no intersection
113     }
114 
115     /** {@inheritDoc}
116      *
117      * @throws IllegalArgumentException if any dimension of the bounding box is zero
118      *      as evaluated by the given precision context
119      */
120     @Override
121     public Parallelogram toRegion(final Precision.DoubleEquivalence precision) {
122         return Parallelogram.axisAligned(getMin(), getMax(), precision);
123     }
124 
125     /** {@inheritDoc} */
126     @Override
127     public int hashCode() {
128         return Objects.hash(getMin(), getMax());
129     }
130 
131     /** {@inheritDoc} */
132     @Override
133     public boolean equals(final Object obj) {
134         if (obj == this) {
135             return true;
136         } else if (!(obj instanceof Bounds2D)) {
137             return false;
138         }
139 
140         final Bounds2D other = (Bounds2D) obj;
141 
142         return getMin().equals(other.getMin()) &&
143                 getMax().equals(other.getMax());
144     }
145 
146     /** Construct a new instance from the given points.
147      * @param first first point
148      * @param more additional points
149      * @return a new instance containing the min and max coordinates values from the input points
150      */
151     public static Bounds2D from(final Vector2D first, final Vector2D... more) {
152         final Builder builder = builder();
153 
154         builder.add(first);
155         builder.addAll(Arrays.asList(more));
156 
157         return builder.build();
158     }
159 
160     /** Construct a new instance from the given points.
161      * @param points input points
162      * @return a new instance containing the min and max coordinates values from the input points
163      */
164     public static Bounds2D from(final Iterable<Vector2D> points) {
165         final Builder builder = builder();
166 
167         builder.addAll(points);
168 
169         return builder.build();
170     }
171 
172     /** Construct a new {@link Builder} instance for creating bounds.
173      * @return a new builder instance for creating bounds
174      */
175     public static Builder builder() {
176         return new Builder();
177     }
178 
179     /** Class used to construct {@link Bounds2D} instances.
180      */
181     public static final class Builder {
182 
183         /** Minimum x coordinate. */
184         private double minX = Double.POSITIVE_INFINITY;
185 
186         /** Minimum y coordinate. */
187         private double minY = Double.POSITIVE_INFINITY;
188 
189         /** Maximum x coordinate. */
190         private double maxX = Double.NEGATIVE_INFINITY;
191 
192         /** Maximum y coordinate. */
193         private double maxY = Double.NEGATIVE_INFINITY;
194 
195         /** Private constructor; instantiate through factory method. */
196         private Builder() { }
197 
198         /** Add a point to this instance.
199          * @param pt point to add
200          * @return this instance
201          */
202         public Builder add(final Vector2D pt) {
203             final double x = pt.getX();
204             final double y = pt.getY();
205 
206             minX = Math.min(x, minX);
207             minY = Math.min(y, minY);
208 
209             maxX = Math.max(x, maxX);
210             maxY = Math.max(y, maxY);
211 
212             return this;
213         }
214 
215         /** Add a collection of points to this instance.
216          * @param pts points to add
217          * @return this instance
218          */
219         public Builder addAll(final Iterable<? extends Vector2D> pts) {
220             for (final Vector2D pt : pts) {
221                 add(pt);
222             }
223 
224             return this;
225         }
226 
227         /** Add the min and max points from the given bounds to this instance.
228          * @param bounds bounds containing the min and max points to add
229          * @return this instance
230          */
231         public Builder add(final Bounds2D bounds) {
232             add(bounds.getMin());
233             add(bounds.getMax());
234 
235             return this;
236         }
237 
238         /** Return true if this builder contains valid min and max coordinate values.
239          * @return true if this builder contains valid min and max coordinate values
240          */
241         public boolean hasBounds() {
242             return Double.isFinite(minX) &&
243                     Double.isFinite(minY) &&
244                     Double.isFinite(maxX) &&
245                     Double.isFinite(maxY);
246         }
247 
248         /** Create a new {@link Bounds2D} instance from the values in this builder.
249          * The builder can continue to be used to create other instances.
250          * @return a new bounds instance
251          * @throws IllegalStateException if no points were given to the builder or any of the computed
252          *      min and max coordinate values are NaN or infinite
253          * @see #hasBounds()
254          */
255         public Bounds2D build() {
256             final Vector2D min = Vector2D.of(minX, minY);
257             final Vector2D max = Vector2D.of(maxX, maxY);
258 
259             if (!hasBounds()) {
260                 if (Double.isInfinite(minX) && minX > 0 &&
261                         Double.isInfinite(maxX) && maxX < 0) {
262                     throw new IllegalStateException("Cannot construct bounds: no points given");
263                 }
264 
265                 throw new IllegalStateException("Invalid bounds: min= " + min + ", max= " + max);
266             }
267 
268             return new Bounds2D(min, max);
269         }
270     }
271 }