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.oned;
    18
    19  import java.util.Collections;
    20  import java.util.List;
    21  import java.util.Objects;
    22
    23  import org.apache.commons.geometry.core.RegionLocation;
    24  import org.apache.commons.geometry.core.Transform;
    25  import org.apache.commons.geometry.core.partitioning.AbstractHyperplane;
    26  import org.apache.commons.geometry.core.partitioning.Hyperplane;
    27  import org.apache.commons.geometry.core.partitioning.HyperplaneConvexSubset;
    28  import org.apache.commons.geometry.core.partitioning.HyperplaneLocation;
    29  import org.apache.commons.geometry.core.partitioning.Split;
    30  import org.apache.commons.numbers.core.Precision;
    31
    32  /** This class represents a 1D oriented hyperplane.
    33   *
    34   * <p>A hyperplane in 1D is a simple point, its orientation being a
    35   * boolean indicating if the direction is positive or negative.</p>
    36   *
    37   * <p>Instances of this class are guaranteed to be immutable.</p>
    38   * @see OrientedPoints
    39   */
    40  public final class OrientedPoint extends AbstractHyperplane<Vector1D> {
    41      /** Hyperplane location as a point. */
    42      private final Vector1D point;
    43
    44      /** Hyperplane direction. */
    45      private final boolean positiveFacing;
    46
    47      /** Simple constructor.
    48       * @param point location of the hyperplane
    49       * @param positiveFacing if true, the hyperplane will face toward positive infinity;
    50       *      otherwise, it will point toward negative infinity.
    51       * @param precision precision context used to compare floating point values
    52       */
    53      OrientedPoint(final Vector1D point, final boolean positiveFacing, final Precision.DoubleEquivalence precision) {
    54          super(precision);
    55
    56          this.point = point;
    57          this.positiveFacing = positiveFacing;
    58      }
    59
    60      /** Get the location of the hyperplane as a point.
    61       * @return the hyperplane location as a point
    62       * @see #getLocation()
    63       */
    64      public Vector1D getPoint() {
    65          return point;
    66      }
    67
    68      /**
    69       * Get the location of the hyperplane as a single value. This is
    70       * equivalent to {@code pt.getPoint().getX()}.
    71       * @return the location of the hyperplane as a single value.
    72       * @see #getPoint()
    73       */
    74      public double getLocation() {
    75          return point.getX();
    76      }
    77
    78      /** Get the direction of the hyperplane's plus side.
    79       * @return the hyperplane direction
    80       */
    81      public Vector1D.Unit getDirection() {
    82          return positiveFacing ? Vector1D.Unit.PLUS : Vector1D.Unit.MINUS;
    83      }
    84
    85      /**
    86       * Return true if the hyperplane is oriented with its plus
    87       * side in the direction of positive infinity.
    88       * @return true if the hyperplane is facing toward positive
    89       *      infinity
    90       */
    91      public boolean isPositiveFacing() {
    92          return positiveFacing;
    93      }
    94
    95      /** {@inheritDoc} */
    96      @Override
    97      public OrientedPoint reverse() {
    98          return new OrientedPoint(point, !positiveFacing, getPrecision());
    99      }
    100
    101     /** {@inheritDoc} */
    102     @Override
    103     public OrientedPoint transform(final Transform<Vector1D> transform) {
    104         final Vector1D transformedPoint = transform.apply(point);
    105
    106         final Vector1D transformedDir;
    107         if (point.isInfinite()) {
    108             // use a test point to determine if the direction switches or not
    109             final Vector1D transformedZero = transform.apply(Vector1D.ZERO);
    110             final Vector1D transformedZeroDir = transform.apply(getDirection());
    111
    112             transformedDir = transformedZero.vectorTo(transformedZeroDir);
    113         } else {
    114             final Vector1D transformedPointPlusDir = transform.apply(point.add(getDirection()));
    115             transformedDir = transformedPoint.vectorTo(transformedPointPlusDir);
    116         }
    117
    118         return OrientedPoints.fromPointAndDirection(
    119                     transformedPoint,
    120                     transformedDir,
    121                     getPrecision()
    122                 );
    123     }
    124
    125     /** {@inheritDoc} */
    126     @Override
    127     public double offset(final Vector1D pt) {
    128         return offset(pt.getX());
    129     }
    130
    131     /** Compute the offset of the given number line location. This is
    132      * a convenience overload of {@link #offset(Vector1D)} for use in
    133      * one dimension.
    134      * @param location the number line location to compute the offset for
    135      * @return the offset of the location from the instance
    136      */
    137     public double offset(final double location) {
    138         final double delta = location - point.getX();
    139         return positiveFacing ? delta : -delta;
    140     }
    141
    142     /** {@inheritDoc} */
    143     @Override
    144     public HyperplaneLocation classify(final Vector1D pt) {
    145         return classify(pt.getX());
    146     }
    147
    148     /** Classify the number line location with respect to the instance.
    149      * This is a convenience overload of {@link #classify(Vector1D)} for
    150      * use in one dimension.
    151      * @param location the number line location to classify
    152      * @return the classification of the number line location with respect
    153      *      to this instance
    154      */
    155     public HyperplaneLocation classify(final double location) {
    156         final double offsetValue = offset(location);
    157
    158         final double cmp = getPrecision().signum(offsetValue);
    159         if (cmp > 0) {
    160             return HyperplaneLocation.PLUS;
    161         } else if (cmp < 0) {
    162             return HyperplaneLocation.MINUS;
    163         }
    164         return HyperplaneLocation.ON;
    165     }
    166
    167     /** {@inheritDoc} */
    168     @Override
    169     public boolean similarOrientation(final Hyperplane<Vector1D> other) {
    170         return positiveFacing == ((OrientedPoint) other).positiveFacing;
    171     }
    172
    173     /** {@inheritDoc} */
    174     @Override
    175     public Vector1D project(final Vector1D pt) {
    176         return this.point;
    177     }
    178
    179     /** {@inheritDoc}
    180      *
    181      * <p>Since there are no subspaces in 1D, this method effectively returns a stub implementation of
    182      * {@link HyperplaneConvexSubset}, the main purpose of which is to support the proper functioning
    183      * of the partitioning code.</p>
    184      */
    185     @Override
    186     public HyperplaneConvexSubset<Vector1D> span() {
    187         return new OrientedPointConvexSubset(this);
    188     }
    189
    190     /** Return true if this instance should be considered equivalent to the argument, using the
    191      * given precision context for comparison.
    192      * <p>Instances are considered equivalent if they
    193      * <ol>
    194      *      <li>have equivalent locations and</li>
    195      *      <li>point in the same direction.</li>
    196      * </ol>
    197      * @param other the point to compare with
    198      * @param precision precision context to use for the comparison
    199      * @return true if this instance should be considered equivalent to the argument
    200      * @see Vector1D#eq(Vector1D, Precision.DoubleEquivalence)
    201      */
    202     public boolean eq(final OrientedPoint other, final Precision.DoubleEquivalence precision) {
    203         return point.eq(other.point, precision) &&
    204                 positiveFacing == other.positiveFacing;
    205     }
    206
    207     /** {@inheritDoc} */
    208     @Override
    209     public int hashCode() {
    210         final int prime = 31;
    211
    212         int result = 1;
    213         result = (prime * result) + Objects.hashCode(point);
    214         result = (prime * result) + Boolean.hashCode(positiveFacing);
    215         result = (prime * result) + Objects.hashCode(getPrecision());
    216
    217         return result;
    218     }
    219
    220     /** {@inheritDoc} */
    221     @Override
    222     public boolean equals(final Object obj) {
    223         if (this == obj) {
    224             return true;
    225         } else if (!(obj instanceof OrientedPoint)) {
    226             return false;
    227         }
    228
    229         final OrientedPoint other = (OrientedPoint) obj;
    230
    231         return Objects.equals(this.point, other.point) &&
    232                 this.positiveFacing == other.positiveFacing &&
    233                 Objects.equals(this.getPrecision(), other.getPrecision());
    234     }
    235
    236     /** {@inheritDoc} */
    237     @Override
    238     public String toString() {
    239         final StringBuilder sb = new StringBuilder();
    240         sb.append(this.getClass().getSimpleName())
    241             .append("[point= ")
    242             .append(point)
    243             .append(", direction= ")
    244             .append(getDirection())
    245             .append(']');
    246
    247         return sb.toString();
    248     }
    249
    250     /** {@link HyperplaneConvexSubset} implementation for Euclidean 1D space. Since there are no subspaces in 1D,
    251      * this is effectively a stub implementation, its main use being to allow for the correct functioning of
    252      * partitioning code.
    253      */
    254     private static class OrientedPointConvexSubset implements HyperplaneConvexSubset<Vector1D> {
    255         /** The underlying hyperplane for this instance. */
    256         private final OrientedPoint hyperplane;
    257
    258         /** Simple constructor.
    259          * @param hyperplane underlying hyperplane instance
    260          */
    261         OrientedPointConvexSubset(final OrientedPoint hyperplane) {
    262             this.hyperplane = hyperplane;
    263         }
    264
    265         /** {@inheritDoc} */
    266         @Override
    267         public OrientedPoint getHyperplane() {
    268             return hyperplane;
    269         }
    270
    271         /** {@inheritDoc}
    272         *
    273         * <p>This method always returns {@code false}.</p>
    274         */
    275         @Override
    276         public boolean isFull() {
    277             return false;
    278         }
    279
    280         /** {@inheritDoc}
    281         *
    282         * <p>This method always returns {@code false}.</p>
    283         */
    284         @Override
    285         public boolean isEmpty() {
    286             return false;
    287         }
    288
    289         /** {@inheritDoc}
    290          *
    291          * <p>This method always returns {@code false}.</p>
    292          */
    293         @Override
    294         public boolean isInfinite() {
    295             return false;
    296         }
    297
    298         /** {@inheritDoc}
    299         *
    300         * <p>This method always returns {@code true}.</p>
    301         */
    302         @Override
    303         public boolean isFinite() {
    304             return true;
    305         }
    306
    307         /** {@inheritDoc}
    308          *
    309          *  <p>This method always returns {@code 0}.</p>
    310          */
    311         @Override
    312         public double getSize() {
    313             return 0;
    314         }
    315
    316         /** {@inheritDoc}
    317         *
    318         *  <p>This method returns the point for the defining hyperplane.</p>
    319         */
    320         @Override
    321         public Vector1D getCentroid() {
    322             return hyperplane.getPoint();
    323         }
    324
    325         /** {@inheritDoc}
    326          *
    327          * <p>This method returns {@link RegionLocation#BOUNDARY} if the
    328          * point is on the hyperplane and {@link RegionLocation#OUTSIDE}
    329          * otherwise.</p>
    330          */
    331         @Override
    332         public RegionLocation classify(final Vector1D point) {
    333             if (hyperplane.contains(point)) {
    334                 return RegionLocation.BOUNDARY;
    335             }
    336
    337             return RegionLocation.OUTSIDE;
    338         }
    339
    340         /** {@inheritDoc} */
    341         @Override
    342         public Vector1D closest(final Vector1D point) {
    343             return hyperplane.project(point);
    344         }
    345
    346         /** {@inheritDoc} */
    347         @Override
    348         public Split<OrientedPointConvexSubset> split(final Hyperplane<Vector1D> splitter) {
    349             final HyperplaneLocation side = splitter.classify(hyperplane.getPoint());
    350
    351             OrientedPointConvexSubset minus = null;
    352             OrientedPointConvexSubset plus = null;
    353
    354             if (side == HyperplaneLocation.MINUS) {
    355                 minus = this;
    356             } else if (side == HyperplaneLocation.PLUS) {
    357                 plus = this;
    358             }
    359
    360             return new Split<>(minus, plus);
    361         }
    362
    363         /** {@inheritDoc} */
    364         @Override
    365         public List<OrientedPointConvexSubset> toConvex() {
    366             return Collections.singletonList(this);
    367         }
    368
    369         /** {@inheritDoc} */
    370         @Override
    371         public OrientedPointConvexSubset transform(final Transform<Vector1D> transform) {
    372             return new OrientedPointConvexSubset(getHyperplane().transform(transform));
    373         }
    374
    375         /** {@inheritDoc} */
    376         @Override
    377         public OrientedPointConvexSubset reverse() {
    378             return new OrientedPointConvexSubset(hyperplane.reverse());
    379         }
    380
    381         /** {@inheritDoc} */
    382         @Override
    383         public String toString() {
    384             final StringBuilder sb = new StringBuilder();
    385             sb.append(this.getClass().getSimpleName())
    386                 .append("[hyperplane= ")
    387                 .append(hyperplane)
    388                 .append(']');
    389
    390             return sb.toString();
    391         }
    392     }
    393 }