Point1S.java

  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. import java.util.Comparator;

  19. import org.apache.commons.geometry.core.Point;
  20. import org.apache.commons.geometry.core.internal.DoubleFunction1N;
  21. import org.apache.commons.geometry.core.internal.SimpleTupleFormat;
  22. import org.apache.commons.geometry.euclidean.twod.PolarCoordinates;
  23. import org.apache.commons.geometry.euclidean.twod.Vector2D;
  24. import org.apache.commons.numbers.angle.Angle;
  25. import org.apache.commons.numbers.core.Precision;

  26. /** This class represents a point on the 1-sphere, or in other words, an
  27.  * azimuth angle on a circle. The value of the azimuth angle is not normalized
  28.  * by default, meaning that instances can be constructed representing negative
  29.  * values or values greater than {@code 2pi}. However, instances separated by a
  30.  * multiple of {@code 2pi} are considered equivalent for most methods, with the
  31.  * exceptions being {@link #equals(Object)} and {@link #hashCode()}, where the
  32.  * azimuth values must match exactly in order for instances to be considered
  33.  * equal.
  34.  *
  35.  * <p>Instances of this class are guaranteed to be immutable.</p>
  36.  */
  37. public final class Point1S implements Point<Point1S> {

  38.     /** A point with coordinates set to {@code 0*pi}. */
  39.     public static final Point1S ZERO = Point1S.of(0.0);

  40.     /** A point with coordinates set to {@code pi}. */
  41.     public static final Point1S PI = Point1S.of(Math.PI);

  42.     /** A point with all coordinates set to NaN. */
  43.     public static final Point1S NaN = Point1S.of(Double.NaN);

  44.     /** Comparator that sorts points by normalized azimuth in ascending order.
  45.      * Points are only considered equal if their normalized azimuths match exactly.
  46.      * Null arguments are evaluated as being greater than non-null arguments.
  47.      * @see #getNormalizedAzimuth()
  48.      */
  49.     public static final Comparator<Point1S> NORMALIZED_AZIMUTH_ASCENDING_ORDER = (a, b) -> {
  50.         int cmp = 0;

  51.         if (a != null && b != null) {
  52.             cmp = Double.compare(a.getNormalizedAzimuth(), b.getNormalizedAzimuth());
  53.         } else if (a != null) {
  54.             cmp = -1;
  55.         } else if (b != null) {
  56.             cmp = 1;
  57.         }

  58.         return cmp;
  59.     };

  60.     /** Azimuthal angle in radians. */
  61.     private final double azimuth;

  62.     /** Normalized azimuth value in the range {@code [0, 2pi)}. */
  63.     private final double normalizedAzimuth;

  64.     /** Build a point from its internal components.
  65.      * @param azimuth azimuth angle
  66.      * @param normalizedAzimuth azimuth angle normalized to the range {@code [0, 2pi)}
  67.      */
  68.     private Point1S(final double azimuth, final double normalizedAzimuth) {
  69.         this.azimuth  = azimuth;
  70.         this.normalizedAzimuth = normalizedAzimuth;
  71.     }

  72.     /** Get the azimuth angle in radians. This value is not normalized and
  73.      * can be any floating point number.
  74.      * @return azimuth angle
  75.      * @see Point1S#of(double)
  76.      */
  77.     public double getAzimuth() {
  78.         return azimuth;
  79.     }

  80.     /** Get the azimuth angle normalized to the range {@code [0, 2pi)}.
  81.      * @return the azimuth angle normalized to the range {@code [0, 2pi)}.
  82.      */
  83.     public double getNormalizedAzimuth() {
  84.         return normalizedAzimuth;
  85.     }

  86.     /** Get the normalized vector corresponding to this azimuth angle in 2D Euclidean space.
  87.      * @return normalized vector
  88.      */
  89.     public Vector2D getVector() {
  90.         if (isFinite()) {
  91.             return PolarCoordinates.toCartesian(1, azimuth);
  92.         }

  93.         return null;
  94.     }

  95.     /** {@inheritDoc} */
  96.     @Override
  97.     public int getDimension() {
  98.         return 1;
  99.     }

  100.     /** {@inheritDoc} */
  101.     @Override
  102.     public boolean isNaN() {
  103.         return Double.isNaN(azimuth);
  104.     }

  105.     /** {@inheritDoc} */
  106.     @Override
  107.     public boolean isInfinite() {
  108.         return !isNaN() && Double.isInfinite(azimuth);
  109.     }

  110.     /** {@inheritDoc} */
  111.     @Override
  112.     public boolean isFinite() {
  113.         return Double.isFinite(azimuth);
  114.     }

  115.     /** {@inheritDoc}
  116.      *
  117.      * <p>The returned value is the shortest angular distance between
  118.      * the two points, in the range {@code [0, pi]}.</p>
  119.      */
  120.     @Override
  121.     public double distance(final Point1S point) {
  122.         return distance(this, point);
  123.     }

  124.     /** Return the signed distance (angular separation) between this instance and the
  125.      * given point in the range {@code [-pi, pi)}. If {@code p1} is the current instance,
  126.      * {@code p2} the given point, and {@code d} the signed distance, then
  127.      * {@code p1.getAzimuth() + d} is an angle equivalent to {@code p2.getAzimuth()}.
  128.      * @param point point to compute the signed distance to
  129.      * @return the signed distance between this instance and the given point in the range
  130.      *      {@code [-pi, pi)}
  131.      */
  132.     public double signedDistance(final Point1S point) {
  133.         return signedDistance(this, point);
  134.     }

  135.     /** Return an equivalent point with an azimuth value at or above the given base
  136.      * value in radians. The returned point has an azimuth value in the range
  137.      * {@code [base, base + 2pi)}.
  138.      * @param base base azimuth to place this instance's azimuth value above
  139.      * @return a point equivalent to the current instance but with an azimuth
  140.      *      value in the range {@code [base, base + 2pi)}
  141.      * @throws IllegalArgumentException if the azimuth value is NaN or infinite and
  142.      *      cannot be normalized
  143.      */
  144.     public Point1S above(final double base) {
  145.         if (isFinite()) {
  146.             final double az = Angle.Rad.normalizer(base).applyAsDouble(azimuth);
  147.             return new Point1S(az, normalizedAzimuth);
  148.         }
  149.         throw new IllegalArgumentException("Cannot normalize azimuth value: " + azimuth);
  150.     }

  151.     /** Return an equivalent point with an azimuth value at or above the given base.
  152.      * The returned point has an azimuth value in the range {@code [base, base + 2pi)}.
  153.      * @param base point to place this instance's azimuth value above
  154.      * @return a point equivalent to the current instance but with an azimuth
  155.      *      value in the range {@code [base, base + 2pi)}
  156.      * @throws IllegalArgumentException if the azimuth value is NaN or infinite and
  157.      *      cannot be normalized
  158.      */
  159.     public Point1S above(final Point1S base) {
  160.         return above(base.getAzimuth());
  161.     }

  162.     /** Get the point exactly opposite this point on the circle, {@code pi} distance away.
  163.      * The azimuth of the antipodal point is in the range {@code [0, 2pi)}.
  164.      * @return the point exactly opposite this point on the circle
  165.      */
  166.     public Point1S antipodal() {
  167.         double az = normalizedAzimuth + Math.PI;
  168.         if (az >= Angle.TWO_PI) {
  169.             az -= Angle.TWO_PI;
  170.         }

  171.         return Point1S.of(az);
  172.     }

  173.     /** Return true if this instance is equivalent to the argument. The points are
  174.      * considered equivalent if the shortest angular distance between them is equal to
  175.      * zero as evaluated by the given precision context. This means that points that differ
  176.      * in azimuth by multiples of {@code 2pi} are considered equivalent.
  177.      * @param other point to compare with
  178.      * @param precision precision context used for floating point comparisons
  179.      * @return true if this instance is equivalent to the argument
  180.      */
  181.     public boolean eq(final Point1S other, final Precision.DoubleEquivalence precision) {
  182.         final double dist = signedDistance(other);
  183.         return precision.eqZero(dist);
  184.     }

  185.     /**
  186.      * Get a hashCode for the point. Points normally must have exactly the
  187.      * same azimuth angles in order to have the same hash code. Points
  188.      * will angles that differ by multiples of {@code 2pi} will not
  189.      * necessarily have the same hash code.
  190.      *
  191.      * <p>All NaN values have the same hash code.</p>
  192.      *
  193.      * @return a hash code value for this object
  194.      */
  195.     @Override
  196.     public int hashCode() {
  197.         if (isNaN()) {
  198.             return 542;
  199.         }
  200.         return (Double.hashCode(azimuth) >> 17) ^
  201.                 Double.hashCode(normalizedAzimuth);
  202.     }

  203.     /** Test for the exact equality of two points on the 1-sphere.
  204.      *
  205.      * <p>If all coordinates of the given points are exactly the same, and none are
  206.      * <code>Double.NaN</code>, the points are considered to be equal. Points with
  207.      * azimuth values separated by multiples of {@code 2pi} are <em>not</em> considered
  208.      * equal.</p>
  209.      *
  210.      * <p><code>NaN</code> coordinates are considered to affect globally the vector
  211.      * and be equals to each other - i.e, if either (or all) coordinates of the
  212.      * point are equal to <code>Double.NaN</code>, the point is equal to
  213.      * {@link #NaN}.</p>
  214.      *
  215.      * @param other Object to test for equality to this
  216.      * @return true if two points on the 1-sphere objects are exactly equal, false if
  217.      *         object is null, not an instance of Point1S, or
  218.      *         not equal to this Point1S instance
  219.      *
  220.      */
  221.     @Override
  222.     public boolean equals(final Object other) {
  223.         if (this == other) {
  224.             return true;
  225.         }

  226.         if (other instanceof Point1S) {
  227.             final Point1S rhs = (Point1S) other;

  228.             if (rhs.isNaN()) {
  229.                 return this.isNaN();
  230.             }

  231.             return Double.compare(azimuth, rhs.azimuth) == 0 &&
  232.                     Double.compare(normalizedAzimuth, rhs.normalizedAzimuth) == 0;
  233.         }

  234.         return false;
  235.     }

  236.     /** {@inheritDoc} */
  237.     @Override
  238.     public String toString() {
  239.         return SimpleTupleFormat.getDefault().format(getAzimuth());
  240.     }

  241.     /** Create a new point instance from the given azimuth angle.
  242.      * @param azimuth azimuth angle in radians
  243.      * @return point instance with the given azimuth angle
  244.      * @see #getAzimuth()
  245.      */
  246.     public static Point1S of(final double azimuth) {
  247.         final double normalizedAzimuth = PolarCoordinates.normalizeAzimuth(azimuth);

  248.         return new Point1S(azimuth, normalizedAzimuth);
  249.     }

  250.     /** Create a new point instance from the given azimuth angle.
  251.      * @param azimuth azimuth azimuth angle
  252.      * @return point instance with the given azimuth angle
  253.      * @see #getAzimuth()
  254.      */
  255.     public static Point1S of(final Angle azimuth) {
  256.         return of(azimuth.toRad().getAsDouble());
  257.     }

  258.     /** Create a new point instance from the given Euclidean 2D vector. The returned point
  259.      * will have an azimuth value equal to the angle between the positive x-axis and the
  260.      * given vector, measured in a counter-clockwise direction.
  261.      * @param vector 3D vector to create the point from
  262.      * @return a new point instance with an azimuth value equal to the angle between the given
  263.      *      vector and the positive x-axis, measured in a counter-clockwise direction
  264.      */
  265.     public static Point1S from(final Vector2D vector) {
  266.         final PolarCoordinates polar = PolarCoordinates.fromCartesian(vector);
  267.         final double az = polar.getAzimuth();

  268.         return new Point1S(az, az);
  269.     }

  270.     /** Create a new point instance containing an azimuth value equal to that of the
  271.      * given set of polar coordinates.
  272.      * @param polar polar coordinates to convert to a point
  273.      * @return a new point instance containing an azimuth value equal to that of
  274.      *      the given set of polar coordinates.
  275.      */
  276.     public static Point1S from(final PolarCoordinates polar) {
  277.         final double az = polar.getAzimuth();

  278.         return new Point1S(az, az);
  279.     }

  280.     /** Parse the given string and returns a new point instance. The expected string
  281.      * format is the same as that returned by {@link #toString()}.
  282.      * @param str the string to parse
  283.      * @return point instance represented by the string
  284.      * @throws IllegalArgumentException if the given string has an invalid format
  285.      */
  286.     public static Point1S parse(final String str) {
  287.         return SimpleTupleFormat.getDefault().parse(str, (DoubleFunction1N<Point1S>) Point1S::of);
  288.     }

  289.     /** Compute the signed shortest distance (angular separation) between two points. The return
  290.      * value is in the range {@code [-pi, pi)} and is such that {@code p1.getAzimuth() + d}
  291.      * (where {@code d} is the signed distance) is an angle equivalent to {@code p2.getAzimuth()}.
  292.      * @param p1 first point
  293.      * @param p2 second point
  294.      * @return the signed angular separation between p1 and p2, in the range {@code [-pi, pi)}.
  295.      */
  296.     public static double signedDistance(final Point1S p1, final Point1S p2) {
  297.         double dist = p2.normalizedAzimuth - p1.normalizedAzimuth;
  298.         if (dist < -Math.PI) {
  299.             dist += Angle.TWO_PI;
  300.         }
  301.         if (dist >= Math.PI) {
  302.             dist -= Angle.TWO_PI;
  303.         }
  304.         return dist;
  305.     }

  306.     /** Compute the shortest distance (angular separation) between two points. The returned
  307.      * value is in the range {@code [0, pi]}. This method is equal to the absolute value of
  308.      * the {@link #signedDistance(Point1S, Point1S) signed distance}.
  309.      * @param p1 first point
  310.      * @param p2 second point
  311.      * @return the angular separation between p1 and p2, in the range {@code [0, pi]}.
  312.      * @see #signedDistance(Point1S, Point1S)
  313.      */
  314.     public static double distance(final Point1S p1, final Point1S p2) {
  315.         return Math.abs(signedDistance(p1, p2));
  316.     }
  317. }