1   /* Copyright 2002-2026 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.propagation.analytical.tle;
18  
19  import java.util.ArrayList;
20  import java.util.Collections;
21  import java.util.List;
22  
23  import org.hipparchus.geometry.euclidean.threed.Vector3D;
24  import org.hipparchus.linear.RealMatrix;
25  import org.hipparchus.util.FastMath;
26  import org.hipparchus.util.MathUtils;
27  import org.hipparchus.util.Pair;
28  import org.hipparchus.util.SinCos;
29  import org.orekit.annotation.DefaultDataContext;
30  import org.orekit.attitudes.Attitude;
31  import org.orekit.attitudes.AttitudeProvider;
32  import org.orekit.attitudes.FrameAlignedProvider;
33  import org.orekit.data.DataContext;
34  import org.orekit.errors.OrekitException;
35  import org.orekit.errors.OrekitMessages;
36  import org.orekit.frames.Frame;
37  import org.orekit.orbits.CartesianOrbit;
38  import org.orekit.orbits.Orbit;
39  import org.orekit.propagation.AbstractMatricesHarvester;
40  import org.orekit.propagation.MatricesHarvester;
41  import org.orekit.propagation.SpacecraftState;
42  import org.orekit.propagation.analytical.AbstractAnalyticalPropagator;
43  import org.orekit.propagation.analytical.tle.generation.FixedPointTleGenerationAlgorithm;
44  import org.orekit.propagation.analytical.tle.generation.TleGenerationAlgorithm;
45  import org.orekit.time.AbsoluteDate;
46  import org.orekit.time.TimeScale;
47  import org.orekit.utils.DoubleArrayDictionary;
48  import org.orekit.utils.PVCoordinates;
49  import org.orekit.utils.ParameterDriver;
50  import org.orekit.utils.ParameterDriversProvider;
51  import org.orekit.utils.ParameterObserver;
52  import org.orekit.utils.TimeSpanMap;
53  import org.orekit.utils.TimeSpanMap.Span;
54  
55  /** This class provides elements to propagate TLE's.
56   * <p>
57   * The models used are SGP4 and SDP4, initially proposed by NORAD as the unique convenient
58   * propagator for TLE's. Inputs and outputs of this propagator are only suited for
59   * NORAD two lines elements sets, since it uses estimations and mean values appropriate
60   * for TLE's only.
61   * </p>
62   * <p>
63   * Deep- or near- space propagator is selected internally according to NORAD recommendations
64   * so that the user has not to worry about the used computation methods. One instance is created
65   * for each TLE (this instance can only be get using {@link #selectExtrapolator(TLE)} method,
66   * and can compute {@link PVCoordinates position and velocity coordinates} at any
67   * time. Maximum accuracy is guaranteed in a 24h range period before and after the provided
68   * TLE epoch (of course this accuracy is not really measurable nor predictable: according to
69   * <a href="https://www.celestrak.com/">CelesTrak</a>, the precision is close to one kilometer
70   * and error won't probably rise above 2 km).
71   * </p>
72   * <p>This implementation is largely inspired from the paper and source code <a
73   * href="https://www.celestrak.com/publications/AIAA/2006-6753/">Revisiting Spacetrack
74   * Report #3</a> and is fully compliant with its results and tests cases.</p>
75   * @author Felix R. Hoots, Ronald L. Roehrich, December 1980 (original fortran)
76   * @author David A. Vallado, Paul Crawford, Richard Hujsak, T.S. Kelso (C++ translation and improvements)
77   * @author Fabien Maussion (java translation)
78   * @see TLE
79   */
80  public abstract class TLEPropagator extends AbstractAnalyticalPropagator implements ParameterDriversProvider {
81  
82      // CHECKSTYLE: stop VisibilityModifier check
83  
84      /** Initial state. */
85      protected TLE tle;
86  
87      /** UTC time scale. */
88      protected final TimeScale utc;
89  
90      /** final RAAN. */
91      protected double xnode;
92  
93      /** final semi major axis. */
94      protected double a;
95  
96      /** final eccentricity. */
97      protected double e;
98  
99      /** final inclination. */
100     protected double i;
101 
102     /** final periapsis argument. */
103     protected double omega;
104 
105     /** L from SPTRCK #3. */
106     protected double xl;
107 
108     /** original recovered semi major axis. */
109     protected double a0dp;
110 
111     /** original recovered mean motion. */
112     protected double xn0dp;
113 
114     /** cosinus original inclination. */
115     protected double cosi0;
116 
117     /** cos io squared. */
118     protected double theta2;
119 
120     /** sinus original inclination. */
121     protected double sini0;
122 
123     /** common parameter for mean anomaly (M) computation. */
124     protected double xmdot;
125 
126     /** common parameter for periapsis argument (omega) computation. */
127     protected double omgdot;
128 
129     /** common parameter for raan (OMEGA) computation. */
130     protected double xnodot;
131 
132     /** original eccentricity squared. */
133     protected double e0sq;
134     /** 1 - e2. */
135     protected double beta02;
136 
137     /** sqrt (1 - e2). */
138     protected double beta0;
139 
140     /** periapsis, expressed in KM and ALTITUDE. */
141     protected double perige;
142 
143     /** eta squared. */
144     protected double etasq;
145 
146     /** original eccentricity * eta. */
147     protected double eeta;
148 
149     /** s* new value for the contant s. */
150     protected double s4;
151 
152     /** tsi from SPTRCK #3. */
153     protected double tsi;
154 
155     /** eta from SPTRCK #3. */
156     protected double eta;
157 
158     /** coef for SGP C3 computation. */
159     protected double coef;
160 
161     /** coef for SGP C5 computation. */
162     protected double coef1;
163 
164     /** C1 from SPTRCK #3. */
165     protected double c1;
166 
167     /** C2 from SPTRCK #3. */
168     protected double c2;
169 
170     /** C4 from SPTRCK #3. */
171     protected double c4;
172 
173     /** common parameter for raan (OMEGA) computation. */
174     protected double xnodcf;
175 
176     /** 3/2 * C1. */
177     protected double t2cof;
178 
179     // CHECKSTYLE: resume VisibilityModifier check
180 
181     /** TLE frame. */
182     private final Frame teme;
183 
184     /** All TLEs and masses. */
185     private TimeSpanMap<Pair<TLE, Double>> tlesAndMasses;
186 
187     /** Driver for the ballistic parameter.
188      * @since 14.0
189      */
190     private final ParameterDriver bStarDriver;
191 
192     /** TLE generation algorithm used when resetting TLE from state. */
193     private TleGenerationAlgorithm generationAlgorithm;
194 
195      /** Protected constructor for derived classes.
196      *
197      * <p>This constructor uses the {@link DataContext#getDefault() default data context}.
198      *
199      * @param initialTLE the unique TLE to propagate
200      * @param attitudeProvider provider for attitude computation
201      * @param mass spacecraft mass (kg)
202      * @see #TLEPropagator(TLE, AttitudeProvider, double, Frame)
203      */
204     @DefaultDataContext
205     protected TLEPropagator(final TLE initialTLE, final AttitudeProvider attitudeProvider, final double mass) {
206         this(initialTLE, attitudeProvider, mass,
207              DataContext.getDefault().getFrames().getTEME());
208     }
209 
210     /** Protected constructor for derived classes.
211      * @param initialTLE the unique TLE to propagate
212      * @param attitudeProvider provider for attitude computation
213      * @param mass spacecraft mass (kg)
214      * @param teme the TEME frame to use for propagation.
215      * @since 10.1
216      */
217     protected TLEPropagator(final TLE initialTLE,
218                             final AttitudeProvider attitudeProvider,
219                             final double mass,
220                             final Frame teme) {
221         super(attitudeProvider);
222         setStartDate(initialTLE.getDate());
223         this.utc           = initialTLE.getUtc();
224         initializeTle(initialTLE);
225         this.teme          = teme;
226         this.tlesAndMasses = new TimeSpanMap<>(new Pair<>(tle, mass));
227         this.bStarDriver   = new ParameterDriver(TleGenerationAlgorithm.B_STAR,
228                                                  initialTLE.getBStar(),
229                                                  TleGenerationAlgorithm.B_STAR_SCALE,
230                                                  Double.NEGATIVE_INFINITY,
231                                                  Double.POSITIVE_INFINITY);
232         bStarDriver.addObserver(new ParameterObserver() {
233 
234             @Override
235             public void valueChanged(final double previousValue, final ParameterDriver driver,
236                                      final AbsoluteDate date) {
237                 resetBStar();
238             }
239 
240             @Override
241             public void valueSpanMapChanged(final TimeSpanMap<Double> previousValueSpanMap,
242                                             final ParameterDriver driver) {
243                 resetBStar();
244             }
245         });
246         this.generationAlgorithm = getDefaultTleGenerationAlgorithm(initialTLE, utc, teme);
247 
248         // set the initial state
249         final Orbit orbit = propagateOrbit(initialTLE.getDate());
250         final Attitude attitude = attitudeProvider.getAttitude(orbit, orbit.getDate(), orbit.getFrame());
251         super.resetInitialState(new SpacecraftState(orbit, attitude).withMass(mass));
252     }
253 
254     /** Selects the extrapolator to use with the selected TLE.
255      *
256      * <p>This method uses the {@link DataContext#getDefault() default data context}.
257      *
258      * @param tle the TLE to propagate.
259      * @return the correct propagator.
260      * @see #selectExtrapolator(TLE, Frame)
261      */
262     @DefaultDataContext
263     public static TLEPropagator selectExtrapolator(final TLE tle) {
264         return selectExtrapolator(tle, DataContext.getDefault().getFrames().getTEME());
265     }
266 
267     /** Selects the extrapolator to use with the selected TLE.
268      * @param tle the TLE to propagate.
269      * @param teme TEME frame.
270      * @return the correct propagator.
271      * @since 10.1
272      * @see #selectExtrapolator(TLE, Frame, AttitudeProvider)
273      */
274     public static TLEPropagator selectExtrapolator(final TLE tle, final Frame teme) {
275         return selectExtrapolator(tle, teme, FrameAlignedProvider.of(teme));
276     }
277 
278     /** Selects the extrapolator to use with the selected TLE.
279      * @param tle the TLE to propagate.
280      * @param teme TEME frame.
281      * @param attitudeProvider provider for attitude computation
282      * @return the correct propagator.
283      * @since 12.2
284      */
285     public static TLEPropagator selectExtrapolator(final TLE tle, final Frame teme, final AttitudeProvider attitudeProvider) {
286         return selectExtrapolator(tle, attitudeProvider, DEFAULT_MASS, teme);
287     }
288 
289     /** Selects the extrapolator to use with the selected TLE.
290      *
291      * <p>This method uses the {@link DataContext#getDefault() default data context}.
292      *
293      * @param tle the TLE to propagate.
294      * @param attitudeProvider provider for attitude computation
295      * @param mass spacecraft mass (kg)
296      * @return the correct propagator.
297      * @see #selectExtrapolator(TLE, AttitudeProvider, double, Frame)
298      */
299     @DefaultDataContext
300     public static TLEPropagator selectExtrapolator(final TLE tle, final AttitudeProvider attitudeProvider,
301                                                    final double mass) {
302         return selectExtrapolator(tle, attitudeProvider, mass,
303                                   DataContext.getDefault().getFrames().getTEME());
304     }
305 
306     /** Selects the extrapolator to use with the selected TLE.
307      * @param tle the TLE to propagate.
308      * @param attitudeProvider provider for attitude computation
309      * @param mass spacecraft mass (kg)
310      * @param teme the TEME frame to use for propagation.
311      * @return the correct propagator.
312      * @since 10.1
313      */
314     public static TLEPropagator selectExtrapolator(final TLE tle,
315                                                    final AttitudeProvider attitudeProvider,
316                                                    final double mass,
317                                                    final Frame teme) {
318 
319         final double xkeOverN = TLEConstants.XKE / (tle.getMeanMotion() * 60.0);
320         final double a1 = FastMath.cbrt(xkeOverN * xkeOverN);
321         final double cosi0 = FastMath.cos(tle.getI());
322         final double oMe2  = 1.0 - tle.getE() * tle.getE();
323         final double temp = TLEConstants.CK2 * 1.5 * (3 * cosi0 * cosi0 - 1.0) / (oMe2 * FastMath.sqrt(oMe2));
324         final double delta1 = temp / (a1 * a1);
325         final double a0 = a1 * (1.0 - delta1 * (TLEConstants.ONE_THIRD + delta1 * (delta1 * 134.0 / 81.0 + 1.0)));
326         final double delta0 = temp / (a0 * a0);
327 
328         // recover original mean motion :
329         final double xn0dp = tle.getMeanMotion() * 60.0 / (delta0 + 1.0);
330 
331         // Period >= 225 minutes is deep space
332         if (MathUtils.TWO_PI / (xn0dp * TLEConstants.MINUTES_PER_DAY) >= (1.0 / 6.4)) {
333             return new DeepSDP4(tle, attitudeProvider, mass, teme);
334         } else {
335             return new SGP4(tle, attitudeProvider, mass, teme);
336         }
337     }
338 
339     /** Get the TLE generation algorithm used when resetting TLE from state.
340      * @return TLE generation algorithm
341      * @since 14.0
342      */
343     TleGenerationAlgorithm getTleGenerationAlgorithm() {
344         return generationAlgorithm;
345     }
346 
347     /** Set the TLE generation algorithm used when resetting TLE from state.
348      * @param tleGenerationAlgorithm TLE generation algorithm
349      * @since 14.0
350      */
351     public void setTleGenerationAlgorithm(final TleGenerationAlgorithm tleGenerationAlgorithm) {
352         this.generationAlgorithm = tleGenerationAlgorithm;
353     }
354 
355     /** Get the Earth gravity coefficient used for TLE propagation.
356      * @return the Earth gravity coefficient.
357      */
358     public static double getMU() {
359         return TLEConstants.MU;
360     }
361 
362     /** Get the extrapolated position and velocity from an initial TLE.
363      * @param date the final date
364      * @return the final PVCoordinates
365      */
366     public PVCoordinates getPVCoordinates(final AbsoluteDate date) {
367 
368         sxpPropagate(date.durationFrom(tle.getDate()) / 60.0, tle.getBStar());
369 
370         // Compute PV with previous calculated parameters
371         return computePVCoordinates();
372     }
373 
374     /** Computation of the first commons parameters.
375      * @param bStar value of the ballistic coefficient to use for propagation
376      */
377     private void initializeCommons(final double bStar) {
378 
379         // Sine and cosine of inclination
380         final SinCos scI0 = FastMath.sinCos(tle.getI());
381 
382         final double xkeOverN = TLEConstants.XKE / (tle.getMeanMotion() * 60.0);
383         final double a1 = FastMath.cbrt(xkeOverN * xkeOverN);
384         cosi0 = scI0.cos();
385         theta2 = cosi0 * cosi0;
386         final double x3thm1 = 3.0 * theta2 - 1.0;
387         e0sq = tle.getE() * tle.getE();
388         beta02 = 1.0 - e0sq;
389         beta0 = FastMath.sqrt(beta02);
390         final double tval = TLEConstants.CK2 * 1.5 * x3thm1 / (beta0 * beta02);
391         final double delta1 = tval / (a1 * a1);
392         final double a0 = a1 * (1.0 - delta1 * (TLEConstants.ONE_THIRD + delta1 * (1.0 + 134.0 / 81.0 * delta1)));
393         final double delta0 = tval / (a0 * a0);
394 
395         // recover original mean motion and semi-major axis :
396         xn0dp = tle.getMeanMotion() * 60.0 / (delta0 + 1.0);
397         a0dp = a0 / (1.0 - delta0);
398 
399         // Values of s and qms2t :
400         s4 = TLEConstants.S;  // unmodified value for s
401         double q0ms24 = TLEConstants.QOMS2T; // unmodified value for q0ms2T
402 
403         perige = (a0dp * (1 - tle.getE()) - TLEConstants.NORMALIZED_EQUATORIAL_RADIUS) * TLEConstants.EARTH_RADIUS; // perige
404 
405         //  For periapsis below 156 km, the values of s and qoms2t are changed :
406         if (perige < 156.0) {
407             if (perige <= 98.0) {
408                 s4 = 20.0;
409             } else {
410                 s4 = perige - 78.0;
411             }
412             final double temp_val = (120.0 - s4) * TLEConstants.NORMALIZED_EQUATORIAL_RADIUS / TLEConstants.EARTH_RADIUS;
413             final double temp_val_squared = temp_val * temp_val;
414             q0ms24 = temp_val_squared * temp_val_squared;
415             s4 = s4 / TLEConstants.EARTH_RADIUS + TLEConstants.NORMALIZED_EQUATORIAL_RADIUS; // new value for q0ms2T and s
416         }
417 
418         final double pinv = 1.0 / (a0dp * beta02);
419         final double pinvsq = pinv * pinv;
420         tsi = 1.0 / (a0dp - s4);
421         eta = a0dp * tle.getE() * tsi;
422         etasq = eta * eta;
423         eeta = tle.getE() * eta;
424 
425         final double psisq = FastMath.abs(1.0 - etasq); // abs because pow 3.5 needs positive value
426         final double tsi_squared = tsi * tsi;
427         coef = q0ms24 * tsi_squared * tsi_squared;
428         coef1 = coef / FastMath.pow(psisq, 3.5);
429 
430         // C2 and C1 coefficients computation :
431         c2 = coef1 * xn0dp * (a0dp * (1.0 + 1.5 * etasq + eeta * (4.0 + etasq)) +
432              0.75 * TLEConstants.CK2 * tsi / psisq * x3thm1 * (8.0 + 3.0 * etasq * (8.0 + etasq)));
433         c1 = bStar * c2;
434         sini0 = scI0.sin();
435 
436         final double x1mth2 = 1.0 - theta2;
437 
438         // C4 coefficient computation :
439         c4 = 2.0 * xn0dp * coef1 * a0dp * beta02 * (eta * (2.0 + 0.5 * etasq) +
440                 tle.getE() * (0.5 + 2.0 * etasq) -
441                 2 * TLEConstants.CK2 * tsi / (a0dp * psisq) *
442                         (-3.0 * x3thm1 * (1.0 - 2.0 * eeta + etasq * (1.5 - 0.5 * eeta)) +
443                                 0.75 * x1mth2 * (2.0 * etasq - eeta * (1.0 + etasq)) * FastMath.cos(2.0 * tle.getPeriapsisArgument())));
444 
445         final double theta4 = theta2 * theta2;
446         final double temp1 = 3 * TLEConstants.CK2 * pinvsq * xn0dp;
447         final double temp2 = temp1 * TLEConstants.CK2 * pinvsq;
448         final double temp3 = 1.25 * TLEConstants.CK4 * pinvsq * pinvsq * xn0dp;
449 
450         // atmospheric and gravitation coefs :(Mdf and OMEGAdf)
451         xmdot = xn0dp +
452                 0.5 * temp1 * beta0 * x3thm1 +
453                 0.0625 * temp2 * beta0 * (13.0 - 78.0 * theta2 + 137.0 * theta4);
454 
455         final double x1m5th = 1.0 - 5.0 * theta2;
456 
457         omgdot = -0.5 * temp1 * x1m5th +
458                 0.0625 * temp2 * (7.0 - 114.0 * theta2 + 395.0 * theta4) +
459                 temp3 * (3.0 - 36.0 * theta2 + 49.0 * theta4);
460 
461         final double xhdot1 = -temp1 * cosi0;
462 
463         xnodot = xhdot1 + (0.5 * temp2 * (4.0 - 19.0 * theta2) + 2.0 * temp3 * (3.0 - 7.0 * theta2)) * cosi0;
464         xnodcf = 3.5 * beta02 * xhdot1 * c1;
465         t2cof = 1.5 * c1;
466 
467     }
468 
469     /** Retrieves the position and velocity.
470      * @return the computed PVCoordinates.
471      */
472     private PVCoordinates computePVCoordinates() {
473 
474         // Sine and cosine of final periapsis argument
475         final SinCos scOmega = FastMath.sinCos(omega);
476 
477         // Long period periodics
478         final double axn = e * scOmega.cos();
479         double temp = 1.0 / (a * (1.0 - e * e));
480         final double xlcof = 0.125 * TLEConstants.A3OVK2 * sini0 * (3.0 + 5.0 * cosi0) / (1.0 + cosi0);
481         final double aycof = 0.25 * TLEConstants.A3OVK2 * sini0;
482         final double xll = temp * xlcof * axn;
483         final double aynl = temp * aycof;
484         final double xlt = xl + xll;
485         final double ayn = e * scOmega.sin() + aynl;
486         final double elsq = axn * axn + ayn * ayn;
487         final double capu = MathUtils.normalizeAngle(xlt - xnode, FastMath.PI);
488         double epw = capu;
489         double ecosE = 0;
490         double esinE = 0;
491         double sinEPW = 0;
492         double cosEPW = 0;
493 
494         // Dundee changes:  items dependent on cosio get recomputed:
495         final double cosi0Sq = cosi0 * cosi0;
496         final double x3thm1 = 3.0 * cosi0Sq - 1.0;
497         final double x1mth2 = 1.0 - cosi0Sq;
498         final double x7thm1 = 7.0 * cosi0Sq - 1.0;
499 
500         if (e > (1 - 1e-6)) {
501             throw new OrekitException(OrekitMessages.TOO_LARGE_ECCENTRICITY_FOR_PROPAGATION_MODEL, e);
502         }
503 
504         // Solve Kepler's' Equation.
505         final double newtonRaphsonEpsilon = 1e-12;
506         for (int j = 0; j < 10; j++) {
507 
508             boolean doSecondOrderNewtonRaphson = true;
509 
510             final SinCos scEPW = FastMath.sinCos(epw);
511             sinEPW = scEPW.sin();
512             cosEPW = scEPW.cos();
513             ecosE = axn * cosEPW + ayn * sinEPW;
514             esinE = axn * sinEPW - ayn * cosEPW;
515             final double f = capu - epw + esinE;
516             if (FastMath.abs(f) < newtonRaphsonEpsilon) {
517                 break;
518             }
519             final double fdot = 1.0 - ecosE;
520             double delta_epw = f / fdot;
521             if (j == 0) {
522                 final double maxNewtonRaphson = 1.25 * FastMath.abs(e);
523                 doSecondOrderNewtonRaphson = false;
524                 if (delta_epw > maxNewtonRaphson) {
525                     delta_epw = maxNewtonRaphson;
526                 } else if (delta_epw < -maxNewtonRaphson) {
527                     delta_epw = -maxNewtonRaphson;
528                 } else {
529                     doSecondOrderNewtonRaphson = true;
530                 }
531             }
532             if (doSecondOrderNewtonRaphson) {
533                 delta_epw = f / (fdot + 0.5 * esinE * delta_epw);
534             }
535             epw += delta_epw;
536         }
537 
538         // Short period preliminary quantities
539         temp = 1.0 - elsq;
540         final double pl = a * temp;
541         final double r = a * (1.0 - ecosE);
542         double temp2 = a / r;
543         final double betal = FastMath.sqrt(temp);
544         temp = esinE / (1.0 + betal);
545         final double cosu = temp2 * (cosEPW - axn + ayn * temp);
546         final double sinu = temp2 * (sinEPW - ayn - axn * temp);
547         final double u = FastMath.atan2(sinu, cosu);
548         final double sin2u = 2.0 * sinu * cosu;
549         final double cos2u = 2.0 * cosu * cosu - 1.0;
550         final double temp1 = TLEConstants.CK2 / pl;
551         temp2 = temp1 / pl;
552 
553         // Update for short periodics
554         final double rk = r * (1.0 - 1.5 * temp2 * betal * x3thm1) + 0.5 * temp1 * x1mth2 * cos2u;
555         final double uk = u - 0.25 * temp2 * x7thm1 * sin2u;
556         final double xnodek = xnode + 1.5 * temp2 * cosi0 * sin2u;
557         final double xinck = i + 1.5 * temp2 * cosi0 * sini0 * cos2u;
558 
559         // Orientation vectors
560         final SinCos scuk   = FastMath.sinCos(uk);
561         final SinCos scik   = FastMath.sinCos(xinck);
562         final SinCos scnok  = FastMath.sinCos(xnodek);
563         final double sinuk  = scuk.sin();
564         final double cosuk  = scuk.cos();
565         final double sinik  = scik.sin();
566         final double cosik  = scik.cos();
567         final double sinnok = scnok.sin();
568         final double cosnok = scnok.cos();
569         final double xmx = -sinnok * cosik;
570         final double xmy = cosnok * cosik;
571         final double ux  = xmx * sinuk + cosnok * cosuk;
572         final double uy  = xmy * sinuk + sinnok * cosuk;
573         final double uz  = sinik * sinuk;
574 
575         // Position and velocity
576         final double cr = 1000 * rk * TLEConstants.EARTH_RADIUS;
577         final Vector3D pos = new Vector3D(cr * ux, cr * uy, cr * uz);
578 
579         final double rdot   = TLEConstants.XKE * FastMath.sqrt(a) * esinE / r;
580         final double rfdot  = TLEConstants.XKE * FastMath.sqrt(pl) / r;
581         final double xn     = TLEConstants.XKE / (a * FastMath.sqrt(a));
582         final double rdotk  = rdot - xn * temp1 * x1mth2 * sin2u;
583         final double rfdotk = rfdot + xn * temp1 * (x1mth2 * cos2u + 1.5 * x3thm1);
584         final double vx     = xmx * cosuk - cosnok * sinuk;
585         final double vy     = xmy * cosuk - sinnok * sinuk;
586         final double vz     = sinik * cosuk;
587 
588         final double cv = 1000.0 * TLEConstants.EARTH_RADIUS / 60.0;
589         final Vector3D vel = new Vector3D(cv * (rdotk * ux + rfdotk * vx),
590                                           cv * (rdotk * uy + rfdotk * vy),
591                                           cv * (rdotk * uz + rfdotk * vz));
592 
593         return new PVCoordinates(pos, vel);
594 
595     }
596 
597     /** {@inheritDoc} */
598     @Override
599     public List<ParameterDriver> getParametersDrivers() {
600         return Collections.singletonList(bStarDriver);
601     }
602 
603     /** Initialization proper to each propagator (SGP or SDP).
604      * @param bStar value of the ballistic coefficient to use for propagation
605      */
606     protected abstract void sxpInitialize(double bStar);
607 
608     /** Propagation proper to each propagator (SGP or SDP).
609      * @param t the offset from initial epoch (min)
610      * @param bStar value of the ballistic coefficient to use for propagation
611      */
612     protected abstract void sxpPropagate(double t, double bStar);
613 
614     /** {@inheritDoc}
615      * <p>
616      * For TLE propagator, calling this method is only recommended
617      * for covariance propagation when the new <code>state</code>
618      * differs from the previous one by only adding the additional
619      * state containing the derivatives.
620      * </p>
621      */
622     public void resetInitialState(final SpacecraftState state) {
623         super.resetInitialState(state);
624         resetTle(state);
625         tlesAndMasses = new TimeSpanMap<>(new Pair<>(tle, state.getMass()));
626     }
627 
628     /** {@inheritDoc} */
629     protected void resetIntermediateState(final SpacecraftState state, final boolean forward) {
630         resetTle(state);
631         final Pair<TLE, Double> tleAndMass = new Pair<>(tle, state.getMass());
632         if (forward) {
633             tlesAndMasses.addValidAfter(tleAndMass, state.getDate(), false);
634         } else {
635             tlesAndMasses.addValidBefore(tleAndMass, state.getDate(), false);
636         }
637         stateChanged(state);
638     }
639 
640     /** Reset internal TLE from a SpacecraftState.
641      * @param state spacecraft state on which to base new TLE
642      */
643     private void resetTle(final SpacecraftState state) {
644 
645         final TLE newTle = generationAlgorithm.createFromDrivers();
646         initializeTle(newTle);
647     }
648 
649     /** Reset the B-star value from the parameter driver.
650      * @since 14.0
651      */
652     private void resetBStar() {
653         tle = new TLE(tle.getSatelliteNumber(), tle.getClassification(),
654                       tle.getLaunchYear(), tle.getLaunchNumber(), tle.getLaunchPiece(),
655                       tle.getEphemerisType(), tle.getElementNumber(), tle.getDate(),
656                       tle.getMeanMotion(), tle.getMeanMotionFirstDerivative(),
657                       tle.getMeanMotionSecondDerivative(),
658                       tle.getE(), tle.getI(), tle.getPeriapsisArgument(), tle.getRaan(),
659                       tle.getMeanAnomaly(), tle.getRevolutionNumberAtEpoch(),
660                       bStarDriver.getValue());
661         initializeTle(tle);
662     }
663 
664     /** Initialize internal TLE.
665      * @param newTle tle to replace current one
666      */
667     private void initializeTle(final TLE newTle) {
668         tle = newTle;
669         initializeCommons(newTle.getBStar());
670         sxpInitialize(newTle.getBStar());
671     }
672 
673     /** {@inheritDoc} */
674     protected double getMass(final AbsoluteDate date) {
675         return tlesAndMasses.get(date).getValue();
676     }
677 
678     /** {@inheritDoc} */
679     public Orbit propagateOrbit(final AbsoluteDate date) {
680         final TLE closestTle = tlesAndMasses.get(date).getKey();
681         if (!tle.equals(closestTle)) {
682             initializeTle(closestTle);
683         }
684         return new CartesianOrbit(getPVCoordinates(date), teme, date, TLEConstants.MU);
685     }
686 
687     /** Get the underlying TLE.
688      * If there has been calls to #resetInitialState or #resetIntermediateState,
689      * it will not be the same as given to the constructor.
690      * @return underlying TLE
691      */
692     public TLE getTLE() {
693         return tle;
694     }
695 
696     /** {@inheritDoc} */
697     public Frame getFrame() {
698         return teme;
699     }
700 
701     /** {@inheritDoc} */
702     @Override
703     protected AbstractMatricesHarvester createHarvester(final String stmName, final RealMatrix initialStm,
704                                                         final DoubleArrayDictionary initialJacobianColumns) {
705         // Create the harvester
706         final TLEHarvester harvester = new TLEHarvester(this, stmName, initialStm, initialJacobianColumns);
707         // Update the list of additional state provider
708         addAdditionalDataProvider(harvester);
709         // Return the configured harvester
710         return harvester;
711     }
712 
713     /**
714      * Get the names of the parameters in the matrix returned by {@link MatricesHarvester#getParametersJacobian}.
715      * @return names of the parameters (i.e. columns) of the Jacobian matrix
716      */
717     protected List<String> getJacobiansColumnsNames() {
718         if (bStarDriver.isSelected()) {
719             final List<String> columnsNames = new ArrayList<>();
720             for (Span<String> span = bStarDriver.getNamesSpanMap().getFirstSpan(); span != null; span = span.next()) {
721                 columnsNames.add(span.getData());
722             }
723             return columnsNames;
724         } else {
725             return Collections.emptyList();
726         }
727     }
728 
729     /**
730      * Get the default TLE generation algorithm.
731      * @param templateTLE template TLE
732      * @param utc UTC time scale
733      * @param teme TEME frame
734      * @return a TLE generation algorithm
735      */
736     public static TleGenerationAlgorithm getDefaultTleGenerationAlgorithm(final TLE templateTLE,
737                                                                           final TimeScale utc, final Frame teme) {
738         return new FixedPointTleGenerationAlgorithm(templateTLE,
739                                                     FixedPointTleGenerationAlgorithm.EPSILON_DEFAULT,
740                                                     FixedPointTleGenerationAlgorithm.MAX_ITERATIONS_DEFAULT,
741                                                     FixedPointTleGenerationAlgorithm.SCALE_DEFAULT, utc, teme);
742     }
743 
744 }