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 }