PolarCoordinates.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.euclidean.twod;

  18. import org.apache.commons.geometry.core.Spatial;
  19. import org.apache.commons.geometry.core.internal.SimpleTupleFormat;
  20. import org.apache.commons.numbers.angle.Angle;

  21. /** Class representing <a href="https://en.wikipedia.org/wiki/Polar_coordinate_system">polar coordinates</a>
  22.  * in 2 dimensional Euclidean space.
  23.  *
  24.  * <p>Polar coordinates are defined by a distance from a reference point
  25.  * and an angle from a reference direction. The distance value is called
  26.  * the radial coordinate, or <em>radius</em>, and the angle is called the angular coordinate,
  27.  * or <em>azimuth</em>. This class follows the standard
  28.  * mathematical convention of using the positive x-axis as the reference
  29.  * direction and measuring positive angles counter-clockwise, toward the
  30.  * positive y-axis. The origin is used as the reference point. Polar coordinate
  31.  * are related to Cartesian coordinates as follows:
  32.  * <pre>
  33.  * x = r * cos(&theta;)
  34.  * y = r * sin(&theta;)
  35.  *
  36.  * r = &radic;(x^2 + y^2)
  37.  * &theta; = atan2(y, x)
  38.  * </pre>
  39.  * where <em>r</em> is the radius and <em>&theta;</em> is the azimuth of the polar coordinates.
  40.  *
  41.  * <p>In order to ensure the uniqueness of coordinate sets, coordinate values
  42.  * are normalized so that {@code radius} is in the range {@code [0, +Infinity)}
  43.  * and {@code azimuth} is in the range {@code [0, 2pi)}.</p>
  44.  *
  45.  * @see <a href="https://en.wikipedia.org/wiki/Polar_coordinate_system">Polar Coordinate System</a>
  46.  */
  47. public final class PolarCoordinates implements Spatial {
  48.     /** Radius value. */
  49.     private final double radius;

  50.     /** Azimuth angle in radians. */
  51.     private final double azimuth;

  52.     /** Simple constructor. Input values are normalized.
  53.      * @param radius Radius value.
  54.      * @param azimuth Azimuth angle in radians.
  55.      */
  56.     private PolarCoordinates(final double radius, final double azimuth) {
  57.         double rad = radius;
  58.         double az = azimuth;

  59.         if (rad < 0) {
  60.             // negative radius; flip the angles
  61.             rad = Math.abs(radius);
  62.             az += Math.PI;
  63.         }

  64.         this.radius = rad;
  65.         this.azimuth = normalizeAzimuth(az);
  66.     }

  67.     /** Return the radius value. The value will be greater than or equal to 0.
  68.      * @return radius value
  69.      */
  70.     public double getRadius() {
  71.         return radius;
  72.     }

  73.     /** Return the azimuth angle in radians. The value will be
  74.      * in the range {@code [0, 2pi)}.
  75.      * @return azimuth value in radians.
  76.      */
  77.     public double getAzimuth() {
  78.         return azimuth;
  79.     }

  80.     /** {@inheritDoc} */
  81.     @Override
  82.     public int getDimension() {
  83.         return 2;
  84.     }

  85.     /** {@inheritDoc} */
  86.     @Override
  87.     public boolean isNaN() {
  88.         return Double.isNaN(radius) || Double.isNaN(azimuth);
  89.     }

  90.     /** {@inheritDoc} */
  91.     @Override
  92.     public boolean isInfinite() {
  93.         return !isNaN() && (Double.isInfinite(radius) || Double.isInfinite(azimuth));
  94.     }

  95.     /** {@inheritDoc} */
  96.     @Override
  97.     public boolean isFinite() {
  98.         return Double.isFinite(radius) && Double.isFinite(azimuth);
  99.     }

  100.     /** Convert this set of polar coordinates to Cartesian coordinates.
  101.      * @return A 2-dimensional vector with an equivalent set of
  102.      *      coordinates in Cartesian form
  103.      */
  104.     public Vector2D toCartesian() {
  105.         return toCartesian(radius, azimuth);
  106.     }

  107.     /** Get a hashCode for this set of polar coordinates.
  108.      * <p>All NaN values have the same hash code.</p>
  109.      *
  110.      * @return a hash code value for this object
  111.      */
  112.     @Override
  113.     public int hashCode() {
  114.         if (isNaN()) {
  115.             return 191;
  116.         }
  117.         return 449 * (76 * Double.hashCode(radius) + Double.hashCode(azimuth));
  118.     }

  119.     /** Test for the equality of two sets of polar coordinates.
  120.      * <p>
  121.      * If all values of two sets of coordinates are exactly the same, and none are
  122.      * <code>Double.NaN</code>, the two sets are considered to be equal.
  123.      * </p>
  124.      * <p>
  125.      * <code>NaN</code> values are considered to globally affect the coordinates
  126.      * and be equal to each other - i.e, if either (or all) values of the
  127.      * coordinate set are equal to <code>Double.NaN</code>, the set as a whole is
  128.      * considered to equal <code>NaN</code>.
  129.      * </p>
  130.      *
  131.      * @param other Object to test for equality to this
  132.      * @return true if two PolarCoordinates objects are equal, false if
  133.      *         object is null, not an instance of PolarCoordinates, or
  134.      *         not equal to this PolarCoordinates instance
  135.      *
  136.      */
  137.     @Override
  138.     public boolean equals(final Object other) {
  139.         if (this == other) {
  140.             return true;
  141.         }
  142.         if (other instanceof PolarCoordinates) {
  143.             final PolarCoordinates rhs = (PolarCoordinates) other;
  144.             if (rhs.isNaN()) {
  145.                 return this.isNaN();
  146.             }

  147.             return Double.compare(radius, rhs.radius) == 0 &&
  148.                     Double.compare(azimuth, rhs.azimuth) == 0;
  149.         }
  150.         return false;
  151.     }

  152.     /** {@inheritDoc} */
  153.     @Override
  154.     public String toString() {
  155.         return SimpleTupleFormat.getDefault().format(radius, azimuth);
  156.     }

  157.     /** Return a new instance with the given polar coordinate values.
  158.      * The values are normalized so that {@code radius} lies in the range {@code [0, +Infinity)}
  159.      * and {@code azimuth} in the range {@code [0, 2pi)}.
  160.      * @param radius Radius value.
  161.      * @param azimuth Azimuth angle in radians.
  162.      * @return new {@link PolarCoordinates} instance
  163.      */
  164.     public static PolarCoordinates of(final double radius, final double azimuth) {
  165.         return new PolarCoordinates(radius, azimuth);
  166.     }

  167.     /** Convert the given Cartesian coordinates to polar form.
  168.      * @param x X coordinate value
  169.      * @param y Y coordinate value
  170.      * @return polar coordinates equivalent to the given Cartesian coordinates
  171.      */
  172.     public static PolarCoordinates fromCartesian(final double x, final double y) {
  173.         final double azimuth = Math.atan2(y, x);
  174.         final double radius = Math.hypot(x, y);

  175.         return new PolarCoordinates(radius, azimuth);
  176.     }

  177.     /** Convert the given Cartesian coordinates to polar form.
  178.      * @param vec vector containing Cartesian coordinates
  179.      * @return polar coordinates equivalent to the given Cartesian coordinates
  180.      */
  181.     public static PolarCoordinates fromCartesian(final Vector2D vec) {
  182.         return fromCartesian(vec.getX(), vec.getY());
  183.     }

  184.     /** Convert the given polar coordinates to Cartesian form.
  185.      * @param radius Radius value.
  186.      * @param azimuth Azimuth angle in radians.
  187.      * @return A 2-dimensional vector with an equivalent set of
  188.      *      coordinates in Cartesian form
  189.      */
  190.     public static Vector2D toCartesian(final double radius, final double azimuth) {
  191.         final double x = radius * Math.cos(azimuth);
  192.         final double y = radius * Math.sin(azimuth);

  193.         return Vector2D.of(x, y);
  194.     }

  195.     /** Parse the given string and return a new polar coordinates instance. The parsed
  196.      * coordinates are normalized as in the {@link #of(double, double)} method. The expected string
  197.      * format is the same as that returned by {@link #toString()}.
  198.      * @param input the string to parse
  199.      * @return new {@link PolarCoordinates} instance
  200.      * @throws IllegalArgumentException if the string format is invalid.
  201.      */
  202.     public static PolarCoordinates parse(final String input) {
  203.         return SimpleTupleFormat.getDefault().parse(input, PolarCoordinates::new);
  204.     }

  205.     /** Normalize an azimuth value to be within the range {@code [0, 2pi)}.
  206.      * @param azimuth azimuth value in radians
  207.      * @return equivalent azimuth value in the range {@code [0, 2pi)}.
  208.      */
  209.     public static double normalizeAzimuth(final double azimuth) {
  210.         if (Double.isFinite(azimuth)) {
  211.             return Angle.Rad.WITHIN_0_AND_2PI.applyAsDouble(azimuth);
  212.         }

  213.         return azimuth;
  214.     }
  215. }