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.Angle;
    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(final double radius, final double azimuth) {
    61          double rad = radius;
    62          double az = azimuth;
    63
    64          if (rad < 0) {
    65              // negative radius; flip the angles
    66              rad = Math.abs(radius);
    67              az += Math.PI;
    68          }
    69
    70          this.radius = rad;
    71          this.azimuth = normalizeAzimuth(az);
    72      }
    73
    74      /** Return the radius value. The value will be greater than or equal to 0.
    75       * @return radius value
    76       */
    77      public double getRadius() {
    78          return radius;
    79      }
    80
    81      /** Return the azimuth angle in radians. The value will be
    82       * in the range {@code [0, 2pi)}.
    83       * @return azimuth value in radians.
    84       */
    85      public double getAzimuth() {
    86          return azimuth;
    87      }
    88
    89      /** {@inheritDoc} */
    90      @Override
    91      public int getDimension() {
    92          return 2;
    93      }
    94
    95      /** {@inheritDoc} */
    96      @Override
    97      public boolean isNaN() {
    98          return Double.isNaN(radius) || Double.isNaN(azimuth);
    99      }
    100
    101     /** {@inheritDoc} */
    102     @Override
    103     public boolean isInfinite() {
    104         return !isNaN() && (Double.isInfinite(radius) || Double.isInfinite(azimuth));
    105     }
    106
    107     /** {@inheritDoc} */
    108     @Override
    109     public boolean isFinite() {
    110         return Double.isFinite(radius) && Double.isFinite(azimuth);
    111     }
    112
    113     /** Convert this set of polar coordinates to Cartesian coordinates.
    114      * @return A 2-dimensional vector with an equivalent set of
    115      *      coordinates in Cartesian form
    116      */
    117     public Vector2D toCartesian() {
    118         return toCartesian(radius, azimuth);
    119     }
    120
    121     /** Get a hashCode for this set of polar coordinates.
    122      * <p>All NaN values have the same hash code.</p>
    123      *
    124      * @return a hash code value for this object
    125      */
    126     @Override
    127     public int hashCode() {
    128         if (isNaN()) {
    129             return 191;
    130         }
    131         return 449 * (76 * Double.hashCode(radius) + Double.hashCode(azimuth));
    132     }
    133
    134     /** Test for the equality of two sets of polar coordinates.
    135      * <p>
    136      * If all values of two sets of coordinates are exactly the same, and none are
    137      * <code>Double.NaN</code>, the two sets are considered to be equal.
    138      * </p>
    139      * <p>
    140      * <code>NaN</code> values are considered to globally affect the coordinates
    141      * and be equal to each other - i.e, if either (or all) values of the
    142      * coordinate set are equal to <code>Double.NaN</code>, the set as a whole is
    143      * considered to equal <code>NaN</code>.
    144      * </p>
    145      *
    146      * @param other Object to test for equality to this
    147      * @return true if two PolarCoordinates objects are equal, false if
    148      *         object is null, not an instance of PolarCoordinates, or
    149      *         not equal to this PolarCoordinates instance
    150      *
    151      */
    152     @Override
    153     public boolean equals(final Object other) {
    154         if (this == other) {
    155             return true;
    156         }
    157         if (other instanceof PolarCoordinates) {
    158             final PolarCoordinates rhs = (PolarCoordinates) other;
    159             if (rhs.isNaN()) {
    160                 return this.isNaN();
    161             }
    162
    163             return Double.compare(radius, rhs.radius) == 0 &&
    164                     Double.compare(azimuth, rhs.azimuth) == 0;
    165         }
    166         return false;
    167     }
    168
    169     /** {@inheritDoc} */
    170     @Override
    171     public String toString() {
    172         return SimpleTupleFormat.getDefault().format(radius, azimuth);
    173     }
    174
    175     /** Return a new instance with the given polar coordinate values.
    176      * The values are normalized so that {@code radius} lies in the range {@code [0, +Infinity)}
    177      * and {@code azimuth} in the range {@code [0, 2pi)}.
    178      * @param radius Radius value.
    179      * @param azimuth Azimuth angle in radians.
    180      * @return new {@link PolarCoordinates} instance
    181      */
    182     public static PolarCoordinates of(final double radius, final double azimuth) {
    183         return new PolarCoordinates(radius, azimuth);
    184     }
    185
    186     /** Convert the given Cartesian coordinates to polar form.
    187      * @param x X coordinate value
    188      * @param y Y coordinate value
    189      * @return polar coordinates equivalent to the given Cartesian coordinates
    190      */
    191     public static PolarCoordinates fromCartesian(final double x, final double y) {
    192         final double azimuth = Math.atan2(y, x);
    193         final double radius = Math.hypot(x, y);
    194
    195         return new PolarCoordinates(radius, azimuth);
    196     }
    197
    198     /** Convert the given Cartesian coordinates to polar form.
    199      * @param vec vector containing Cartesian coordinates
    200      * @return polar coordinates equivalent to the given Cartesian coordinates
    201      */
    202     public static PolarCoordinates fromCartesian(final Vector2D vec) {
    203         return fromCartesian(vec.getX(), vec.getY());
    204     }
    205
    206     /** Convert the given polar coordinates to Cartesian form.
    207      * @param radius Radius value.
    208      * @param azimuth Azimuth angle in radians.
    209      * @return A 2-dimensional vector with an equivalent set of
    210      *      coordinates in Cartesian form
    211      */
    212     public static Vector2D toCartesian(final double radius, final double azimuth) {
    213         final double x = radius * Math.cos(azimuth);
    214         final double y = radius * Math.sin(azimuth);
    215
    216         return Vector2D.of(x, y);
    217     }
    218
    219     /** Parse the given string and return a new polar coordinates instance. The parsed
    220      * coordinates are normalized as in the {@link #of(double, double)} method. The expected string
    221      * format is the same as that returned by {@link #toString()}.
    222      * @param input the string to parse
    223      * @return new {@link PolarCoordinates} instance
    224      * @throws IllegalArgumentException if the string format is invalid.
    225      */
    226     public static PolarCoordinates parse(final String input) {
    227         return SimpleTupleFormat.getDefault().parse(input, PolarCoordinates::new);
    228     }
    229
    230     /** Normalize an azimuth value to be within the range {@code [0, 2pi)}.
    231      * @param azimuth azimuth value in radians
    232      * @return equivalent azimuth value in the range {@code [0, 2pi)}.
    233      */
    234     public static double normalizeAzimuth(final double azimuth) {
    235         if (Double.isFinite(azimuth)) {
    236             return Angle.Rad.WITHIN_0_AND_2PI.applyAsDouble(azimuth);
    237         }
    238
    239         return azimuth;
    240     }
    241 }