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.spherical.twod; 18 19 import java.util.Objects; 20 21 import org.apache.commons.geometry.core.Transform; 22 import org.apache.commons.geometry.core.partitioning.AbstractHyperplane; 23 import org.apache.commons.geometry.core.partitioning.EmbeddingHyperplane; 24 import org.apache.commons.geometry.core.partitioning.Hyperplane; 25 import org.apache.commons.geometry.euclidean.threed.Vector3D; 26 import org.apache.commons.geometry.spherical.oned.AngularInterval; 27 import org.apache.commons.geometry.spherical.oned.Point1S; 28 import org.apache.commons.numbers.angle.Angle; 29 import org.apache.commons.numbers.core.Precision; 30 31 /** Class representing a great circle on the 2-sphere. A great circle is the 32 * intersection of a sphere with a plane that passes through its center. It is 33 * the largest diameter circle that can be drawn on the sphere and partitions the 34 * sphere into two hemispheres. The vectors {@code u} and {@code v} lie in the great 35 * circle plane, while the vector {@code w} (the pole) is perpendicular to it. The 36 * pole vector points toward the <em>minus</em> side of the hyperplane. 37 * 38 * <p>Instances of this class are guaranteed to be immutable.</p> 39 * @see GreatCircles 40 */ 41 public final class GreatCircle extends AbstractHyperplane<Point2S> 42 implements EmbeddingHyperplane<Point2S, Point1S> { 43 /** Pole or circle center. */ 44 private final Vector3D.Unit pole; 45 46 /** First axis in the equator plane, origin of the azimuth angles. */ 47 private final Vector3D.Unit u; 48 49 /** Second axis in the equator plane, in quadrature with respect to u. */ 50 private final Vector3D.Unit v; 51 52 /** Simple constructor. Callers are responsible for ensuring the inputs are valid. 53 * @param pole pole vector of the great circle 54 * @param u u axis in the equator plane 55 * @param v v axis in the equator plane 56 * @param precision precision context used for floating point comparisons 57 */ 58 GreatCircle(final Vector3D.Unit pole, final Vector3D.Unit u, final Vector3D.Unit v, 59 final Precision.DoubleEquivalence precision) { 60 super(precision); 61 62 this.pole = pole; 63 this.u = u; 64 this.v = v; 65 } 66 67 /** Get the pole of the great circle. This vector is perpendicular to the 68 * equator plane of the instance. 69 * @return pole of the great circle 70 */ 71 public Vector3D.Unit getPole() { 72 return pole; 73 } 74 75 /** Get the spherical point located at the positive pole of the instance. 76 * @return the spherical point located at the positive pole of the instance 77 */ 78 public Point2S getPolePoint() { 79 return Point2S.from(pole); 80 } 81 82 /** Get the u axis of the great circle. This vector is located in the equator plane and defines 83 * the {@code 0pi} location of the embedded subspace. 84 * @return u axis of the great circle 85 */ 86 public Vector3D.Unit getU() { 87 return u; 88 } 89 90 /** Get the v axis of the great circle. This vector lies in the equator plane, 91 * perpendicular to the u-axis. 92 * @return v axis of the great circle 93 */ 94 public Vector3D.Unit getV() { 95 return v; 96 } 97 98 /** Get the w (pole) axis of the great circle. The method is equivalent to {@code #getPole()}. 99 * @return the w (pole) axis of the great circle. 100 * @see #getPole() 101 */ 102 public Vector3D.Unit getW() { 103 return getPole(); 104 } 105 106 /** {@inheritDoc} 107 * 108 * <p>The returned offset values are in the range {@code [-pi/2, +pi/2]}, 109 * with a point directly on the circle's pole vector having an offset of 110 * {@code -pi/2} and its antipodal point having an offset of {@code +pi/2}. 111 * Thus, the circle's pole vector points toward the <em>minus</em> side of 112 * the hyperplane.</p> 113 * 114 * @see #offset(Vector3D) 115 */ 116 @Override 117 public double offset(final Point2S point) { 118 return offset(point.getVector()); 119 } 120 121 /** Get the offset (oriented distance) of a direction. 122 * 123 * <p>The offset computed here is equal to the angle between the circle's 124 * pole and the given vector minus {@code pi/2}. Thus, the pole vector 125 * has an offset of {@code -pi/2}, a point on the circle itself has an 126 * offset of {@code 0}, and the negation of the pole vector has an offset 127 * of {@code +pi/2}.</p> 128 * @param vec vector to compute the offset for 129 * @return the offset (oriented distance) of a direction 130 */ 131 public double offset(final Vector3D vec) { 132 return pole.angle(vec) - Angle.PI_OVER_TWO; 133 } 134 135 /** Get the azimuth angle of a point relative to this great circle instance, 136 * in the range {@code [0, 2pi)}. 137 * @param pt point to compute the azimuth for 138 * @return azimuth angle of the point in the range {@code [0, 2pi)} 139 */ 140 public double azimuth(final Point2S pt) { 141 return azimuth(pt.getVector()); 142 } 143 144 /** Get the azimuth angle of a vector in the range {@code [0, 2pi)}. 145 * The azimuth angle is the angle of the projection of the argument on the 146 * equator plane relative to the plane's u-axis. Since the vector is 147 * projected onto the equator plane, it does not need to belong to the circle. 148 * Vectors parallel to the great circle's pole do not have a defined azimuth angle. 149 * In these cases, the method follows the rules of the 150 * {@code Math#atan2(double, double)} method and returns {@code 0}. 151 * @param vector vector to compute the great circle azimuth of 152 * @return azimuth angle of the vector around the great circle in the range 153 * {@code [0, 2pi)} 154 * @see #toSubspace(Point2S) 155 */ 156 public double azimuth(final Vector3D vector) { 157 double az = Math.atan2(vector.dot(v), vector.dot(u)); 158 159 // adjust range 160 if (az < 0) { 161 az += Angle.TWO_PI; 162 } 163 164 return az; 165 } 166 167 /** Get the vector on the great circle with the given azimuth angle. 168 * @param azimuth azimuth angle in radians 169 * @return the point on the great circle with the given phase angle 170 */ 171 public Vector3D vectorAt(final double azimuth) { 172 return Vector3D.Sum.create() 173 .addScaled(Math.cos(azimuth), u) 174 .addScaled(Math.sin(azimuth), v).get(); 175 } 176 177 /** {@inheritDoc} */ 178 @Override 179 public Point2S project(final Point2S point) { 180 final double az = azimuth(point.getVector()); 181 return Point2S.from(vectorAt(az)); 182 } 183 184 /** {@inheritDoc} 185 * 186 * <p>The returned instance has the same u-axis but opposite pole and v-axis 187 * as this instance.</p> 188 */ 189 @Override 190 public GreatCircle reverse() { 191 return new GreatCircle(pole.negate(), u, v.negate(), getPrecision()); 192 } 193 194 /** {@inheritDoc} */ 195 @Override 196 public GreatCircle transform(final Transform<Point2S> transform) { 197 final Point2S tu = transform.apply(Point2S.from(u)); 198 final Point2S tv = transform.apply(Point2S.from(v)); 199 200 return GreatCircles.fromPoints(tu, tv, getPrecision()); 201 } 202 203 /** {@inheritDoc} */ 204 @Override 205 public boolean similarOrientation(final Hyperplane<Point2S> other) { 206 final GreatCircle otherCircle = (GreatCircle) other; 207 return pole.dot(otherCircle.pole) > 0.0; 208 } 209 210 /** {@inheritDoc} */ 211 @Override 212 public GreatArc span() { 213 return GreatCircles.arcFromInterval(this, AngularInterval.full()); 214 } 215 216 /** Create an arc on this circle between the given points. 217 * @param start start point 218 * @param end end point 219 * @return an arc on this circle between the given points 220 * @throws IllegalArgumentException if the specified interval is not 221 * convex (ie, the angle between the points is greater than {@code pi} 222 */ 223 public GreatArc arc(final Point2S start, final Point2S end) { 224 return arc(toSubspace(start), toSubspace(end)); 225 } 226 227 /** Create an arc on this circle between the given subspace points. 228 * @param start start subspace point 229 * @param end end subspace point 230 * @return an arc on this circle between the given subspace points 231 * @throws IllegalArgumentException if the specified interval is not 232 * convex (ie, the angle between the points is greater than {@code pi} 233 */ 234 public GreatArc arc(final Point1S start, final Point1S end) { 235 return arc(start.getAzimuth(), end.getAzimuth()); 236 } 237 238 /** Create an arc on this circle between the given subspace azimuth values. 239 * @param start start subspace azimuth 240 * @param end end subspace azimuth 241 * @return an arc on this circle between the given subspace azimuths 242 * @throws IllegalArgumentException if the specified interval is not 243 * convex (ie, the angle between the points is greater than {@code pi} 244 */ 245 public GreatArc arc(final double start, final double end) { 246 return arc(AngularInterval.Convex.of(start, end, getPrecision())); 247 } 248 249 /** Create an arc on this circle consisting of the given subspace interval. 250 * @param interval subspace interval 251 * @return an arc on this circle consisting of the given subspace interval 252 */ 253 public GreatArc arc(final AngularInterval.Convex interval) { 254 return GreatCircles.arcFromInterval(this, interval); 255 } 256 257 /** Return one of the two intersection points between this instance and the argument. 258 * If the circles occupy the same space (ie, their poles are parallel or anti-parallel), 259 * then null is returned. Otherwise, the intersection located at the cross product of 260 * the pole of this instance and that of the argument is returned (ie, {@code thisPole.cross(otherPole)}. 261 * The other intersection point of the pair is antipodal to this point. 262 * @param other circle to intersect with 263 * @return one of the two intersection points between this instance and the argument 264 */ 265 public Point2S intersection(final GreatCircle other) { 266 final Vector3D cross = pole.cross(other.pole); 267 if (!cross.eq(Vector3D.ZERO, getPrecision())) { 268 return Point2S.from(cross); 269 } 270 271 return null; 272 } 273 274 /** Compute the angle between this great circle and the argument. 275 * The return value is the angle between the poles of the two circles, 276 * in the range {@code [0, pi]}. 277 * @param other great circle to compute the angle with 278 * @return the angle between this great circle and the argument in the 279 * range {@code [0, pi]} 280 * @see #angle(GreatCircle, Point2S) 281 */ 282 public double angle(final GreatCircle other) { 283 return pole.angle(other.pole); 284 } 285 286 /** Compute the angle between this great circle and the argument, measured 287 * at the intersection point closest to the given point. The value is computed 288 * as if a tangent line was drawn from each great circle at the intersection 289 * point closest to {@code pt}, and the angle required to rotate the tangent 290 * line representing the current instance to align with that of the given 291 * instance was measured. The return value lies in the range {@code [-pi, pi)} and 292 * has an absolute value equal to that returned by {@link #angle(GreatCircle)}, but 293 * possibly a different sign. If the given point is equidistant from both intersection 294 * points (as evaluated by this instance's precision context), then the point is assumed 295 * to be closest to the point opposite the cross product of the two poles. 296 * @param other great circle to compute the angle with 297 * @param pt point determining the circle intersection to compute the angle at 298 * @return the angle between this great circle and the argument as measured at the 299 * intersection point closest to the given point; the value is in the range 300 * {@code [-pi, pi)} 301 * @see #angle(GreatCircle) 302 */ 303 public double angle(final GreatCircle other, final Point2S pt) { 304 final double theta = angle(other); 305 final Vector3D cross = pole.cross(other.pole); 306 307 return getPrecision().gt(pt.getVector().dot(cross), 0) ? 308 theta : 309 -theta; 310 } 311 312 /** {@inheritDoc} */ 313 @Override 314 public Point1S toSubspace(final Point2S point) { 315 return Point1S.of(azimuth(point.getVector())); 316 } 317 318 /** {@inheritDoc} */ 319 @Override 320 public Point2S toSpace(final Point1S point) { 321 return Point2S.from(vectorAt(point.getAzimuth())); 322 } 323 324 /** Return true if this instance should be considered equivalent to the argument, using the 325 * given precision context for comparison. Instances are considered equivalent if have equivalent 326 * {@code pole}, {@code u}, and {@code v} vectors. 327 * @param other great circle to compare with 328 * @param precision precision context to use for the comparison 329 * @return true if this instance should be considered equivalent to the argument 330 * @see Vector3D#eq(Vector3D, Precision.DoubleEquivalence) 331 */ 332 public boolean eq(final GreatCircle other, final Precision.DoubleEquivalence precision) { 333 return pole.eq(other.pole, precision) && 334 u.eq(other.u, precision) && 335 v.eq(other.v, precision); 336 } 337 338 /** {@inheritDoc} */ 339 @Override 340 public int hashCode() { 341 return Objects.hash(pole, u, v, getPrecision()); 342 } 343 344 /** {@inheritDoc} */ 345 @Override 346 public boolean equals(final Object obj) { 347 if (this == obj) { 348 return true; 349 } else if (!(obj instanceof GreatCircle)) { 350 return false; 351 } 352 353 final GreatCircle other = (GreatCircle) obj; 354 355 return Objects.equals(this.pole, other.pole) && 356 Objects.equals(this.u, other.u) && 357 Objects.equals(this.v, other.v) && 358 Objects.equals(this.getPrecision(), other.getPrecision()); 359 } 360 361 /** {@inheritDoc} */ 362 @Override 363 public String toString() { 364 final StringBuilder sb = new StringBuilder(); 365 sb.append(getClass().getSimpleName()) 366 .append("[pole= ") 367 .append(pole) 368 .append(", u= ") 369 .append(u) 370 .append(", v= ") 371 .append(v) 372 .append(']'); 373 374 return sb.toString(); 375 } 376 }