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