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 org.apache.commons.geometry.core.Spatial;
    20  import org.apache.commons.geometry.core.internal.SimpleTupleFormat;
    21  import org.apache.commons.numbers.angle.PlaneAngleRadians;
    22
    23  /** Class representing <a href="https://en.wikipedia.org/wiki/Polar_coordinate_system">polar coordinates</a>
    24   * in 2 dimensional Euclidean space.
    25   *
    26   * <p>Polar coordinates are defined by a distance from a reference point
    27   * and an angle from a reference direction. The distance value is called
    28   * the radial coordinate, or <em>radius</em>, and the angle is called the angular coordinate,
    29   * or <em>azimuth</em>. This class follows the standard
    30   * mathematical convention of using the positive x-axis as the reference
    31   * direction and measuring positive angles counter-clockwise, toward the
    32   * positive y-axis. The origin is used as the reference point. Polar coordinate
    33   * are related to Cartesian coordinates as follows:
    34   * <pre>
    35   * x = r * cos(&theta;)
    36   * y = r * sin(&theta;)
    37   *
    38   * r = &radic;(x^2 + y^2)
    39   * &theta; = atan2(y, x)
    40   * </pre>
    41   * where <em>r</em> is the radius and <em>&theta;</em> is the azimuth of the polar coordinates.
    42   *
    43   * <p>In order to ensure the uniqueness of coordinate sets, coordinate values
    44   * are normalized so that {@code radius} is in the range {@code [0, +Infinity)}
    45   * and {@code azimuth} is in the range {@code [0, 2pi)}.</p>
    46   *
    47   * @see <a href="https://en.wikipedia.org/wiki/Polar_coordinate_system">Polar Coordinate System</a>
    48   */
    49  public final class PolarCoordinates implements Spatial {
    50      /** Radius value. */
    51      private final double radius;
    52
    53      /** Azimuth angle in radians. */
    54      private final double azimuth;
    55
    56      /** Simple constructor. Input values are normalized.
    57       * @param radius Radius value.
    58       * @param azimuth Azimuth angle in radians.
    59       */
    60      private PolarCoordinates(double radius, double azimuth) {
    61          if (radius < 0) {
    62              // negative radius; flip the angles
    63              radius = Math.abs(radius);
    64              azimuth += PlaneAngleRadians.PI;
    65          }
    66
    67          this.radius = radius;
    68          this.azimuth = normalizeAzimuth(azimuth);
    69      }
    70
    71      /** Return the radius value. The value will be greater than or equal to 0.
    72       * @return radius value
    73       */
    74      public double getRadius() {
    75          return radius;
    76      }
    77
    78      /** Return the azimuth angle in radians. The value will be
    79       * in the range {@code [0, 2pi)}.
    80       * @return azimuth value in radians.
    81       */
    82      public double getAzimuth() {
    83          return azimuth;
    84      }
    85
    86      /** {@inheritDoc} */
    87      @Override
    88      public int getDimension() {
    89          return 2;
    90      }
    91
    92      /** {@inheritDoc} */
    93      @Override
    94      public boolean isNaN() {
    95          return Double.isNaN(radius) || Double.isNaN(azimuth);
    96      }
    97
    98      /** {@inheritDoc} */
    99      @Override
    100     public boolean isInfinite() {
    101         return !isNaN() && (Double.isInfinite(radius) || Double.isInfinite(azimuth));
    102     }
    103
    104     /** {@inheritDoc} */
    105     @Override
    106     public boolean isFinite() {
    107         return Double.isFinite(radius) && Double.isFinite(azimuth);
    108     }
    109
    110     /** Convert this set of polar coordinates to Cartesian coordinates.
    111      * @return A 2-dimensional vector with an equivalent set of
    112      *      coordinates in Cartesian form
    113      */
    114     public Vector2D toCartesian() {
    115         return toCartesian(radius, azimuth);
    116     }
    117
    118     /** Get a hashCode for this set of polar coordinates.
    119      * <p>All NaN values have the same hash code.</p>
    120      *
    121      * @return a hash code value for this object
    122      */
    123     @Override
    124     public int hashCode() {
    125         if (isNaN()) {
    126             return 191;
    127         }
    128         return 449 * (76 * Double.hashCode(radius) + Double.hashCode(azimuth));
    129     }
    130
    131     /** Test for the equality of two sets of polar coordinates.
    132      * <p>
    133      * If all values of two sets of coordinates are exactly the same, and none are
    134      * <code>Double.NaN</code>, the two sets are considered to be equal.
    135      * </p>
    136      * <p>
    137      * <code>NaN</code> values are considered to globally affect the coordinates
    138      * and be equal to each other - i.e, if either (or all) values of the
    139      * coordinate set are equal to <code>Double.NaN</code>, the set as a whole is
    140      * considered to equal <code>NaN</code>.
    141      * </p>
    142      *
    143      * @param other Object to test for equality to this
    144      * @return true if two PolarCoordinates objects are equal, false if
    145      *         object is null, not an instance of PolarCoordinates, or
    146      *         not equal to this PolarCoordinates instance
    147      *
    148      */
    149     @Override
    150     public boolean equals(final Object other) {
    151         if (this == other) {
    152             return true;
    153         }
    154         if (other instanceof PolarCoordinates) {
    155             final PolarCoordinates rhs = (PolarCoordinates) other;
    156             if (rhs.isNaN()) {
    157                 return this.isNaN();
    158             }
    159
    160             return Double.compare(radius, rhs.radius) == 0 &&
    161                     Double.compare(azimuth, rhs.azimuth) == 0;
    162         }
    163         return false;
    164     }
    165
    166     /** {@inheritDoc} */
    167     @Override
    168     public String toString() {
    169         return SimpleTupleFormat.getDefault().format(radius, azimuth);
    170     }
    171
    172     /** Return a new instance with the given polar coordinate values.
    173      * The values are normalized so that {@code radius} lies in the range {@code [0, +Infinity)}
    174      * and {@code azimuth} in the range {@code [0, 2pi)}.
    175      * @param radius Radius value.
    176      * @param azimuth Azimuth angle in radians.
    177      * @return new {@link PolarCoordinates} instance
    178      */
    179     public static PolarCoordinates of(final double radius, final double azimuth) {
    180         return new PolarCoordinates(radius, azimuth);
    181     }
    182
    183     /** Convert the given Cartesian coordinates to polar form.
    184      * @param x X coordinate value
    185      * @param y Y coordinate value
    186      * @return polar coordinates equivalent to the given Cartesian coordinates
    187      */
    188     public static PolarCoordinates fromCartesian(final double x, final double y) {
    189         final double azimuth = Math.atan2(y, x);
    190         final double radius = Math.hypot(x, y);
    191
    192         return new PolarCoordinates(radius, azimuth);
    193     }
    194
    195     /** Convert the given Cartesian coordinates to polar form.
    196      * @param vec vector containing Cartesian coordinates
    197      * @return polar coordinates equivalent to the given Cartesian coordinates
    198      */
    199     public static PolarCoordinates fromCartesian(final Vector2D vec) {
    200         return fromCartesian(vec.getX(), vec.getY());
    201     }
    202
    203     /** Convert the given polar coordinates to Cartesian form.
    204      * @param radius Radius value.
    205      * @param azimuth Azimuth angle in radians.
    206      * @return A 2-dimensional vector with an equivalent set of
    207      *      coordinates in Cartesian form
    208      */
    209     public static Vector2D toCartesian(final double radius, final double azimuth) {
    210         final double x = radius * Math.cos(azimuth);
    211         final double y = radius * Math.sin(azimuth);
    212
    213         return Vector2D.of(x, y);
    214     }
    215
    216     /** Parse the given string and return a new polar coordinates instance. The parsed
    217      * coordinates are normalized as in the {@link #of(double, double)} method. The expected string
    218      * format is the same as that returned by {@link #toString()}.
    219      * @param input the string to parse
    220      * @return new {@link PolarCoordinates} instance
    221      * @throws IllegalArgumentException if the string format is invalid.
    222      */
    223     public static PolarCoordinates parse(final String input) {
    224         return SimpleTupleFormat.getDefault().parse(input, PolarCoordinates::new);
    225     }
    226
    227     /** Normalize an azimuth value to be within the range {@code [0, 2pi)}.
    228      * @param azimuth azimuth value in radians
    229      * @return equivalent azimuth value in the range {@code [0, 2pi)}.
    230      */
    231     public static double normalizeAzimuth(double azimuth) {
    232         if (Double.isFinite(azimuth)) {
    233             azimuth = PlaneAngleRadians.normalizeBetweenZeroAndTwoPi(azimuth);
    234         }
    235
    236         return azimuth;
    237     }
    238 }