1   /* Copyright 2002-2016 CS Systèmes d'Information
2    * Licensed to CS Systèmes d'Information (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.orbits;
18  
19  import java.io.Serializable;
20  import java.util.Collection;
21  
22  import org.apache.commons.math3.analysis.interpolation.HermiteInterpolator;
23  import org.apache.commons.math3.exception.ConvergenceException;
24  import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;
25  import org.apache.commons.math3.util.FastMath;
26  import org.apache.commons.math3.util.MathUtils;
27  import org.orekit.errors.OrekitIllegalArgumentException;
28  import org.orekit.errors.OrekitInternalError;
29  import org.orekit.errors.OrekitMessages;
30  import org.orekit.frames.Frame;
31  import org.orekit.time.AbsoluteDate;
32  import org.orekit.utils.PVCoordinates;
33  import org.orekit.utils.TimeStampedPVCoordinates;
34  
35  
36  /**
37   * This class handles traditional keplerian orbital parameters.
38  
39   * <p>
40   * The parameters used internally are the classical keplerian elements:
41   *   <pre>
42   *     a
43   *     e
44   *     i
45   *     ω
46   *     Ω
47   *     v
48   *   </pre>
49   * where ω stands for the Perigee Argument, Ω stands for the
50   * Right Ascension of the Ascending Node and v stands for the true anomaly.
51   * </p>
52   * <p>
53   * This class supports hyperbolic orbits, using the convention that semi major
54   * axis is negative for such orbits (and of course eccentricity is greater than 1).
55   * </p>
56   * <p>
57   * When orbit is either equatorial or circular, some keplerian elements
58   * (more precisely ω and Ω) become ambiguous so this class should not
59   * be used for such orbits. For this reason, {@link EquinoctialOrbit equinoctial
60   * orbits} is the recommended way to represent orbits.
61   * </p>
62  
63   * <p>
64   * The instance <code>KeplerianOrbit</code> is guaranteed to be immutable.
65   * </p>
66   * @see     Orbit
67   * @see    CircularOrbit
68   * @see    CartesianOrbit
69   * @see    EquinoctialOrbit
70   * @author Luc Maisonobe
71   * @author Guylaine Prat
72   * @author Fabien Maussion
73   * @author V&eacute;ronique Pommier-Maurussane
74   */
75  public class KeplerianOrbit extends Orbit {
76  
77      /** Serializable UID. */
78      private static final long serialVersionUID = 20141228L;
79  
80      /** First coefficient to compute Kepler equation solver starter. */
81      private static final double A;
82  
83      /** Second coefficient to compute Kepler equation solver starter. */
84      private static final double B;
85  
86      static {
87          final double k1 = 3 * FastMath.PI + 2;
88          final double k2 = FastMath.PI - 1;
89          final double k3 = 6 * FastMath.PI - 1;
90          A  = 3 * k2 * k2 / k1;
91          B  = k3 * k3 / (6 * k1);
92      }
93  
94      /** Semi-major axis (m). */
95      private final double a;
96  
97      /** Eccentricity. */
98      private final double e;
99  
100     /** Inclination (rad). */
101     private final double i;
102 
103     /** Perigee Argument (rad). */
104     private final double pa;
105 
106     /** Right Ascension of Ascending Node (rad). */
107     private final double raan;
108 
109     /** True anomaly (rad). */
110     private final double v;
111 
112     /** Creates a new instance.
113      * @param a  semi-major axis (m), negative for hyperbolic orbits
114      * @param e eccentricity
115      * @param i inclination (rad)
116      * @param pa perigee argument (ω, rad)
117      * @param raan right ascension of ascending node (Ω, rad)
118      * @param anomaly mean, eccentric or true anomaly (rad)
119      * @param type type of anomaly
120      * @param frame the frame in which the parameters are defined
121      * (<em>must</em> be a {@link Frame#isPseudoInertial pseudo-inertial frame})
122      * @param date date of the orbital parameters
123      * @param mu central attraction coefficient (m³/s²)
124      * @exception IllegalArgumentException if frame is not a {@link
125      * Frame#isPseudoInertial pseudo-inertial frame} or a and e don't match for hyperbolic orbits,
126      * or v is out of range for hyperbolic orbits
127      */
128     public KeplerianOrbit(final double a, final double e, final double i,
129                           final double pa, final double raan,
130                           final double anomaly, final PositionAngle type,
131                           final Frame frame, final AbsoluteDate date, final double mu)
132         throws IllegalArgumentException {
133         super(frame, date, mu);
134 
135         if (a * (1 - e) < 0) {
136             throw new OrekitIllegalArgumentException(OrekitMessages.ORBIT_A_E_MISMATCH_WITH_CONIC_TYPE, a, e);
137         }
138 
139         this.a    =    a;
140         this.e    =    e;
141         this.i    =    i;
142         this.pa   =   pa;
143         this.raan = raan;
144 
145         final double tmpV;
146         switch (type) {
147             case MEAN :
148                 tmpV = (a < 0) ? hyperbolicEccentricToTrue(meanToHyperbolicEccentric(anomaly, e)) :
149                     ellipticEccentricToTrue(meanToEllipticEccentric(anomaly));
150                 break;
151             case ECCENTRIC :
152                 tmpV = (a < 0) ? hyperbolicEccentricToTrue(anomaly) :
153                     ellipticEccentricToTrue(anomaly);
154                 break;
155             case TRUE :
156                 tmpV = anomaly;
157                 break;
158             default : // this should never happen
159                 throw new OrekitInternalError(null);
160         }
161 
162         // check true anomaly range
163         if (1 + e * FastMath.cos(tmpV) <= 0) {
164             final double vMax = FastMath.acos(-1 / e);
165             throw new OrekitIllegalArgumentException(OrekitMessages.ORBIT_ANOMALY_OUT_OF_HYPERBOLIC_RANGE,
166                                                                  tmpV, e, -vMax, vMax);
167         }
168         this.v = tmpV;
169 
170     }
171 
172     /** Constructor from cartesian parameters.
173      *
174      * <p> The acceleration provided in {@code pvCoordinates} is accessible using
175      * {@link #getPVCoordinates()} and {@link #getPVCoordinates(Frame)}. All other methods
176      * use {@code mu} and the position to compute the acceleration, including
177      * {@link #shiftedBy(double)} and {@link #getPVCoordinates(AbsoluteDate, Frame)}.
178      *
179      * @param pvCoordinates the PVCoordinates of the satellite
180      * @param frame the frame in which are defined the {@link PVCoordinates}
181      * (<em>must</em> be a {@link Frame#isPseudoInertial pseudo-inertial frame})
182      * @param mu central attraction coefficient (m³/s²)
183      * @exception IllegalArgumentException if frame is not a {@link
184      * Frame#isPseudoInertial pseudo-inertial frame}
185      */
186     public KeplerianOrbit(final TimeStampedPVCoordinates pvCoordinates,
187                           final Frame frame, final double mu)
188         throws IllegalArgumentException {
189         super(pvCoordinates, frame, mu);
190 
191         // compute inclination
192         final Vector3D momentum = pvCoordinates.getMomentum();
193         final double m2 = momentum.getNormSq();
194         i = Vector3D.angle(momentum, Vector3D.PLUS_K);
195 
196         // compute right ascension of ascending node
197         raan = Vector3D.crossProduct(Vector3D.PLUS_K, momentum).getAlpha();
198 
199         // preliminary computations for parameters depending on orbit shape (elliptic or hyperbolic)
200         final Vector3D pvP     = pvCoordinates.getPosition();
201         final Vector3D pvV     = pvCoordinates.getVelocity();
202         final double   r       = pvP.getNorm();
203         final double   V2      = pvV.getNormSq();
204         final double   rV2OnMu = r * V2 / mu;
205 
206         // compute semi-major axis (will be negative for hyperbolic orbits)
207         a = r / (2 - rV2OnMu);
208         final double muA = mu * a;
209 
210         // compute true anomaly
211         if (a > 0) {
212             // elliptic or circular orbit
213             final double eSE = Vector3D.dotProduct(pvP, pvV) / FastMath.sqrt(muA);
214             final double eCE = rV2OnMu - 1;
215             e = FastMath.sqrt(eSE * eSE + eCE * eCE);
216             v = ellipticEccentricToTrue(FastMath.atan2(eSE, eCE));
217         } else {
218             // hyperbolic orbit
219             final double eSH = Vector3D.dotProduct(pvP, pvV) / FastMath.sqrt(-muA);
220             final double eCH = rV2OnMu - 1;
221             e = FastMath.sqrt(1 - m2 / muA);
222             v = hyperbolicEccentricToTrue(FastMath.log((eCH + eSH) / (eCH - eSH)) / 2);
223         }
224 
225         // compute perigee argument
226         final Vector3D node = new Vector3D(raan, 0.0);
227         final double px = Vector3D.dotProduct(pvP, node);
228         final double py = Vector3D.dotProduct(pvP, Vector3D.crossProduct(momentum, node)) / FastMath.sqrt(m2);
229         pa = FastMath.atan2(py, px) - v;
230 
231     }
232 
233     /** Constructor from cartesian parameters.
234      *
235      * <p> The acceleration provided in {@code pvCoordinates} is accessible using
236      * {@link #getPVCoordinates()} and {@link #getPVCoordinates(Frame)}. All other methods
237      * use {@code mu} and the position to compute the acceleration, including
238      * {@link #shiftedBy(double)} and {@link #getPVCoordinates(AbsoluteDate, Frame)}.
239      *
240      * @param pvCoordinates the PVCoordinates of the satellite
241      * @param frame the frame in which are defined the {@link PVCoordinates}
242      * (<em>must</em> be a {@link Frame#isPseudoInertial pseudo-inertial frame})
243      * @param date date of the orbital parameters
244      * @param mu central attraction coefficient (m³/s²)
245      * @exception IllegalArgumentException if frame is not a {@link
246      * Frame#isPseudoInertial pseudo-inertial frame}
247      */
248     public KeplerianOrbit(final PVCoordinates pvCoordinates,
249                           final Frame frame, final AbsoluteDate date, final double mu)
250         throws IllegalArgumentException {
251         this(new TimeStampedPVCoordinates(date, pvCoordinates), frame, mu);
252     }
253 
254     /** Constructor from any kind of orbital parameters.
255      * @param op orbital parameters to copy
256      */
257     public KeplerianOrbit(final Orbit op) {
258         this(op.getPVCoordinates(), op.getFrame(), op.getDate(), op.getMu());
259     }
260 
261     /** {@inheritDoc} */
262     public OrbitType getType() {
263         return OrbitType.KEPLERIAN;
264     }
265 
266     /** {@inheritDoc} */
267     public double getA() {
268         return a;
269     }
270 
271     /** {@inheritDoc} */
272     public double getE() {
273         return e;
274     }
275 
276     /** {@inheritDoc} */
277     public double getI() {
278         return i;
279     }
280 
281     /** Get the perigee argument.
282      * @return perigee argument (rad)
283      */
284     public double getPerigeeArgument() {
285         return pa;
286     }
287 
288     /** Get the right ascension of the ascending node.
289      * @return right ascension of the ascending node (rad)
290      */
291     public double getRightAscensionOfAscendingNode() {
292         return raan;
293     }
294 
295     /** Get the anomaly.
296      * @param type type of the angle
297      * @return anomaly (rad)
298      */
299     public double getAnomaly(final PositionAngle type) {
300         return (type == PositionAngle.MEAN) ? getMeanAnomaly() :
301                                               ((type == PositionAngle.ECCENTRIC) ? getEccentricAnomaly() :
302                                                                                    getTrueAnomaly());
303     }
304 
305     /** Get the true anomaly.
306      * @return true anomaly (rad)
307      */
308     public double getTrueAnomaly() {
309         return v;
310     }
311 
312     /** Get the eccentric anomaly.
313      * @return eccentric anomaly (rad)
314      */
315     public double getEccentricAnomaly() {
316         if (a < 0) {
317             // hyperbolic case
318             final double sinhH = FastMath.sqrt(e * e - 1) * FastMath.sin(v) /
319                                  (1 + e * FastMath.cos(v));
320             return FastMath.asinh(sinhH);
321         }
322 
323         // elliptic case
324         final double beta = e / (1 + FastMath.sqrt((1 - e) * (1 + e)));
325         return v - 2 * FastMath.atan(beta * FastMath.sin(v) / (1 + beta * FastMath.cos(v)));
326 
327     }
328 
329     /** Computes the true anomaly from the elliptic eccentric anomaly.
330      * @param E eccentric anomaly (rad)
331      * @return v the true anomaly
332      */
333     private double ellipticEccentricToTrue(final double E) {
334         final double beta = e / (1 + FastMath.sqrt((1 - e) * (1 + e)));
335         return E + 2 * FastMath.atan(beta * FastMath.sin(E) / (1 - beta * FastMath.cos(E)));
336     }
337 
338     /** Computes the true anomaly from the hyperbolic eccentric anomaly.
339      * @param H hyperbolic eccentric anomaly (rad)
340      * @return v the true anomaly
341      */
342     private double hyperbolicEccentricToTrue(final double H) {
343         return 2 * FastMath.atan(FastMath.sqrt((e + 1) / (e - 1)) * FastMath.tanh(H / 2));
344     }
345 
346     /** Get the mean anomaly.
347      * @return mean anomaly (rad)
348      */
349     public double getMeanAnomaly() {
350 
351         if (a < 0) {
352             // hyperbolic case
353             final double H = getEccentricAnomaly();
354             return e * FastMath.sinh(H) - H;
355         }
356 
357         // elliptic case
358         final double E = getEccentricAnomaly();
359         return E - e * FastMath.sin(E);
360 
361     }
362 
363     /** Computes the elliptic eccentric anomaly from the mean anomaly.
364      * <p>
365      * The algorithm used here for solving Kepler equation has been published
366      * in: "Procedures for  solving Kepler's Equation", A. W. Odell and
367      * R. H. Gooding, Celestial Mechanics 38 (1986) 307-334
368      * </p>
369      * @param M mean anomaly (rad)
370      * @return v the true anomaly
371      */
372     private double meanToEllipticEccentric(final double M) {
373 
374         // reduce M to [-PI PI) interval
375         final double reducedM = MathUtils.normalizeAngle(M, 0.0);
376 
377         // compute start value according to A. W. Odell and R. H. Gooding S12 starter
378         double E;
379         if (FastMath.abs(reducedM) < 1.0 / 6.0) {
380             E = reducedM + e * (FastMath.cbrt(6 * reducedM) - reducedM);
381         } else {
382             if (reducedM < 0) {
383                 final double w = FastMath.PI + reducedM;
384                 E = reducedM + e * (A * w / (B - w) - FastMath.PI - reducedM);
385             } else {
386                 final double w = FastMath.PI - reducedM;
387                 E = reducedM + e * (FastMath.PI - A * w / (B - w) - reducedM);
388             }
389         }
390 
391         final double e1 = 1 - e;
392         final boolean noCancellationRisk = (e1 + E * E / 6) >= 0.1;
393 
394         // perform two iterations, each consisting of one Halley step and one Newton-Raphson step
395         for (int j = 0; j < 2; ++j) {
396             double f;
397             double fd;
398             final double fdd  = e * FastMath.sin(E);
399             final double fddd = e * FastMath.cos(E);
400             if (noCancellationRisk) {
401                 f  = (E - fdd) - reducedM;
402                 fd = 1 - fddd;
403             } else {
404                 f  = eMeSinE(E) - reducedM;
405                 final double s = FastMath.sin(0.5 * E);
406                 fd = e1 + 2 * e * s * s;
407             }
408             final double dee = f * fd / (0.5 * f * fdd - fd * fd);
409 
410             // update eccentric anomaly, using expressions that limit underflow problems
411             final double w = fd + 0.5 * dee * (fdd + dee * fddd / 3);
412             fd += dee * (fdd + 0.5 * dee * fddd);
413             E  -= (f - dee * (fd - w)) / fd;
414 
415         }
416 
417         // expand the result back to original range
418         E += M - reducedM;
419 
420         return E;
421 
422     }
423 
424     /** Accurate computation of E - e sin(E).
425      * <p>
426      * This method is used when E is close to 0 and e close to 1,
427      * i.e. near the perigee of almost parabolic orbits
428      * </p>
429      * @param E eccentric anomaly
430      * @return E - e sin(E)
431      */
432     private double eMeSinE(final double E) {
433         double x = (1 - e) * FastMath.sin(E);
434         final double mE2 = -E * E;
435         double term = E;
436         double d    = 0;
437         // the inequality test below IS intentional and should NOT be replaced by a check with a small tolerance
438         for (double x0 = Double.NaN; x != x0;) {
439             d += 2;
440             term *= mE2 / (d * (d + 1));
441             x0 = x;
442             x = x - term;
443         }
444         return x;
445     }
446 
447     /** Computes the hyperbolic eccentric anomaly from the mean anomaly.
448      * <p>
449      * The algorithm used here for solving hyperbolic Kepler equation is
450      * Danby's iterative method (3rd order) with Vallado's initial guess.
451      * </p>
452      * @param M mean anomaly (rad)
453      * @param ecc eccentricity
454      * @return H the hyperbolic eccentric anomaly
455      */
456     private double meanToHyperbolicEccentric(final double M, final double ecc) {
457 
458         // Resolution of hyperbolic Kepler equation for keplerian parameters
459 
460         // Initial guess
461         double H;
462         if (ecc < 1.6) {
463             if ((-FastMath.PI < M && M < 0.) || M > FastMath.PI) {
464                 H = M - ecc;
465             } else {
466                 H = M + ecc;
467             }
468         } else {
469             if (ecc < 3.6 && FastMath.abs(M) > FastMath.PI) {
470                 H = M - FastMath.copySign(ecc, M);
471             } else {
472                 H = M / (ecc - 1.);
473             }
474         }
475 
476         // Iterative computation
477         int iter = 0;
478         do {
479             final double f3  = ecc * FastMath.cosh(H);
480             final double f2  = ecc * FastMath.sinh(H);
481             final double f1  = f3 - 1.;
482             final double f0  = f2 - H - M;
483             final double f12 = 2. * f1;
484             final double d   = f0 / f12;
485             final double fdf = f1 - d * f2;
486             final double ds  = f0 / fdf;
487 
488             final double shift = f0 / (fdf + ds * ds * f3 / 6.);
489 
490             H -= shift;
491 
492             if (FastMath.abs(shift) <= 1.0e-12) {
493                 return H;
494             }
495 
496         } while (++iter < 50);
497 
498         throw new ConvergenceException(OrekitMessages.UNABLE_TO_COMPUTE_HYPERBOLIC_ECCENTRIC_ANOMALY,
499                                        iter);
500     }
501 
502     /** {@inheritDoc} */
503     public double getEquinoctialEx() {
504         return  e * FastMath.cos(pa + raan);
505     }
506 
507     /** {@inheritDoc} */
508     public double getEquinoctialEy() {
509         return  e * FastMath.sin(pa + raan);
510     }
511 
512     /** {@inheritDoc} */
513     public double getHx() {
514         // Check for equatorial retrograde orbit
515         if (FastMath.abs(i - FastMath.PI) < 1.0e-10) {
516             return Double.NaN;
517         }
518         return  FastMath.cos(raan) * FastMath.tan(i / 2);
519     }
520 
521     /** {@inheritDoc} */
522     public double getHy() {
523         // Check for equatorial retrograde orbit
524         if (FastMath.abs(i - FastMath.PI) < 1.0e-10) {
525             return Double.NaN;
526         }
527         return  FastMath.sin(raan) * FastMath.tan(i / 2);
528     }
529 
530     /** {@inheritDoc} */
531     public double getLv() {
532         return pa + raan + v;
533     }
534 
535     /** {@inheritDoc} */
536     public double getLE() {
537         return pa + raan + getEccentricAnomaly();
538     }
539 
540     /** {@inheritDoc} */
541     public double getLM() {
542         return pa + raan + getMeanAnomaly();
543     }
544 
545     /** {@inheritDoc} */
546     protected TimeStampedPVCoordinates initPVCoordinates() {
547 
548         // preliminary variables
549         final double cosRaan = FastMath.cos(raan);
550         final double sinRaan = FastMath.sin(raan);
551         final double cosPa   = FastMath.cos(pa);
552         final double sinPa   = FastMath.sin(pa);
553         final double cosI    = FastMath.cos(i);
554         final double sinI    = FastMath.sin(i);
555 
556         final double crcp    = cosRaan * cosPa;
557         final double crsp    = cosRaan * sinPa;
558         final double srcp    = sinRaan * cosPa;
559         final double srsp    = sinRaan * sinPa;
560 
561         // reference axes defining the orbital plane
562         final Vector3D p = new Vector3D( crcp - cosI * srsp,  srcp + cosI * crsp, sinI * sinPa);
563         final Vector3D q = new Vector3D(-crsp - cosI * srcp, -srsp + cosI * crcp, sinI * cosPa);
564 
565         return (a > 0) ? initPVCoordinatesElliptical(p, q) : initPVCoordinatesHyperbolic(p, q);
566 
567     }
568 
569     /** Initialize the position/velocity coordinates, elliptic case.
570      * @param p unit vector in the orbital plane pointing towards perigee
571      * @param q unit vector in the orbital plane in quadrature with p
572      * @return computed position/velocity coordinates
573      */
574     private TimeStampedPVCoordinates initPVCoordinatesElliptical(final Vector3D p, final Vector3D q) {
575 
576         // elliptic eccentric anomaly
577         final double uME2   = (1 - e) * (1 + e);
578         final double s1Me2  = FastMath.sqrt(uME2);
579         final double E      = getEccentricAnomaly();
580         final double cosE   = FastMath.cos(E);
581         final double sinE   = FastMath.sin(E);
582 
583         // coordinates of position and velocity in the orbital plane
584         final double x      = a * (cosE - e);
585         final double y      = a * sinE * s1Me2;
586         final double factor = FastMath.sqrt(getMu() / a) / (1 - e * cosE);
587         final double xDot   = -sinE * factor;
588         final double yDot   =  cosE * s1Me2 * factor;
589 
590 
591         final Vector3D position = new Vector3D(x, p, y, q);
592         final double r2 = x * x + y * y;
593         final Vector3D velocity = new Vector3D(xDot, p, yDot, q);
594         final Vector3D acceleration = new Vector3D(-getMu() / (r2 * FastMath.sqrt(r2)), position);
595         return new TimeStampedPVCoordinates(getDate(), position, velocity, acceleration);
596 
597     }
598 
599     /** Initialize the position/velocity coordinates, hyperbolic case.
600      * @param p unit vector in the orbital plane pointing towards perigee
601      * @param q unit vector in the orbital plane in quadrature with p
602      * @return computed position/velocity coordinates
603      */
604     private TimeStampedPVCoordinates initPVCoordinatesHyperbolic(final Vector3D p, final Vector3D q) {
605 
606         // compute position and velocity factors
607         final double sinV      = FastMath.sin(v);
608         final double cosV      = FastMath.cos(v);
609         final double f         = a * (1 - e * e);
610         final double posFactor = f / (1 + e * cosV);
611         final double velFactor = FastMath.sqrt(getMu() / f);
612 
613         final Vector3D position     = new Vector3D( posFactor * cosV, p, posFactor * sinV, q);
614         final Vector3D velocity     = new Vector3D(-velFactor * sinV, p, velFactor * (e + cosV), q);
615         final Vector3D acceleration = new Vector3D(-getMu() / (posFactor * posFactor * posFactor), position);
616         return new TimeStampedPVCoordinates(getDate(), position, velocity, acceleration);
617 
618     }
619 
620     /** {@inheritDoc} */
621     public KeplerianOrbit shiftedBy(final double dt) {
622         return new KeplerianOrbit(a, e, i, pa, raan,
623                                   getMeanAnomaly() + getKeplerianMeanMotion() * dt,
624                                   PositionAngle.MEAN, getFrame(), getDate().shiftedBy(dt), getMu());
625     }
626 
627     /** {@inheritDoc}
628      * <p>
629      * The interpolated instance is created by polynomial Hermite interpolation
630      * on Keplerian elements, without derivatives (which means the interpolation
631      * falls back to Lagrange interpolation only).
632      * </p>
633      * <p>
634      * As this implementation of interpolation is polynomial, it should be used only
635      * with small samples (about 10-20 points) in order to avoid <a
636      * href="http://en.wikipedia.org/wiki/Runge%27s_phenomenon">Runge's phenomenon</a>
637      * and numerical problems (including NaN appearing).
638      * </p>
639      * <p>
640      * If orbit interpolation on large samples is needed, using the {@link
641      * org.orekit.propagation.analytical.Ephemeris} class is a better way than using this
642      * low-level interpolation. The Ephemeris class automatically handles selection of
643      * a neighboring sub-sample with a predefined number of point from a large global sample
644      * in a thread-safe way.
645      * </p>
646      */
647     public KeplerianOrbit interpolate(final AbsoluteDate date, final Collection<Orbit> sample) {
648 
649         // set up an interpolator
650         final HermiteInterpolator interpolator = new HermiteInterpolator();
651 
652         // add sample points
653         AbsoluteDate previousDate = null;
654         double previousPA   = Double.NaN;
655         double previousRAAN = Double.NaN;
656         double previousM    = Double.NaN;
657         for (final Orbit orbit : sample) {
658             final KeplerianOrbit kep = (KeplerianOrbit) OrbitType.KEPLERIAN.convertType(orbit);
659             final double continuousPA;
660             final double continuousRAAN;
661             final double continuousM;
662             if (previousDate == null) {
663                 continuousPA   = kep.getPerigeeArgument();
664                 continuousRAAN = kep.getRightAscensionOfAscendingNode();
665                 continuousM    = kep.getMeanAnomaly();
666             } else {
667                 final double dt      = kep.getDate().durationFrom(previousDate);
668                 final double keplerM = previousM + kep.getKeplerianMeanMotion() * dt;
669                 continuousPA   = MathUtils.normalizeAngle(kep.getPerigeeArgument(), previousPA);
670                 continuousRAAN = MathUtils.normalizeAngle(kep.getRightAscensionOfAscendingNode(), previousRAAN);
671                 continuousM    = MathUtils.normalizeAngle(kep.getMeanAnomaly(), keplerM);
672             }
673             previousDate = kep.getDate();
674             previousPA   = continuousPA;
675             previousRAAN = continuousRAAN;
676             previousM    = continuousM;
677             interpolator.addSamplePoint(kep.getDate().durationFrom(date),
678                                         new double[] {
679                                             kep.getA(),
680                                             kep.getE(),
681                                             kep.getI(),
682                                             continuousPA,
683                                             continuousRAAN,
684                                             continuousM
685                                         });
686         }
687 
688         // interpolate
689         final double[] interpolated = interpolator.value(0);
690 
691         // build a new interpolated instance
692         return new KeplerianOrbit(interpolated[0], interpolated[1], interpolated[2],
693                                   interpolated[3], interpolated[4], interpolated[5],
694                                   PositionAngle.MEAN, getFrame(), date, getMu());
695 
696     }
697 
698     /** {@inheritDoc} */
699     protected double[][] computeJacobianMeanWrtCartesian() {
700         if (a > 0) {
701             return computeJacobianMeanWrtCartesianElliptical();
702         } else {
703             return computeJacobianMeanWrtCartesianHyperbolic();
704         }
705     }
706 
707     /** Compute the Jacobian of the orbital parameters with respect to the cartesian parameters.
708      * <p>
709      * Element {@code jacobian[i][j]} is the derivative of parameter i of the orbit with
710      * respect to cartesian coordinate j (x for j=0, y for j=1, z for j=2, xDot for j=3,
711      * yDot for j=4, zDot for j=5).
712      * </p>
713      * @return 6x6 Jacobian matrix
714      */
715     private double[][] computeJacobianMeanWrtCartesianElliptical() {
716 
717         final double[][] jacobian = new double[6][6];
718 
719         // compute various intermediate parameters
720         final PVCoordinates pvc = getPVCoordinates();
721         final Vector3D position = pvc.getPosition();
722         final Vector3D velocity = pvc.getVelocity();
723         final Vector3D momentum = pvc.getMomentum();
724         final double v2         = velocity.getNormSq();
725         final double r2         = position.getNormSq();
726         final double r          = FastMath.sqrt(r2);
727         final double r3         = r * r2;
728 
729         final double px         = position.getX();
730         final double py         = position.getY();
731         final double pz         = position.getZ();
732         final double vx         = velocity.getX();
733         final double vy         = velocity.getY();
734         final double vz         = velocity.getZ();
735         final double mx         = momentum.getX();
736         final double my         = momentum.getY();
737         final double mz         = momentum.getZ();
738 
739         final double mu         = getMu();
740         final double sqrtMuA    = FastMath.sqrt(a * mu);
741         final double sqrtAoMu   = FastMath.sqrt(a / mu);
742         final double a2         = a * a;
743         final double twoA       = 2 * a;
744         final double rOnA       = r / a;
745 
746         final double oMe2       = 1 - e * e;
747         final double epsilon    = FastMath.sqrt(oMe2);
748         final double sqrtRec    = 1 / epsilon;
749 
750         final double cosI       = FastMath.cos(i);
751         final double sinI       = FastMath.sin(i);
752         final double cosPA      = FastMath.cos(pa);
753         final double sinPA      = FastMath.sin(pa);
754 
755         final double pv         = Vector3D.dotProduct(position, velocity);
756         final double cosE       = (a - r) / (a * e);
757         final double sinE       = pv / (e * sqrtMuA);
758 
759         // da
760         final Vector3D vectorAR = new Vector3D(2 * a2 / r3, position);
761         final Vector3D vectorARDot = velocity.scalarMultiply(2 * a2 / mu);
762         fillHalfRow(1, vectorAR,    jacobian[0], 0);
763         fillHalfRow(1, vectorARDot, jacobian[0], 3);
764 
765         // de
766         final double factorER3 = pv / twoA;
767         final Vector3D vectorER   = new Vector3D(cosE * v2 / (r * mu), position,
768                                                  sinE / sqrtMuA, velocity,
769                                                  -factorER3 * sinE / sqrtMuA, vectorAR);
770         final Vector3D vectorERDot = new Vector3D(sinE / sqrtMuA, position,
771                                                   cosE * 2 * r / mu, velocity,
772                                                   -factorER3 * sinE / sqrtMuA, vectorARDot);
773         fillHalfRow(1, vectorER,    jacobian[1], 0);
774         fillHalfRow(1, vectorERDot, jacobian[1], 3);
775 
776         // dE / dr (Eccentric anomaly)
777         final double coefE = cosE / (e * sqrtMuA);
778         final Vector3D  vectorEAnR =
779             new Vector3D(-sinE * v2 / (e * r * mu), position, coefE, velocity,
780                          -factorER3 * coefE, vectorAR);
781 
782         // dE / drDot
783         final Vector3D  vectorEAnRDot =
784             new Vector3D(-sinE * 2 * r / (e * mu), velocity, coefE, position,
785                          -factorER3 * coefE, vectorARDot);
786 
787         // precomputing some more factors
788         final double s1 = -sinE * pz / r - cosE * vz * sqrtAoMu;
789         final double s2 = -cosE * pz / r3;
790         final double s3 = -sinE * vz / (2 * sqrtMuA);
791         final double t1 = sqrtRec * (cosE * pz / r - sinE * vz * sqrtAoMu);
792         final double t2 = sqrtRec * (-sinE * pz / r3);
793         final double t3 = sqrtRec * (cosE - e) * vz / (2 * sqrtMuA);
794         final double t4 = sqrtRec * (e * sinI * cosPA * sqrtRec - vz * sqrtAoMu);
795         final Vector3D s = new Vector3D(cosE / r, Vector3D.PLUS_K,
796                                         s1,       vectorEAnR,
797                                         s2,       position,
798                                         s3,       vectorAR);
799         final Vector3D sDot = new Vector3D(-sinE * sqrtAoMu, Vector3D.PLUS_K,
800                                            s1,               vectorEAnRDot,
801                                            s3,               vectorARDot);
802         final Vector3D t =
803             new Vector3D(sqrtRec * sinE / r, Vector3D.PLUS_K).add(new Vector3D(t1, vectorEAnR,
804                                                                                t2, position,
805                                                                                t3, vectorAR,
806                                                                                t4, vectorER));
807         final Vector3D tDot = new Vector3D(sqrtRec * (cosE - e) * sqrtAoMu, Vector3D.PLUS_K,
808                                            t1,                              vectorEAnRDot,
809                                            t3,                              vectorARDot,
810                                            t4,                              vectorERDot);
811 
812         // di
813         final double factorI1 = -sinI * sqrtRec / sqrtMuA;
814         final double i1 =  factorI1;
815         final double i2 = -factorI1 * mz / twoA;
816         final double i3 =  factorI1 * mz * e / oMe2;
817         final double i4 = cosI * sinPA;
818         final double i5 = cosI * cosPA;
819         fillHalfRow(i1, new Vector3D(vy, -vx, 0), i2, vectorAR, i3, vectorER, i4, s, i5, t,
820                     jacobian[2], 0);
821         fillHalfRow(i1, new Vector3D(-py, px, 0), i2, vectorARDot, i3, vectorERDot, i4, sDot, i5, tDot,
822                     jacobian[2], 3);
823 
824         // dpa
825         fillHalfRow(cosPA / sinI, s,    -sinPA / sinI, t,    jacobian[3], 0);
826         fillHalfRow(cosPA / sinI, sDot, -sinPA / sinI, tDot, jacobian[3], 3);
827 
828         // dRaan
829         final double factorRaanR = 1 / (mu * a * oMe2 * sinI * sinI);
830         fillHalfRow(-factorRaanR * my, new Vector3D(  0, vz, -vy),
831                      factorRaanR * mx, new Vector3D(-vz,  0,  vx),
832                      jacobian[4], 0);
833         fillHalfRow(-factorRaanR * my, new Vector3D( 0, -pz,  py),
834                      factorRaanR * mx, new Vector3D(pz,   0, -px),
835                      jacobian[4], 3);
836 
837         // dM
838         fillHalfRow(rOnA, vectorEAnR,    -sinE, vectorER,    jacobian[5], 0);
839         fillHalfRow(rOnA, vectorEAnRDot, -sinE, vectorERDot, jacobian[5], 3);
840 
841         return jacobian;
842 
843     }
844 
845     /** Compute the Jacobian of the orbital parameters with respect to the cartesian parameters.
846      * <p>
847      * Element {@code jacobian[i][j]} is the derivative of parameter i of the orbit with
848      * respect to cartesian coordinate j (x for j=0, y for j=1, z for j=2, xDot for j=3,
849      * yDot for j=4, zDot for j=5).
850      * </p>
851      * @return 6x6 Jacobian matrix
852      */
853     private double[][] computeJacobianMeanWrtCartesianHyperbolic() {
854 
855         final double[][] jacobian = new double[6][6];
856 
857         // compute various intermediate parameters
858         final PVCoordinates pvc = getPVCoordinates();
859         final Vector3D position = pvc.getPosition();
860         final Vector3D velocity = pvc.getVelocity();
861         final Vector3D momentum = pvc.getMomentum();
862         final double r2         = position.getNormSq();
863         final double r          = FastMath.sqrt(r2);
864         final double r3         = r * r2;
865 
866         final double x          = position.getX();
867         final double y          = position.getY();
868         final double z          = position.getZ();
869         final double vx         = velocity.getX();
870         final double vy         = velocity.getY();
871         final double vz         = velocity.getZ();
872         final double mx         = momentum.getX();
873         final double my         = momentum.getY();
874         final double mz         = momentum.getZ();
875 
876         final double mu         = getMu();
877         final double absA       = -a;
878         final double sqrtMuA    = FastMath.sqrt(absA * mu);
879         final double a2         = a * a;
880         final double rOa        = r / absA;
881 
882         final double cosI       = FastMath.cos(i);
883         final double sinI       = FastMath.sin(i);
884 
885         final double pv         = Vector3D.dotProduct(position, velocity);
886 
887         // da
888         final Vector3D vectorAR = new Vector3D(-2 * a2 / r3, position);
889         final Vector3D vectorARDot = velocity.scalarMultiply(-2 * a2 / mu);
890         fillHalfRow(-1, vectorAR,    jacobian[0], 0);
891         fillHalfRow(-1, vectorARDot, jacobian[0], 3);
892 
893         // differentials of the momentum
894         final double m      = momentum.getNorm();
895         final double oOm    = 1 / m;
896         final Vector3D dcXP = new Vector3D(  0,  vz, -vy);
897         final Vector3D dcYP = new Vector3D(-vz,   0,  vx);
898         final Vector3D dcZP = new Vector3D( vy, -vx,   0);
899         final Vector3D dcXV = new Vector3D(  0,  -z,   y);
900         final Vector3D dcYV = new Vector3D(  z,   0,  -x);
901         final Vector3D dcZV = new Vector3D( -y,   x,   0);
902         final Vector3D dCP  = new Vector3D(mx * oOm, dcXP, my * oOm, dcYP, mz * oOm, dcZP);
903         final Vector3D dCV  = new Vector3D(mx * oOm, dcXV, my * oOm, dcYV, mz * oOm, dcZV);
904 
905         // dp
906         final double mOMu   = m / mu;
907         final Vector3D dpP  = new Vector3D(2 * mOMu, dCP);
908         final Vector3D dpV  = new Vector3D(2 * mOMu, dCV);
909 
910         // de
911         final double p      = m * mOMu;
912         final double moO2ae = 1 / (2 * absA * e);
913         final double m2OaMu = -p / absA;
914         fillHalfRow(moO2ae, dpP, m2OaMu * moO2ae, vectorAR,    jacobian[1], 0);
915         fillHalfRow(moO2ae, dpV, m2OaMu * moO2ae, vectorARDot, jacobian[1], 3);
916 
917         // di
918         final double cI1 = 1 / (m * sinI);
919         final double cI2 = cosI * cI1;
920         fillHalfRow(cI2, dCP, -cI1, dcZP, jacobian[2], 0);
921         fillHalfRow(cI2, dCV, -cI1, dcZV, jacobian[2], 3);
922 
923         // dPA
924         final double cP1     =  y * oOm;
925         final double cP2     = -x * oOm;
926         final double cP3     = -(mx * cP1 + my * cP2);
927         final double cP4     = cP3 * oOm;
928         final double cP5     = -1 / (r2 * sinI * sinI);
929         final double cP6     = z  * cP5;
930         final double cP7     = cP3 * cP5;
931         final Vector3D dacP  = new Vector3D(cP1, dcXP, cP2, dcYP, cP4, dCP, oOm, new Vector3D(-my, mx, 0));
932         final Vector3D dacV  = new Vector3D(cP1, dcXV, cP2, dcYV, cP4, dCV);
933         final Vector3D dpoP  = new Vector3D(cP6, dacP, cP7, Vector3D.PLUS_K);
934         final Vector3D dpoV  = new Vector3D(cP6, dacV);
935 
936         final double re2     = r2 * e * e;
937         final double recOre2 = (p - r) / re2;
938         final double resOre2 = (pv * mOMu) / re2;
939         final Vector3D dreP  = new Vector3D(mOMu, velocity, pv / mu, dCP);
940         final Vector3D dreV  = new Vector3D(mOMu, position, pv / mu, dCV);
941         final Vector3D davP  = new Vector3D(-resOre2, dpP, recOre2, dreP, resOre2 / r, position);
942         final Vector3D davV  = new Vector3D(-resOre2, dpV, recOre2, dreV);
943         fillHalfRow(1, dpoP, -1, davP, jacobian[3], 0);
944         fillHalfRow(1, dpoV, -1, davV, jacobian[3], 3);
945 
946         // dRAAN
947         final double cO0 = cI1 * cI1;
948         final double cO1 =  mx * cO0;
949         final double cO2 = -my * cO0;
950         fillHalfRow(cO1, dcYP, cO2, dcXP, jacobian[4], 0);
951         fillHalfRow(cO1, dcYV, cO2, dcXV, jacobian[4], 3);
952 
953         // dM
954         final double s2a    = pv / (2 * absA);
955         final double oObux  = 1 / FastMath.sqrt(m * m + mu * absA);
956         final double scasbu = pv * oObux;
957         final Vector3D dauP = new Vector3D(1 / sqrtMuA, velocity, -s2a / sqrtMuA, vectorAR);
958         final Vector3D dauV = new Vector3D(1 / sqrtMuA, position, -s2a / sqrtMuA, vectorARDot);
959         final Vector3D dbuP = new Vector3D(oObux * mu / 2, vectorAR,    m * oObux, dCP);
960         final Vector3D dbuV = new Vector3D(oObux * mu / 2, vectorARDot, m * oObux, dCV);
961         final Vector3D dcuP = new Vector3D(oObux, velocity, -scasbu * oObux, dbuP);
962         final Vector3D dcuV = new Vector3D(oObux, position, -scasbu * oObux, dbuV);
963         fillHalfRow(1, dauP, -e / (1 + rOa), dcuP, jacobian[5], 0);
964         fillHalfRow(1, dauV, -e / (1 + rOa), dcuV, jacobian[5], 3);
965 
966         return jacobian;
967 
968     }
969 
970     /** {@inheritDoc} */
971     protected double[][] computeJacobianEccentricWrtCartesian() {
972         if (a > 0) {
973             return computeJacobianEccentricWrtCartesianElliptical();
974         } else {
975             return computeJacobianEccentricWrtCartesianHyperbolic();
976         }
977     }
978 
979     /** Compute the Jacobian of the orbital parameters with respect to the cartesian parameters.
980      * <p>
981      * Element {@code jacobian[i][j]} is the derivative of parameter i of the orbit with
982      * respect to cartesian coordinate j (x for j=0, y for j=1, z for j=2, xDot for j=3,
983      * yDot for j=4, zDot for j=5).
984      * </p>
985      * @return 6x6 Jacobian matrix
986      */
987     private double[][] computeJacobianEccentricWrtCartesianElliptical() {
988 
989         // start by computing the Jacobian with mean angle
990         final double[][] jacobian = computeJacobianMeanWrtCartesianElliptical();
991 
992         // Differentiating the Kepler equation M = E - e sin E leads to:
993         // dM = (1 - e cos E) dE - sin E de
994         // which is inverted and rewritten as:
995         // dE = a/r dM + sin E a/r de
996         final double eccentricAnomaly = getEccentricAnomaly();
997         final double cosE             = FastMath.cos(eccentricAnomaly);
998         final double sinE             = FastMath.sin(eccentricAnomaly);
999         final double aOr              = 1 / (1 - e * cosE);
1000 
1001         // update anomaly row
1002         final double[] eRow           = jacobian[1];
1003         final double[] anomalyRow     = jacobian[5];
1004         for (int j = 0; j < anomalyRow.length; ++j) {
1005             anomalyRow[j] = aOr * (anomalyRow[j] + sinE * eRow[j]);
1006         }
1007 
1008         return jacobian;
1009 
1010     }
1011 
1012     /** Compute the Jacobian of the orbital parameters with respect to the cartesian parameters.
1013      * <p>
1014      * Element {@code jacobian[i][j]} is the derivative of parameter i of the orbit with
1015      * respect to cartesian coordinate j (x for j=0, y for j=1, z for j=2, xDot for j=3,
1016      * yDot for j=4, zDot for j=5).
1017      * </p>
1018      * @return 6x6 Jacobian matrix
1019      */
1020     private double[][] computeJacobianEccentricWrtCartesianHyperbolic() {
1021 
1022         // start by computing the Jacobian with mean angle
1023         final double[][] jacobian = computeJacobianMeanWrtCartesianHyperbolic();
1024 
1025         // Differentiating the Kepler equation M = e sinh H - H leads to:
1026         // dM = (e cosh H - 1) dH + sinh H de
1027         // which is inverted and rewritten as:
1028         // dH = 1 / (e cosh H - 1) dM - sinh H / (e cosh H - 1) de
1029         final double H      = getEccentricAnomaly();
1030         final double coshH  = FastMath.cosh(H);
1031         final double sinhH  = FastMath.sinh(H);
1032         final double absaOr = 1 / (e * coshH - 1);
1033 
1034         // update anomaly row
1035         final double[] eRow       = jacobian[1];
1036         final double[] anomalyRow = jacobian[5];
1037         for (int j = 0; j < anomalyRow.length; ++j) {
1038             anomalyRow[j] = absaOr * (anomalyRow[j] - sinhH * eRow[j]);
1039         }
1040 
1041         return jacobian;
1042 
1043     }
1044 
1045     /** {@inheritDoc} */
1046     protected double[][] computeJacobianTrueWrtCartesian() {
1047         if (a > 0) {
1048             return computeJacobianTrueWrtCartesianElliptical();
1049         } else {
1050             return computeJacobianTrueWrtCartesianHyperbolic();
1051         }
1052     }
1053 
1054     /** Compute the Jacobian of the orbital parameters with respect to the cartesian parameters.
1055      * <p>
1056      * Element {@code jacobian[i][j]} is the derivative of parameter i of the orbit with
1057      * respect to cartesian coordinate j (x for j=0, y for j=1, z for j=2, xDot for j=3,
1058      * yDot for j=4, zDot for j=5).
1059      * </p>
1060      * @return 6x6 Jacobian matrix
1061      */
1062     private double[][] computeJacobianTrueWrtCartesianElliptical() {
1063 
1064         // start by computing the Jacobian with eccentric angle
1065         final double[][] jacobian = computeJacobianEccentricWrtCartesianElliptical();
1066 
1067         // Differentiating the eccentric anomaly equation sin E = sqrt(1-e^2) sin v / (1 + e cos v)
1068         // and using cos E = (e + cos v) / (1 + e cos v) to get rid of cos E leads to:
1069         // dE = [sqrt (1 - e^2) / (1 + e cos v)] dv - [sin E / (1 - e^2)] de
1070         // which is inverted and rewritten as:
1071         // dv = sqrt (1 - e^2) a/r dE + [sin E / sqrt (1 - e^2)] a/r de
1072         final double e2               = e * e;
1073         final double oMe2             = 1 - e2;
1074         final double epsilon          = FastMath.sqrt(oMe2);
1075         final double eccentricAnomaly = getEccentricAnomaly();
1076         final double cosE             = FastMath.cos(eccentricAnomaly);
1077         final double sinE             = FastMath.sin(eccentricAnomaly);
1078         final double aOr              = 1 / (1 - e * cosE);
1079         final double aFactor          = epsilon * aOr;
1080         final double eFactor          = sinE * aOr / epsilon;
1081 
1082         // update anomaly row
1083         final double[] eRow           = jacobian[1];
1084         final double[] anomalyRow     = jacobian[5];
1085         for (int j = 0; j < anomalyRow.length; ++j) {
1086             anomalyRow[j] = aFactor * anomalyRow[j] + eFactor * eRow[j];
1087         }
1088 
1089         return jacobian;
1090 
1091     }
1092 
1093     /** Compute the Jacobian of the orbital parameters with respect to the cartesian parameters.
1094      * <p>
1095      * Element {@code jacobian[i][j]} is the derivative of parameter i of the orbit with
1096      * respect to cartesian coordinate j (x for j=0, y for j=1, z for j=2, xDot for j=3,
1097      * yDot for j=4, zDot for j=5).
1098      * </p>
1099      * @return 6x6 Jacobian matrix
1100      */
1101     private double[][] computeJacobianTrueWrtCartesianHyperbolic() {
1102 
1103         // start by computing the Jacobian with eccentric angle
1104         final double[][] jacobian = computeJacobianEccentricWrtCartesianHyperbolic();
1105 
1106         // Differentiating the eccentric anomaly equation sinh H = sqrt(e^2-1) sin v / (1 + e cos v)
1107         // and using cosh H = (e + cos v) / (1 + e cos v) to get rid of cosh H leads to:
1108         // dH = [sqrt (e^2 - 1) / (1 + e cos v)] dv + [sinh H / (e^2 - 1)] de
1109         // which is inverted and rewritten as:
1110         // dv = sqrt (1 - e^2) a/r dH - [sinh H / sqrt (e^2 - 1)] a/r de
1111         final double e2       = e * e;
1112         final double e2Mo     = e2 - 1;
1113         final double epsilon  = FastMath.sqrt(e2Mo);
1114         final double H        = getEccentricAnomaly();
1115         final double coshH    = FastMath.cosh(H);
1116         final double sinhH    = FastMath.sinh(H);
1117         final double aOr      = 1 / (e * coshH - 1);
1118         final double aFactor  = epsilon * aOr;
1119         final double eFactor  = sinhH * aOr / epsilon;
1120 
1121         // update anomaly row
1122         final double[] eRow           = jacobian[1];
1123         final double[] anomalyRow     = jacobian[5];
1124         for (int j = 0; j < anomalyRow.length; ++j) {
1125             anomalyRow[j] = aFactor * anomalyRow[j] - eFactor * eRow[j];
1126         }
1127 
1128         return jacobian;
1129 
1130     }
1131 
1132     /** {@inheritDoc} */
1133     public void addKeplerContribution(final PositionAngle type, final double gm,
1134                                       final double[] pDot) {
1135         final double oMe2;
1136         final double ksi;
1137         final double absA = FastMath.abs(a);
1138         final double n    = FastMath.sqrt(gm / absA) / absA;
1139         switch (type) {
1140             case MEAN :
1141                 pDot[5] += n;
1142                 break;
1143             case ECCENTRIC :
1144                 oMe2 = FastMath.abs(1 - e * e);
1145                 ksi  = 1 + e * FastMath.cos(v);
1146                 pDot[5] += n * ksi / oMe2;
1147                 break;
1148             case TRUE :
1149                 oMe2 = FastMath.abs(1 - e * e);
1150                 ksi  = 1 + e * FastMath.cos(v);
1151                 pDot[5] += n * ksi * ksi / (oMe2 * FastMath.sqrt(oMe2));
1152                 break;
1153             default :
1154                 throw new OrekitInternalError(null);
1155         }
1156     }
1157 
1158     /**  Returns a string representation of this keplerian parameters object.
1159      * @return a string representation of this object
1160      */
1161     public String toString() {
1162         return new StringBuffer().append("keplerian parameters: ").append('{').
1163                                   append("a: ").append(a).
1164                                   append("; e: ").append(e).
1165                                   append("; i: ").append(FastMath.toDegrees(i)).
1166                                   append("; pa: ").append(FastMath.toDegrees(pa)).
1167                                   append("; raan: ").append(FastMath.toDegrees(raan)).
1168                                   append("; v: ").append(FastMath.toDegrees(v)).
1169                                   append(";}").toString();
1170     }
1171 
1172     /** Replace the instance with a data transfer object for serialization.
1173      * @return data transfer object that will be serialized
1174      */
1175     private Object writeReplace() {
1176         return new DTO(this);
1177     }
1178 
1179     /** Internal class used only for serialization. */
1180     private static class DTO implements Serializable {
1181 
1182         /** Serializable UID. */
1183         private static final long serialVersionUID = 20140617L;
1184 
1185         /** Double values. */
1186         private double[] d;
1187 
1188         /** Frame in which are defined the orbital parameters. */
1189         private final Frame frame;
1190 
1191         /** Simple constructor.
1192          * @param orbit instance to serialize
1193          */
1194         private DTO(final KeplerianOrbit orbit) {
1195 
1196             final TimeStampedPVCoordinates pv = orbit.getPVCoordinates();
1197 
1198             // decompose date
1199             final double epoch  = FastMath.floor(pv.getDate().durationFrom(AbsoluteDate.J2000_EPOCH));
1200             final double offset = pv.getDate().durationFrom(AbsoluteDate.J2000_EPOCH.shiftedBy(epoch));
1201 
1202             this.d = new double[] {
1203                 epoch, offset, orbit.getMu(),
1204                 orbit.a, orbit.e, orbit.i,
1205                 orbit.pa, orbit.raan, orbit.v
1206             };
1207 
1208             this.frame = orbit.getFrame();
1209 
1210         }
1211 
1212         /** Replace the deserialized data transfer object with a {@link KeplerianOrbit}.
1213          * @return replacement {@link KeplerianOrbit}
1214          */
1215         private Object readResolve() {
1216             return new KeplerianOrbit(d[3], d[4], d[5], d[6], d[7], d[8], PositionAngle.TRUE,
1217                                       frame, AbsoluteDate.J2000_EPOCH.shiftedBy(d[0]).shiftedBy(d[1]),
1218                                       d[2]);
1219         }
1220 
1221     }
1222 
1223 }