View Javadoc
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 }