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(θ) 36 * y = r * sin(θ) 37 * 38 * r = √(x^2 + y^2) 39 * θ = atan2(y, x) 40 * </pre> 41 * where <em>r</em> is the radius and <em>θ</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 }