1   /* Copyright 2002-2023 CS GROUP
2    * Licensed to CS GROUP (CS) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * CS 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.orekit.bodies;
18  
19  import java.io.Serializable;
20  
21  import org.hipparchus.geometry.euclidean.threed.Vector3D;
22  import org.hipparchus.geometry.euclidean.twod.Vector2D;
23  import org.hipparchus.util.FastMath;
24  import org.hipparchus.util.MathArrays;
25  import org.hipparchus.util.SinCos;
26  import org.orekit.frames.Frame;
27  import org.orekit.utils.TimeStampedPVCoordinates;
28  
29  /**
30   * Model of a 2D ellipse in 3D space.
31   * <p>
32   * These ellipses are mainly created as plane sections of general 3D ellipsoids,
33   * but can be used for other purposes.
34   * </p>
35   * <p>
36   * Instances of this class are guaranteed to be immutable.
37   * </p>
38   * @see Ellipsoid#getPlaneSection(Vector3D, Vector3D)
39   * @since 7.0
40   * @author Luc Maisonobe
41   */
42  public class Ellipse implements Serializable {
43  
44      /** Serializable UID. */
45      private static final long serialVersionUID = 20140925L;
46  
47      /** Convergence limit. */
48      private static final double ANGULAR_THRESHOLD = 1.0e-12;
49  
50      /** Center of the 2D ellipse. */
51      private final Vector3D center;
52  
53      /** Unit vector along the major axis. */
54      private final Vector3D u;
55  
56      /** Unit vector along the minor axis. */
57      private final Vector3D v;
58  
59      /** Semi major axis. */
60      private final double a;
61  
62      /** Semi minor axis. */
63      private final double b;
64  
65      /** Frame in which the ellipse is defined. */
66      private final Frame frame;
67  
68      /** Semi major axis radius power 2. */
69      private final double a2;
70  
71      /** Semi minor axis power 2. */
72      private final double b2;
73  
74      /** Eccentricity power 2. */
75      private final double e2;
76  
77      /** 1 minus flatness. */
78      private final double g;
79  
80      /** g * g. */
81      private final double g2;
82  
83      /** Evolute factor along major axis. */
84      private final double evoluteFactorX;
85  
86      /** Evolute factor along minor axis. */
87      private final double evoluteFactorY;
88  
89      /** Simple constructor.
90       * @param center center of the 2D ellipse
91       * @param u unit vector along the major axis
92       * @param v unit vector along the minor axis
93       * @param a semi major axis
94       * @param b semi minor axis
95       * @param frame frame in which the ellipse is defined
96       */
97      public Ellipse(final Vector3D center, final Vector3D u,
98                     final Vector3D v, final double a, final double b,
99                     final Frame frame) {
100         this.center = center;
101         this.u      = u;
102         this.v      = v;
103         this.a      = a;
104         this.b      = b;
105         this.frame  = frame;
106         this.a2     = a * a;
107         this.g      = b / a;
108         this.g2     = g * g;
109         this.e2     = 1 - g2;
110         this.b2     = b * b;
111         this.evoluteFactorX = (a2 - b2) / (a2 * a2);
112         this.evoluteFactorY = (b2 - a2) / (b2 * b2);
113     }
114 
115     /** Get the center of the 2D ellipse.
116      * @return center of the 2D ellipse
117      */
118     public Vector3D getCenter() {
119         return center;
120     }
121 
122     /** Get the unit vector along the major axis.
123      * @return unit vector along the major axis
124      */
125     public Vector3D getU() {
126         return u;
127     }
128 
129     /** Get the unit vector along the minor axis.
130      * @return unit vector along the minor axis
131      */
132     public Vector3D getV() {
133         return v;
134     }
135 
136     /** Get the semi major axis.
137      * @return semi major axis
138      */
139     public double getA() {
140         return a;
141     }
142 
143     /** Get the semi minor axis.
144      * @return semi minor axis
145      */
146     public double getB() {
147         return b;
148     }
149 
150     /** Get the defining frame.
151      * @return defining frame
152      */
153     public Frame getFrame() {
154         return frame;
155     }
156 
157     /** Get a point of the 2D ellipse.
158      * @param theta angular parameter on the ellipse (really the eccentric anomaly)
159      * @return ellipse point at theta, in underlying ellipsoid frame
160      */
161     public Vector3D pointAt(final double theta) {
162         final SinCos scTheta = FastMath.sinCos(theta);
163         return toSpace(new Vector2D(a * scTheta.cos(), b * scTheta.sin()));
164     }
165 
166     /** Create a point from its ellipse-relative coordinates.
167      * @param p point defined with respect to ellipse
168      * @return point defined with respect to 3D frame
169      * @see #toPlane(Vector3D)
170      */
171     public Vector3D toSpace(final Vector2D p) {
172         return new Vector3D(1, center, p.getX(), u, p.getY(), v);
173     }
174 
175     /** Project a point to the ellipse plane.
176      * @param p point defined with respect to 3D frame
177      * @return point defined with respect to ellipse
178      * @see #toSpace(Vector2D)
179      */
180     public Vector2D toPlane(final Vector3D p) {
181         final Vector3D delta = p.subtract(center);
182         return new Vector2D(Vector3D.dotProduct(delta, u), Vector3D.dotProduct(delta, v));
183     }
184 
185     /** Find the closest ellipse point.
186      * @param p point in the ellipse plane to project on the ellipse itself
187      * @return closest point belonging to 2D meridian ellipse
188      */
189     public Vector2D projectToEllipse(final Vector2D p) {
190 
191         final double x = FastMath.abs(p.getX());
192         final double y = p.getY();
193 
194         if (x <= ANGULAR_THRESHOLD * FastMath.abs(y)) {
195             // the point is almost on the minor axis, approximate the ellipse with
196             // the osculating circle whose center is at evolute cusp along minor axis
197             final double osculatingRadius = a2 / b;
198             final double evoluteCuspZ     = FastMath.copySign(a * e2 / g, -y);
199             final double deltaZ           = y - evoluteCuspZ;
200             final double ratio            = osculatingRadius / FastMath.hypot(deltaZ, x);
201             return new Vector2D(FastMath.copySign(ratio * x, p.getX()),
202                                 evoluteCuspZ + ratio * deltaZ);
203         }
204 
205         if (FastMath.abs(y) <= ANGULAR_THRESHOLD * x) {
206             // the point is almost on the major axis
207 
208             final double osculatingRadius = b2 / a;
209             final double evoluteCuspR     = a * e2;
210             final double deltaR           = x - evoluteCuspR;
211             if (deltaR >= 0) {
212                 // the point is outside of the ellipse evolute, approximate the ellipse
213                 // with the osculating circle whose center is at evolute cusp along major axis
214                 final double ratio = osculatingRadius / FastMath.hypot(y, deltaR);
215                 return new Vector2D(FastMath.copySign(evoluteCuspR + ratio * deltaR, p.getX()),
216                                     ratio * y);
217             }
218 
219             // the point is on the part of the major axis within ellipse evolute
220             // we can compute the closest ellipse point analytically
221             final double rEllipse = x / e2;
222             return new Vector2D(FastMath.copySign(rEllipse, p.getX()),
223                                 FastMath.copySign(g * FastMath.sqrt(a2 - rEllipse * rEllipse), y));
224 
225         } else {
226 
227             // initial point at evolute cusp along major axis
228             double omegaX = a * e2;
229             double omegaY = 0.0;
230 
231             double projectedX = x;
232             double projectedY = y;
233             double deltaX     = Double.POSITIVE_INFINITY;
234             double deltaY     = Double.POSITIVE_INFINITY;
235             int count = 0;
236             final double threshold = ANGULAR_THRESHOLD * ANGULAR_THRESHOLD * a2;
237             while ((deltaX * deltaX + deltaY * deltaY) > threshold && count++ < 100) { // this loop usually converges in 3 iterations
238 
239                 // find point at the intersection of ellipse and line going from query point to evolute point
240                 final double dx         = x - omegaX;
241                 final double dy         = y - omegaY;
242                 final double alpha      = b2 * dx * dx + a2 * dy * dy;
243                 final double betaPrime  = b2 * omegaX * dx + a2 * omegaY * dy;
244                 final double gamma      = b2 * omegaX * omegaX + a2 * omegaY * omegaY - a2 * b2;
245                 final double deltaPrime = MathArrays.linearCombination(betaPrime, betaPrime, -alpha, gamma);
246                 final double ratio      = (betaPrime <= 0) ?
247                                           (FastMath.sqrt(deltaPrime) - betaPrime) / alpha :
248                                           -gamma / (FastMath.sqrt(deltaPrime) + betaPrime);
249                 final double previousX  = projectedX;
250                 final double previousY  = projectedY;
251                 projectedX = omegaX + ratio * dx;
252                 projectedY = omegaY + ratio * dy;
253 
254                 // find new evolute point
255                 omegaX     = evoluteFactorX * projectedX * projectedX * projectedX;
256                 omegaY     = evoluteFactorY * projectedY * projectedY * projectedY;
257 
258                 // compute convergence parameters
259                 deltaX     = projectedX - previousX;
260                 deltaY     = projectedY - previousY;
261 
262             }
263             return new Vector2D(FastMath.copySign(projectedX, p.getX()), projectedY);
264         }
265     }
266 
267     /** Project position-velocity-acceleration on an ellipse.
268      * @param pv position-velocity-acceleration to project, in the reference frame
269      * @return projected position-velocity-acceleration
270      */
271     public TimeStampedPVCoordinates projectToEllipse(final TimeStampedPVCoordinates pv) {
272 
273         // find the closest point in the meridian plane
274         final Vector2D p2D = toPlane(pv.getPosition());
275         final Vector2D e2D = projectToEllipse(p2D);
276 
277         // tangent to the ellipse
278         final double fx = -a2 * e2D.getY();
279         final double fy =  b2 * e2D.getX();
280         final double f2 = fx * fx + fy * fy;
281         final double f  = FastMath.sqrt(f2);
282         final Vector2D tangent = new Vector2D(fx / f, fy / f);
283 
284         // normal to the ellipse (towards interior)
285         final Vector2D normal = new Vector2D(-tangent.getY(), tangent.getX());
286 
287         // center of curvature
288         final double x2     = e2D.getX() * e2D.getX();
289         final double y2     = e2D.getY() * e2D.getY();
290         final double eX     = evoluteFactorX * x2;
291         final double eY     = evoluteFactorY * y2;
292         final double omegaX = eX * e2D.getX();
293         final double omegaY = eY * e2D.getY();
294 
295         // velocity projection ratio
296         final double rho                = FastMath.hypot(e2D.getX() - omegaX, e2D.getY() - omegaY);
297         final double d                  = FastMath.hypot(p2D.getX() - omegaX, p2D.getY() - omegaY);
298         final double projectionRatio    = rho / d;
299 
300         // tangential velocity
301         final Vector2D pDot2D           = new Vector2D(Vector3D.dotProduct(pv.getVelocity(), u),
302                                                        Vector3D.dotProduct(pv.getVelocity(), v));
303         final double   pDotTangent      = pDot2D.dotProduct(tangent);
304         final double   pDotNormal       = pDot2D.dotProduct(normal);
305         final double   eDotTangent      = projectionRatio * pDotTangent;
306         final Vector2D eDot2D           = new Vector2D(eDotTangent, tangent);
307         final Vector2D tangentDot       = new Vector2D(a2 * b2 * (e2D.getX() * eDot2D.getY() - e2D.getY() * eDot2D.getX()) / f2,
308                                                        normal);
309 
310         // velocity of the center of curvature in the meridian plane
311         final double omegaXDot          = 3 * eX * eDotTangent * tangent.getX();
312         final double omegaYDot          = 3 * eY * eDotTangent * tangent.getY();
313 
314         // derivative of the projection ratio
315         final double voz                = omegaXDot * tangent.getY() - omegaYDot * tangent.getX();
316         final double vsz                = -pDotNormal;
317         final double projectionRatioDot = ((rho - d) * voz - rho * vsz) / (d * d);
318 
319         // acceleration
320         final Vector2D pDotDot2D        = new Vector2D(Vector3D.dotProduct(pv.getAcceleration(), u),
321                                                        Vector3D.dotProduct(pv.getAcceleration(), v));
322         final double   pDotDotTangent   = pDotDot2D.dotProduct(tangent);
323         final double   pDotTangentDot   = pDot2D.dotProduct(tangentDot);
324         final double   eDotDotTangent   = projectionRatio    * (pDotDotTangent + pDotTangentDot) +
325                                           projectionRatioDot * pDotTangent;
326         final Vector2D eDotDot2D        = new Vector2D(eDotDotTangent, tangent, eDotTangent, tangentDot);
327 
328         // back to 3D
329         final Vector3D e3D       = toSpace(e2D);
330         final Vector3D eDot3D    = new Vector3D(eDot2D.getX(),    u, eDot2D.getY(),    v);
331         final Vector3D eDotDot3D = new Vector3D(eDotDot2D.getX(), u, eDotDot2D.getY(), v);
332 
333         return new TimeStampedPVCoordinates(pv.getDate(), e3D, eDot3D, eDotDot3D);
334 
335     }
336 
337     /** Find the center of curvature (point on the evolute) at the nadir of a point.
338      * @param point point in the ellipse plane
339      * @return center of curvature of the ellipse directly at point nadir
340      * @since 7.1
341      */
342     public Vector2D getCenterOfCurvature(final Vector2D point) {
343         final Vector2D projected = projectToEllipse(point);
344         return new Vector2D(evoluteFactorX * projected.getX() * projected.getX() * projected.getX(),
345                             evoluteFactorY * projected.getY() * projected.getY() * projected.getY());
346     }
347 
348 }