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 * https://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 18 /* 19 * This is not the original file distributed by the Apache Software Foundation 20 * It has been modified by the Hipparchus project 21 */ 22 package org.hipparchus.geometry.spherical.twod; 23 24 import org.hipparchus.exception.MathIllegalArgumentException; 25 import org.hipparchus.geometry.Point; 26 import org.hipparchus.geometry.euclidean.threed.Rotation; 27 import org.hipparchus.geometry.euclidean.threed.Vector3D; 28 import org.hipparchus.geometry.partitioning.Embedding; 29 import org.hipparchus.geometry.partitioning.Hyperplane; 30 import org.hipparchus.geometry.partitioning.RegionFactory; 31 import org.hipparchus.geometry.partitioning.SubHyperplane; 32 import org.hipparchus.geometry.partitioning.Transform; 33 import org.hipparchus.geometry.spherical.oned.Arc; 34 import org.hipparchus.geometry.spherical.oned.ArcsSet; 35 import org.hipparchus.geometry.spherical.oned.S1Point; 36 import org.hipparchus.geometry.spherical.oned.Sphere1D; 37 import org.hipparchus.util.FastMath; 38 import org.hipparchus.util.SinCos; 39 40 /** This class represents an oriented great circle on the 2-sphere. 41 42 * <p>An oriented circle can be defined by a center point. The circle 43 * is the the set of points that are in the normal plan the center.</p> 44 45 * <p>Since it is oriented the two spherical caps at its two sides are 46 * unambiguously identified as a left cap and a right cap. This can be 47 * used to identify the interior and the exterior in a simple way by 48 * local properties only when part of a line is used to define part of 49 * a spherical polygon boundary.</p> 50 51 */ 52 public class Circle implements Hyperplane<Sphere2D>, Embedding<Sphere2D, Sphere1D> { 53 54 /** Pole or circle center. */ 55 private Vector3D pole; 56 57 /** First axis in the equator plane, origin of the phase angles. */ 58 private Vector3D x; 59 60 /** Second axis in the equator plane, in quadrature with respect to x. */ 61 private Vector3D y; 62 63 /** Tolerance below which close sub-arcs are merged together. */ 64 private final double tolerance; 65 66 /** Build a great circle from its pole. 67 * <p>The circle is oriented in the trigonometric direction around pole.</p> 68 * @param pole circle pole 69 * @param tolerance tolerance below which close sub-arcs are merged together 70 * @exception MathIllegalArgumentException if tolerance is smaller than {@link Sphere1D#SMALLEST_TOLERANCE} 71 */ 72 public Circle(final Vector3D pole, final double tolerance) 73 throws MathIllegalArgumentException { 74 Sphere2D.checkTolerance(tolerance); 75 reset(pole); 76 this.tolerance = tolerance; 77 } 78 79 /** Build a great circle from two non-aligned points. 80 * <p>The circle is oriented from first to second point using the path smaller than \( \pi \).</p> 81 * @param first first point contained in the great circle 82 * @param second second point contained in the great circle 83 * @param tolerance tolerance below which close sub-arcs are merged together 84 * @exception MathIllegalArgumentException if tolerance is smaller than {@link Sphere1D#SMALLEST_TOLERANCE} 85 */ 86 public Circle(final S2Point first, final S2Point second, final double tolerance) 87 throws MathIllegalArgumentException { 88 Sphere2D.checkTolerance(tolerance); 89 reset(first.getVector().crossProduct(second.getVector())); 90 this.tolerance = tolerance; 91 } 92 93 /** Build a circle from its internal components. 94 * <p>The circle is oriented in the trigonometric direction around center.</p> 95 * @param pole circle pole 96 * @param x first axis in the equator plane 97 * @param y second axis in the equator plane 98 * @param tolerance tolerance below which close sub-arcs are merged together 99 * @exception MathIllegalArgumentException if tolerance is smaller than {@link Sphere1D#SMALLEST_TOLERANCE} 100 */ 101 private Circle(final Vector3D pole, final Vector3D x, final Vector3D y, final double tolerance) 102 throws MathIllegalArgumentException { 103 Sphere2D.checkTolerance(tolerance); 104 this.pole = pole; 105 this.x = x; 106 this.y = y; 107 this.tolerance = tolerance; 108 } 109 110 /** Copy constructor. 111 * <p>The created instance is completely independent from the 112 * original instance, it is a deep copy.</p> 113 * @param circle circle to copy 114 */ 115 public Circle(final Circle circle) { 116 this(circle.pole, circle.x, circle.y, circle.tolerance); 117 } 118 119 /** {@inheritDoc} */ 120 @Override 121 public Circle copySelf() { 122 return new Circle(this); 123 } 124 125 /** Reset the instance as if built from a pole. 126 * <p>The circle is oriented in the trigonometric direction around pole.</p> 127 * @param newPole circle pole 128 */ 129 public void reset(final Vector3D newPole) { 130 this.pole = newPole.normalize(); 131 this.x = newPole.orthogonal(); 132 this.y = Vector3D.crossProduct(newPole, x).normalize(); 133 } 134 135 /** Revert the instance. 136 */ 137 public void revertSelf() { 138 // x remains the same 139 y = y.negate(); 140 pole = pole.negate(); 141 } 142 143 /** Get the reverse of the instance. 144 * <p>Get a circle with reversed orientation with respect to the 145 * instance. A new object is built, the instance is untouched.</p> 146 * @return a new circle, with orientation opposite to the instance orientation 147 */ 148 public Circle getReverse() { 149 return new Circle(pole.negate(), x, y.negate(), tolerance); 150 } 151 152 /** {@inheritDoc} */ 153 @Override 154 public Point<Sphere2D> project(Point<Sphere2D> point) { 155 return toSpace(toSubSpace(point)); 156 } 157 158 /** {@inheritDoc} */ 159 @Override 160 public double getTolerance() { 161 return tolerance; 162 } 163 164 /** {@inheritDoc} 165 * @see #getPhase(Vector3D) 166 */ 167 @Override 168 public S1Point toSubSpace(final Point<Sphere2D> point) { 169 return new S1Point(getPhase(((S2Point) point).getVector())); 170 } 171 172 /** Get the phase angle of a direction. 173 * <p> 174 * The direction may not belong to the circle as the 175 * phase is computed for the meridian plane between the circle 176 * pole and the direction. 177 * </p> 178 * @param direction direction for which phase is requested 179 * @return phase angle of the direction around the circle 180 * @see #toSubSpace(Point) 181 */ 182 public double getPhase(final Vector3D direction) { 183 return FastMath.PI + FastMath.atan2(-direction.dotProduct(y), -direction.dotProduct(x)); 184 } 185 186 /** {@inheritDoc} 187 * @see #getPointAt(double) 188 */ 189 @Override 190 public S2Point toSpace(final Point<Sphere1D> point) { 191 return new S2Point(getPointAt(((S1Point) point).getAlpha())); 192 } 193 194 /** Get a circle point from its phase around the circle. 195 * @param alpha phase around the circle 196 * @return circle point on the sphere 197 * @see #toSpace(Point) 198 * @see #getXAxis() 199 * @see #getYAxis() 200 */ 201 public Vector3D getPointAt(final double alpha) { 202 final SinCos sc = FastMath.sinCos(alpha); 203 return new Vector3D(sc.cos(), x, sc.sin(), y); 204 } 205 206 /** Get the X axis of the circle. 207 * <p> 208 * This method returns the same value as {@link #getPointAt(double) 209 * getPointAt(0.0)} but it does not do any computation and always 210 * return the same instance. 211 * </p> 212 * @return an arbitrary x axis on the circle 213 * @see #getPointAt(double) 214 * @see #getYAxis() 215 * @see #getPole() 216 */ 217 public Vector3D getXAxis() { 218 return x; 219 } 220 221 /** Get the Y axis of the circle. 222 * <p> 223 * This method returns the same value as {@link #getPointAt(double) 224 * getPointAt(0.5 * FastMath.PI)} but it does not do any computation and always 225 * return the same instance. 226 * </p> 227 * @return an arbitrary y axis point on the circle 228 * @see #getPointAt(double) 229 * @see #getXAxis() 230 * @see #getPole() 231 */ 232 public Vector3D getYAxis() { 233 return y; 234 } 235 236 /** Get the pole of the circle. 237 * <p> 238 * As the circle is a great circle, the pole does <em>not</em> 239 * belong to it. 240 * </p> 241 * @return pole of the circle 242 * @see #getXAxis() 243 * @see #getYAxis() 244 */ 245 public Vector3D getPole() { 246 return pole; 247 } 248 249 /** Get the arc of the instance that lies inside the other circle. 250 * @param other other circle 251 * @return arc of the instance that lies inside the other circle 252 */ 253 public Arc getInsideArc(final Circle other) { 254 final double alpha = getPhase(other.pole); 255 final double halfPi = 0.5 * FastMath.PI; 256 return new Arc(alpha - halfPi, alpha + halfPi, tolerance); 257 } 258 259 /** {@inheritDoc} */ 260 @Override 261 public SubCircle wholeHyperplane() { 262 return new SubCircle(this, new ArcsSet(tolerance)); 263 } 264 265 /** {@inheritDoc} */ 266 @Override 267 public SubCircle emptyHyperplane() { 268 return new SubCircle(this, new RegionFactory<Sphere1D>().getComplement(new ArcsSet(tolerance))); 269 } 270 271 /** Build a region covering the whole space. 272 * @return a region containing the instance (really a {@link 273 * SphericalPolygonsSet SphericalPolygonsSet} instance) 274 */ 275 @Override 276 public SphericalPolygonsSet wholeSpace() { 277 return new SphericalPolygonsSet(tolerance); 278 } 279 280 /** {@inheritDoc} 281 * @see #getOffset(Vector3D) 282 */ 283 @Override 284 public double getOffset(final Point<Sphere2D> point) { 285 return getOffset(((S2Point) point).getVector()); 286 } 287 288 /** Get the offset (oriented distance) of a direction. 289 * <p>The offset is defined as the angular distance between the 290 * circle center and the direction minus the circle radius. It 291 * is therefore 0 on the circle, positive for directions outside of 292 * the cone delimited by the circle, and negative inside the cone.</p> 293 * @param direction direction to check 294 * @return offset of the direction 295 * @see #getOffset(Point) 296 */ 297 public double getOffset(final Vector3D direction) { 298 return Vector3D.angle(pole, direction) - 0.5 * FastMath.PI; 299 } 300 301 /** {@inheritDoc} */ 302 @Override 303 public boolean sameOrientationAs(final Hyperplane<Sphere2D> other) { 304 final Circle otherC = (Circle) other; 305 return Vector3D.dotProduct(pole, otherC.pole) >= 0.0; 306 } 307 308 /** 309 * Get the arc on this circle between two defining points. Only the point's projection 310 * on the circle matters, which is computed using {@link #getPhase(Vector3D)}. 311 * 312 * @param a first point. 313 * @param b second point. 314 * @return an arc of the circle. 315 */ 316 public Arc getArc(final S2Point a, final S2Point b) { 317 final double phaseA = getPhase(a.getVector()); 318 double phaseB = getPhase(b.getVector()); 319 if (phaseB < phaseA) { 320 phaseB += 2 * FastMath.PI; 321 } 322 return new Arc(phaseA, phaseB, tolerance); 323 } 324 325 /** Get a {@link org.hipparchus.geometry.partitioning.Transform 326 * Transform} embedding a 3D rotation. 327 * @param rotation rotation to use 328 * @return a new transform that can be applied to either {@link 329 * Point Point}, {@link Circle Line} or {@link 330 * org.hipparchus.geometry.partitioning.SubHyperplane 331 * SubHyperplane} instances 332 */ 333 public static Transform<Sphere2D, Sphere1D> getTransform(final Rotation rotation) { 334 return new CircleTransform(rotation); 335 } 336 337 /** Class embedding a 3D rotation. */ 338 private static class CircleTransform implements Transform<Sphere2D, Sphere1D> { 339 340 /** Underlying rotation. */ 341 private final Rotation rotation; 342 343 /** Build a transform from a {@code Rotation}. 344 * @param rotation rotation to use 345 */ 346 CircleTransform(final Rotation rotation) { 347 this.rotation = rotation; 348 } 349 350 /** {@inheritDoc} */ 351 @Override 352 public S2Point apply(final Point<Sphere2D> point) { 353 return new S2Point(rotation.applyTo(((S2Point) point).getVector())); 354 } 355 356 /** {@inheritDoc} */ 357 @Override 358 public Circle apply(final Hyperplane<Sphere2D> hyperplane) { 359 final Circle circle = (Circle) hyperplane; 360 return new Circle(rotation.applyTo(circle.pole), 361 rotation.applyTo(circle.x), 362 rotation.applyTo(circle.y), 363 circle.tolerance); 364 } 365 366 /** {@inheritDoc} */ 367 @Override 368 public SubHyperplane<Sphere1D> apply(final SubHyperplane<Sphere1D> sub, 369 final Hyperplane<Sphere2D> original, 370 final Hyperplane<Sphere2D> transformed) { 371 // as the circle is rotated, the limit angles are rotated too 372 return sub; 373 } 374 375 } 376 377 }