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.spherical.oned;
    18
    19  import java.util.Comparator;
    20
    21  import org.apache.commons.geometry.core.Point;
    22  import org.apache.commons.geometry.core.internal.DoubleFunction1N;
    23  import org.apache.commons.geometry.core.internal.SimpleTupleFormat;
    24  import org.apache.commons.geometry.euclidean.twod.PolarCoordinates;
    25  import org.apache.commons.geometry.euclidean.twod.Vector2D;
    26  import org.apache.commons.numbers.angle.Angle;
    27  import org.apache.commons.numbers.core.Precision;
    28
    29  /** This class represents a point on the 1-sphere, or in other words, an
    30   * azimuth angle on a circle. The value of the azimuth angle is not normalized
    31   * by default, meaning that instances can be constructed representing negative
    32   * values or values greater than {@code 2pi}. However, instances separated by a
    33   * multiple of {@code 2pi} are considered equivalent for most methods, with the
    34   * exceptions being {@link #equals(Object)} and {@link #hashCode()}, where the
    35   * azimuth values must match exactly in order for instances to be considered
    36   * equal.
    37   *
    38   * <p>Instances of this class are guaranteed to be immutable.</p>
    39   */
    40  public final class Point1S implements Point<Point1S> {
    41
    42      /** A point with coordinates set to {@code 0*pi}. */
    43      public static final Point1S ZERO = Point1S.of(0.0);
    44
    45      /** A point with coordinates set to {@code pi}. */
    46      public static final Point1S PI = Point1S.of(Math.PI);
    47
    48      /** A point with all coordinates set to NaN. */
    49      public static final Point1S NaN = Point1S.of(Double.NaN);
    50
    51      /** Comparator that sorts points by normalized azimuth in ascending order.
    52       * Points are only considered equal if their normalized azimuths match exactly.
    53       * Null arguments are evaluated as being greater than non-null arguments.
    54       * @see #getNormalizedAzimuth()
    55       */
    56      public static final Comparator<Point1S> NORMALIZED_AZIMUTH_ASCENDING_ORDER = (a, b) -> {
    57          int cmp = 0;
    58
    59          if (a != null && b != null) {
    60              cmp = Double.compare(a.getNormalizedAzimuth(), b.getNormalizedAzimuth());
    61          } else if (a != null) {
    62              cmp = -1;
    63          } else if (b != null) {
    64              cmp = 1;
    65          }
    66
    67          return cmp;
    68      };
    69
    70      /** Azimuthal angle in radians. */
    71      private final double azimuth;
    72
    73      /** Normalized azimuth value in the range {@code [0, 2pi)}. */
    74      private final double normalizedAzimuth;
    75
    76      /** Build a point from its internal components.
    77       * @param azimuth azimuth angle
    78       * @param normalizedAzimuth azimuth angle normalized to the range {@code [0, 2pi)}
    79       */
    80      private Point1S(final double azimuth, final double normalizedAzimuth) {
    81          this.azimuth  = azimuth;
    82          this.normalizedAzimuth = normalizedAzimuth;
    83      }
    84
    85      /** Get the azimuth angle in radians. This value is not normalized and
    86       * can be any floating point number.
    87       * @return azimuth angle
    88       * @see Point1S#of(double)
    89       */
    90      public double getAzimuth() {
    91          return azimuth;
    92      }
    93
    94      /** Get the azimuth angle normalized to the range {@code [0, 2pi)}.
    95       * @return the azimuth angle normalized to the range {@code [0, 2pi)}.
    96       */
    97      public double getNormalizedAzimuth() {
    98          return normalizedAzimuth;
    99      }
    100
    101     /** Get the normalized vector corresponding to this azimuth angle in 2D Euclidean space.
    102      * @return normalized vector
    103      */
    104     public Vector2D getVector() {
    105         if (isFinite()) {
    106             return PolarCoordinates.toCartesian(1, azimuth);
    107         }
    108
    109         return null;
    110     }
    111
    112     /** {@inheritDoc} */
    113     @Override
    114     public int getDimension() {
    115         return 1;
    116     }
    117
    118     /** {@inheritDoc} */
    119     @Override
    120     public boolean isNaN() {
    121         return Double.isNaN(azimuth);
    122     }
    123
    124     /** {@inheritDoc} */
    125     @Override
    126     public boolean isInfinite() {
    127         return !isNaN() && Double.isInfinite(azimuth);
    128     }
    129
    130     /** {@inheritDoc} */
    131     @Override
    132     public boolean isFinite() {
    133         return Double.isFinite(azimuth);
    134     }
    135
    136     /** {@inheritDoc}
    137      *
    138      * <p>The returned value is the shortest angular distance between
    139      * the two points, in the range {@code [0, pi]}.</p>
    140      */
    141     @Override
    142     public double distance(final Point1S point) {
    143         return distance(this, point);
    144     }
    145
    146     /** Return the signed distance (angular separation) between this instance and the
    147      * given point in the range {@code [-pi, pi)}. If {@code p1} is the current instance,
    148      * {@code p2} the given point, and {@code d} the signed distance, then
    149      * {@code p1.getAzimuth() + d} is an angle equivalent to {@code p2.getAzimuth()}.
    150      * @param point point to compute the signed distance to
    151      * @return the signed distance between this instance and the given point in the range
    152      *      {@code [-pi, pi)}
    153      */
    154     public double signedDistance(final Point1S point) {
    155         return signedDistance(this, point);
    156     }
    157
    158     /** Return an equivalent point with an azimuth value at or above the given base
    159      * value in radians. The returned point has an azimuth value in the range
    160      * {@code [base, base + 2pi)}.
    161      * @param base base azimuth to place this instance's azimuth value above
    162      * @return a point equivalent to the current instance but with an azimuth
    163      *      value in the range {@code [base, base + 2pi)}
    164      * @throws IllegalArgumentException if the azimuth value is NaN or infinite and
    165      *      cannot be normalized
    166      */
    167     public Point1S above(final double base) {
    168         if (isFinite()) {
    169             final double az = Angle.Rad.normalizer(base).applyAsDouble(azimuth);
    170             return new Point1S(az, normalizedAzimuth);
    171         }
    172         throw new IllegalArgumentException("Cannot normalize azimuth value: " + azimuth);
    173     }
    174
    175     /** Return an equivalent point with an azimuth value at or above the given base.
    176      * The returned point has an azimuth value in the range {@code [base, base + 2pi)}.
    177      * @param base point to place this instance's azimuth value above
    178      * @return a point equivalent to the current instance but with an azimuth
    179      *      value in the range {@code [base, base + 2pi)}
    180      * @throws IllegalArgumentException if the azimuth value is NaN or infinite and
    181      *      cannot be normalized
    182      */
    183     public Point1S above(final Point1S base) {
    184         return above(base.getAzimuth());
    185     }
    186
    187     /** Get the point exactly opposite this point on the circle, {@code pi} distance away.
    188      * The azimuth of the antipodal point is in the range {@code [0, 2pi)}.
    189      * @return the point exactly opposite this point on the circle
    190      */
    191     public Point1S antipodal() {
    192         double az = normalizedAzimuth + Math.PI;
    193         if (az >= Angle.TWO_PI) {
    194             az -= Angle.TWO_PI;
    195         }
    196
    197         return Point1S.of(az);
    198     }
    199
    200     /** Return true if this instance is equivalent to the argument. The points are
    201      * considered equivalent if the shortest angular distance between them is equal to
    202      * zero as evaluated by the given precision context. This means that points that differ
    203      * in azimuth by multiples of {@code 2pi} are considered equivalent.
    204      * @param other point to compare with
    205      * @param precision precision context used for floating point comparisons
    206      * @return true if this instance is equivalent to the argument
    207      */
    208     public boolean eq(final Point1S other, final Precision.DoubleEquivalence precision) {
    209         final double dist = signedDistance(other);
    210         return precision.eqZero(dist);
    211     }
    212
    213     /**
    214      * Get a hashCode for the point. Points normally must have exactly the
    215      * same azimuth angles in order to have the same hash code. Points
    216      * will angles that differ by multiples of {@code 2pi} will not
    217      * necessarily have the same hash code.
    218      *
    219      * <p>All NaN values have the same hash code.</p>
    220      *
    221      * @return a hash code value for this object
    222      */
    223     @Override
    224     public int hashCode() {
    225         if (isNaN()) {
    226             return 542;
    227         }
    228         return (Double.hashCode(azimuth) >> 17) ^
    229                 Double.hashCode(normalizedAzimuth);
    230     }
    231
    232     /** Test for the exact equality of two points on the 1-sphere.
    233      *
    234      * <p>If all coordinates of the given points are exactly the same, and none are
    235      * <code>Double.NaN</code>, the points are considered to be equal. Points with
    236      * azimuth values separated by multiples of {@code 2pi} are <em>not</em> considered
    237      * equal.</p>
    238      *
    239      * <p><code>NaN</code> coordinates are considered to affect globally the vector
    240      * and be equals to each other - i.e, if either (or all) coordinates of the
    241      * point are equal to <code>Double.NaN</code>, the point is equal to
    242      * {@link #NaN}.</p>
    243      *
    244      * @param other Object to test for equality to this
    245      * @return true if two points on the 1-sphere objects are exactly equal, false if
    246      *         object is null, not an instance of Point1S, or
    247      *         not equal to this Point1S instance
    248      *
    249      */
    250     @Override
    251     public boolean equals(final Object other) {
    252         if (this == other) {
    253             return true;
    254         }
    255
    256         if (other instanceof Point1S) {
    257             final Point1S rhs = (Point1S) other;
    258
    259             if (rhs.isNaN()) {
    260                 return this.isNaN();
    261             }
    262
    263             return Double.compare(azimuth, rhs.azimuth) == 0 &&
    264                     Double.compare(normalizedAzimuth, rhs.normalizedAzimuth) == 0;
    265         }
    266
    267         return false;
    268     }
    269
    270     /** {@inheritDoc} */
    271     @Override
    272     public String toString() {
    273         return SimpleTupleFormat.getDefault().format(getAzimuth());
    274     }
    275
    276     /** Create a new point instance from the given azimuth angle.
    277      * @param azimuth azimuth angle in radians
    278      * @return point instance with the given azimuth angle
    279      * @see #getAzimuth()
    280      */
    281     public static Point1S of(final double azimuth) {
    282         final double normalizedAzimuth = PolarCoordinates.normalizeAzimuth(azimuth);
    283
    284         return new Point1S(azimuth, normalizedAzimuth);
    285     }
    286
    287     /** Create a new point instance from the given azimuth angle.
    288      * @param azimuth azimuth azimuth angle
    289      * @return point instance with the given azimuth angle
    290      * @see #getAzimuth()
    291      */
    292     public static Point1S of(final Angle azimuth) {
    293         return of(azimuth.toRad().getAsDouble());
    294     }
    295
    296     /** Create a new point instance from the given Euclidean 2D vector. The returned point
    297      * will have an azimuth value equal to the angle between the positive x-axis and the
    298      * given vector, measured in a counter-clockwise direction.
    299      * @param vector 3D vector to create the point from
    300      * @return a new point instance with an azimuth value equal to the angle between the given
    301      *      vector and the positive x-axis, measured in a counter-clockwise direction
    302      */
    303     public static Point1S from(final Vector2D vector) {
    304         final PolarCoordinates polar = PolarCoordinates.fromCartesian(vector);
    305         final double az = polar.getAzimuth();
    306
    307         return new Point1S(az, az);
    308     }
    309
    310     /** Create a new point instance containing an azimuth value equal to that of the
    311      * given set of polar coordinates.
    312      * @param polar polar coordinates to convert to a point
    313      * @return a new point instance containing an azimuth value equal to that of
    314      *      the given set of polar coordinates.
    315      */
    316     public static Point1S from(final PolarCoordinates polar) {
    317         final double az = polar.getAzimuth();
    318
    319         return new Point1S(az, az);
    320     }
    321
    322     /** Parse the given string and returns a new point instance. The expected string
    323      * format is the same as that returned by {@link #toString()}.
    324      * @param str the string to parse
    325      * @return point instance represented by the string
    326      * @throws IllegalArgumentException if the given string has an invalid format
    327      */
    328     public static Point1S parse(final String str) {
    329         return SimpleTupleFormat.getDefault().parse(str, (DoubleFunction1N<Point1S>) Point1S::of);
    330     }
    331
    332     /** Compute the signed shortest distance (angular separation) between two points. The return
    333      * value is in the range {@code [-pi, pi)} and is such that {@code p1.getAzimuth() + d}
    334      * (where {@code d} is the signed distance) is an angle equivalent to {@code p2.getAzimuth()}.
    335      * @param p1 first point
    336      * @param p2 second point
    337      * @return the signed angular separation between p1 and p2, in the range {@code [-pi, pi)}.
    338      */
    339     public static double signedDistance(final Point1S p1, final Point1S p2) {
    340         double dist = p2.normalizedAzimuth - p1.normalizedAzimuth;
    341         if (dist < -Math.PI) {
    342             dist += Angle.TWO_PI;
    343         }
    344         if (dist >= Math.PI) {
    345             dist -= Angle.TWO_PI;
    346         }
    347         return dist;
    348     }
    349
    350     /** Compute the shortest distance (angular separation) between two points. The returned
    351      * value is in the range {@code [0, pi]}. This method is equal to the absolute value of
    352      * the {@link #signedDistance(Point1S, Point1S) signed distance}.
    353      * @param p1 first point
    354      * @param p2 second point
    355      * @return the angular separation between p1 and p2, in the range {@code [0, pi]}.
    356      * @see #signedDistance(Point1S, Point1S)
    357      */
    358     public static double distance(final Point1S p1, final Point1S p2) {
    359         return Math.abs(signedDistance(p1, p2));
    360     }
    361 }