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(θ) 036 * y = r * sin(θ) 037 * 038 * r = √(x^2 + y^2) 039 * θ = atan2(y, x) 040 * </pre> 041 * where <em>r</em> is the radius and <em>θ</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}