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 }