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.math3.geometry.spherical.twod; 018 019import org.apache.commons.math3.exception.MathArithmeticException; 020import org.apache.commons.math3.exception.OutOfRangeException; 021import org.apache.commons.math3.geometry.Point; 022import org.apache.commons.math3.geometry.Space; 023import org.apache.commons.math3.geometry.euclidean.threed.Vector3D; 024import org.apache.commons.math3.util.FastMath; 025import org.apache.commons.math3.util.MathUtils; 026 027/** This class represents a point on the 2-sphere. 028 * <p> 029 * We use the mathematical convention to use the azimuthal angle \( \theta \) 030 * in the x-y plane as the first coordinate, and the polar angle \( \varphi \) 031 * as the second coordinate (see <a 032 * href="http://mathworld.wolfram.com/SphericalCoordinates.html">Spherical 033 * Coordinates</a> in MathWorld). 034 * </p> 035 * <p>Instances of this class are guaranteed to be immutable.</p> 036 * @since 3.3 037 */ 038public class S2Point implements Point<Sphere2D> { 039 040 /** +I (coordinates: \( \theta = 0, \varphi = \pi/2 \)). */ 041 public static final S2Point PLUS_I = new S2Point(0, 0.5 * FastMath.PI, Vector3D.PLUS_I); 042 043 /** +J (coordinates: \( \theta = \pi/2, \varphi = \pi/2 \))). */ 044 public static final S2Point PLUS_J = new S2Point(0.5 * FastMath.PI, 0.5 * FastMath.PI, Vector3D.PLUS_J); 045 046 /** +K (coordinates: \( \theta = any angle, \varphi = 0 \)). */ 047 public static final S2Point PLUS_K = new S2Point(0, 0, Vector3D.PLUS_K); 048 049 /** -I (coordinates: \( \theta = \pi, \varphi = \pi/2 \)). */ 050 public static final S2Point MINUS_I = new S2Point(FastMath.PI, 0.5 * FastMath.PI, Vector3D.MINUS_I); 051 052 /** -J (coordinates: \( \theta = 3\pi/2, \varphi = \pi/2 \)). */ 053 public static final S2Point MINUS_J = new S2Point(1.5 * FastMath.PI, 0.5 * FastMath.PI, Vector3D.MINUS_J); 054 055 /** -K (coordinates: \( \theta = any angle, \varphi = \pi \)). */ 056 public static final S2Point MINUS_K = new S2Point(0, FastMath.PI, Vector3D.MINUS_K); 057 058 // CHECKSTYLE: stop ConstantName 059 /** A vector with all coordinates set to NaN. */ 060 public static final S2Point NaN = new S2Point(Double.NaN, Double.NaN, Vector3D.NaN); 061 // CHECKSTYLE: resume ConstantName 062 063 /** Serializable UID. */ 064 private static final long serialVersionUID = 20131218L; 065 066 /** Azimuthal angle \( \theta \) in the x-y plane. */ 067 private final double theta; 068 069 /** Polar angle \( \varphi \). */ 070 private final double phi; 071 072 /** Corresponding 3D normalized vector. */ 073 private final Vector3D vector; 074 075 /** Simple constructor. 076 * Build a vector from its spherical coordinates 077 * @param theta azimuthal angle \( \theta \) in the x-y plane 078 * @param phi polar angle \( \varphi \) 079 * @see #getTheta() 080 * @see #getPhi() 081 * @exception OutOfRangeException if \( \varphi \) is not in the [\( 0; \pi \)] range 082 */ 083 public S2Point(final double theta, final double phi) 084 throws OutOfRangeException { 085 this(theta, phi, vector(theta, phi)); 086 } 087 088 /** Simple constructor. 089 * Build a vector from its underlying 3D vector 090 * @param vector 3D vector 091 * @exception MathArithmeticException if vector norm is zero 092 */ 093 public S2Point(final Vector3D vector) throws MathArithmeticException { 094 this(FastMath.atan2(vector.getY(), vector.getX()), Vector3D.angle(Vector3D.PLUS_K, vector), 095 vector.normalize()); 096 } 097 098 /** Build a point from its internal components. 099 * @param theta azimuthal angle \( \theta \) in the x-y plane 100 * @param phi polar angle \( \varphi \) 101 * @param vector corresponding vector 102 */ 103 private S2Point(final double theta, final double phi, final Vector3D vector) { 104 this.theta = theta; 105 this.phi = phi; 106 this.vector = vector; 107 } 108 109 /** Build the normalized vector corresponding to spherical coordinates. 110 * @param theta azimuthal angle \( \theta \) in the x-y plane 111 * @param phi polar angle \( \varphi \) 112 * @return normalized vector 113 * @exception OutOfRangeException if \( \varphi \) is not in the [\( 0; \pi \)] range 114 */ 115 private static Vector3D vector(final double theta, final double phi) 116 throws OutOfRangeException { 117 118 if (phi < 0 || phi > FastMath.PI) { 119 throw new OutOfRangeException(phi, 0, FastMath.PI); 120 } 121 122 final double cosTheta = FastMath.cos(theta); 123 final double sinTheta = FastMath.sin(theta); 124 final double cosPhi = FastMath.cos(phi); 125 final double sinPhi = FastMath.sin(phi); 126 127 return new Vector3D(cosTheta * sinPhi, sinTheta * sinPhi, cosPhi); 128 129 } 130 131 /** Get the azimuthal angle \( \theta \) in the x-y plane. 132 * @return azimuthal angle \( \theta \) in the x-y plane 133 * @see #S2Point(double, double) 134 */ 135 public double getTheta() { 136 return theta; 137 } 138 139 /** Get the polar angle \( \varphi \). 140 * @return polar angle \( \varphi \) 141 * @see #S2Point(double, double) 142 */ 143 public double getPhi() { 144 return phi; 145 } 146 147 /** Get the corresponding normalized vector in the 3D euclidean space. 148 * @return normalized vector 149 */ 150 public Vector3D getVector() { 151 return vector; 152 } 153 154 /** {@inheritDoc} */ 155 public Space getSpace() { 156 return Sphere2D.getInstance(); 157 } 158 159 /** {@inheritDoc} */ 160 public boolean isNaN() { 161 return Double.isNaN(theta) || Double.isNaN(phi); 162 } 163 164 /** Get the opposite of the instance. 165 * @return a new vector which is opposite to the instance 166 */ 167 public S2Point negate() { 168 return new S2Point(-theta, FastMath.PI - phi, vector.negate()); 169 } 170 171 /** {@inheritDoc} */ 172 public double distance(final Point<Sphere2D> point) { 173 return distance(this, (S2Point) point); 174 } 175 176 /** Compute the distance (angular separation) between two points. 177 * @param p1 first vector 178 * @param p2 second vector 179 * @return the angular separation between p1 and p2 180 */ 181 public static double distance(S2Point p1, S2Point p2) { 182 return Vector3D.angle(p1.vector, p2.vector); 183 } 184 185 /** 186 * Test for the equality of two points on the 2-sphere. 187 * <p> 188 * If all coordinates of two points are exactly the same, and none are 189 * <code>Double.NaN</code>, the two points are considered to be equal. 190 * </p> 191 * <p> 192 * <code>NaN</code> coordinates are considered to affect globally the vector 193 * and be equals to each other - i.e, if either (or all) coordinates of the 194 * 2D vector are equal to <code>Double.NaN</code>, the 2D vector is equal to 195 * {@link #NaN}. 196 * </p> 197 * 198 * @param other Object to test for equality to this 199 * @return true if two points on the 2-sphere objects are equal, false if 200 * object is null, not an instance of S2Point, or 201 * not equal to this S2Point instance 202 * 203 */ 204 @Override 205 public boolean equals(Object other) { 206 207 if (this == other) { 208 return true; 209 } 210 211 if (other instanceof S2Point) { 212 final S2Point rhs = (S2Point) other; 213 if (rhs.isNaN()) { 214 return this.isNaN(); 215 } 216 217 return (theta == rhs.theta) && (phi == rhs.phi); 218 } 219 return false; 220 } 221 222 /** 223 * Get a hashCode for the 2D vector. 224 * <p> 225 * All NaN values have the same hash code.</p> 226 * 227 * @return a hash code value for this object 228 */ 229 @Override 230 public int hashCode() { 231 if (isNaN()) { 232 return 542; 233 } 234 return 134 * (37 * MathUtils.hash(theta) + MathUtils.hash(phi)); 235 } 236 237}