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 }