001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.geometry.euclidean.twod;
018
019import org.apache.commons.geometry.core.Spatial;
020import org.apache.commons.geometry.core.internal.SimpleTupleFormat;
021import org.apache.commons.numbers.angle.Angle;
022
023/** Class representing <a href="https://en.wikipedia.org/wiki/Polar_coordinate_system">polar coordinates</a>
024 * in 2 dimensional Euclidean space.
025 *
026 * <p>Polar coordinates are defined by a distance from a reference point
027 * and an angle from a reference direction. The distance value is called
028 * the radial coordinate, or <em>radius</em>, and the angle is called the angular coordinate,
029 * or <em>azimuth</em>. This class follows the standard
030 * mathematical convention of using the positive x-axis as the reference
031 * direction and measuring positive angles counter-clockwise, toward the
032 * positive y-axis. The origin is used as the reference point. Polar coordinate
033 * are related to Cartesian coordinates as follows:
034 * <pre>
035 * x = r * cos(&theta;)
036 * y = r * sin(&theta;)
037 *
038 * r = &radic;(x^2 + y^2)
039 * &theta; = atan2(y, x)
040 * </pre>
041 * where <em>r</em> is the radius and <em>&theta;</em> is the azimuth of the polar coordinates.
042 *
043 * <p>In order to ensure the uniqueness of coordinate sets, coordinate values
044 * are normalized so that {@code radius} is in the range {@code [0, +Infinity)}
045 * and {@code azimuth} is in the range {@code [0, 2pi)}.</p>
046 *
047 * @see <a href="https://en.wikipedia.org/wiki/Polar_coordinate_system">Polar Coordinate System</a>
048 */
049public final class PolarCoordinates implements Spatial {
050    /** Radius value. */
051    private final double radius;
052
053    /** Azimuth angle in radians. */
054    private final double azimuth;
055
056    /** Simple constructor. Input values are normalized.
057     * @param radius Radius value.
058     * @param azimuth Azimuth angle in radians.
059     */
060    private PolarCoordinates(final double radius, final double azimuth) {
061        double rad = radius;
062        double az = azimuth;
063
064        if (rad < 0) {
065            // negative radius; flip the angles
066            rad = Math.abs(radius);
067            az += Math.PI;
068        }
069
070        this.radius = rad;
071        this.azimuth = normalizeAzimuth(az);
072    }
073
074    /** Return the radius value. The value will be greater than or equal to 0.
075     * @return radius value
076     */
077    public double getRadius() {
078        return radius;
079    }
080
081    /** Return the azimuth angle in radians. The value will be
082     * in the range {@code [0, 2pi)}.
083     * @return azimuth value in radians.
084     */
085    public double getAzimuth() {
086        return azimuth;
087    }
088
089    /** {@inheritDoc} */
090    @Override
091    public int getDimension() {
092        return 2;
093    }
094
095    /** {@inheritDoc} */
096    @Override
097    public boolean isNaN() {
098        return Double.isNaN(radius) || Double.isNaN(azimuth);
099    }
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}