1   /* Copyright 2002-2021 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.io.Serializable;
20  import java.text.DecimalFormat;
21  import java.text.DecimalFormatSymbols;
22  import java.util.Collections;
23  import java.util.List;
24  import java.util.Locale;
25  import java.util.Objects;
26  
27  import org.hipparchus.CalculusFieldElement;
28  import org.hipparchus.Field;
29  import org.hipparchus.geometry.euclidean.threed.Rotation;
30  import org.hipparchus.util.ArithmeticUtils;
31  import org.hipparchus.util.FastMath;
32  import org.hipparchus.util.MathArrays;
33  import org.hipparchus.util.MathUtils;
34  import org.orekit.annotation.DefaultDataContext;
35  import org.orekit.attitudes.InertialProvider;
36  import org.orekit.data.DataContext;
37  import org.orekit.errors.OrekitException;
38  import org.orekit.errors.OrekitInternalError;
39  import org.orekit.errors.OrekitMessages;
40  import org.orekit.frames.Frame;
41  import org.orekit.orbits.FieldEquinoctialOrbit;
42  import org.orekit.orbits.FieldKeplerianOrbit;
43  import org.orekit.orbits.FieldOrbit;
44  import org.orekit.orbits.OrbitType;
45  import org.orekit.orbits.PositionAngle;
46  import org.orekit.propagation.FieldSpacecraftState;
47  import org.orekit.time.DateComponents;
48  import org.orekit.time.DateTimeComponents;
49  import org.orekit.time.FieldAbsoluteDate;
50  import org.orekit.time.FieldTimeStamped;
51  import org.orekit.time.TimeComponents;
52  import org.orekit.time.TimeScale;
53  import org.orekit.utils.ParameterDriver;
54  
55  /** This class is a container for a single set of TLE data.
56   *
57   * <p>TLE sets can be built either by providing directly the two lines, in
58   * which case parsing is performed internally or by providing the already
59   * parsed elements.</p>
60   * <p>TLE are not transparently convertible to {@link org.orekit.orbits.Orbit Orbit}
61   * instances. They are significant only with respect to their dedicated {@link
62   * TLEPropagator propagator}, which also computes position and velocity coordinates.
63   * Any attempt to directly use orbital parameters like {@link #getE() eccentricity},
64   * {@link #getI() inclination}, etc. without any reference to the {@link TLEPropagator
65   * TLE propagator} is prone to errors.</p>
66   * <p>More information on the TLE format can be found on the
67   * <a href="https://www.celestrak.com/">CelesTrak website.</a></p>
68   * @author Fabien Maussion
69   * @author Luc Maisonobe
70   * @author Thomas Paulet (field translation)
71   * @since 11.0
72   */
73  public class FieldTLE<T extends CalculusFieldElement<T>> implements FieldTimeStamped<T>, Serializable {
74  
75      /** Identifier for default type of ephemeris (SGP4/SDP4). */
76      public static final int DEFAULT = 0;
77  
78      /** Identifier for SGP type of ephemeris. */
79      public static final int SGP = 1;
80  
81      /** Identifier for SGP4 type of ephemeris. */
82      public static final int SGP4 = 2;
83  
84      /** Identifier for SDP4 type of ephemeris. */
85      public static final int SDP4 = 3;
86  
87      /** Identifier for SGP8 type of ephemeris. */
88      public static final int SGP8 = 4;
89  
90      /** Identifier for SDP8 type of ephemeris. */
91      public static final int SDP8 = 5;
92  
93      /** Parameter name for B* coefficient. */
94      public static final String B_STAR = "BSTAR";
95  
96      /** Default value for epsilon. */
97      private static final double EPSILON_DEFAULT = 1.0e-10;
98  
99      /** Default value for maxIterations. */
100     private static final int MAX_ITERATIONS_DEFAULT = 100;
101 
102     /** B* scaling factor.
103      * <p>
104      * We use a power of 2 to avoid numeric noise introduction
105      * in the multiplications/divisions sequences.
106      * </p>
107      */
108     private static final double B_STAR_SCALE = FastMath.scalb(1.0, -20);
109 
110     /** Name of the mean motion parameter. */
111     private static final String MEAN_MOTION = "meanMotion";
112 
113     /** Name of the inclination parameter. */
114     private static final String INCLINATION = "inclination";
115 
116     /** Name of the eccentricity parameter. */
117     private static final String ECCENTRICITY = "eccentricity";
118 
119     /** International symbols for parsing. */
120     private static final DecimalFormatSymbols SYMBOLS =
121         new DecimalFormatSymbols(Locale.US);
122 
123     /** Serializable UID. */
124     private static final long serialVersionUID = -1596648022319057689L;
125 
126     /** The satellite number. */
127     private final int satelliteNumber;
128 
129     /** Classification (U for unclassified). */
130     private final char classification;
131 
132     /** Launch year. */
133     private final int launchYear;
134 
135     /** Launch number. */
136     private final int launchNumber;
137 
138     /** Piece of launch (from "A" to "ZZZ"). */
139     private final String launchPiece;
140 
141     /** Type of ephemeris. */
142     private final int ephemerisType;
143 
144     /** Element number. */
145     private final int elementNumber;
146 
147     /** the TLE current date. */
148     private final transient FieldAbsoluteDate<T> epoch;
149 
150     /** Mean motion (rad/s). */
151     private final T meanMotion;
152 
153     /** Mean motion first derivative (rad/s²). */
154     private final T meanMotionFirstDerivative;
155 
156     /** Mean motion second derivative (rad/s³). */
157     private final T meanMotionSecondDerivative;
158 
159     /** Eccentricity. */
160     private final T eccentricity;
161 
162     /** Inclination (rad). */
163     private final T inclination;
164 
165     /** Argument of perigee (rad). */
166     private final T pa;
167 
168     /** Right Ascension of the Ascending node (rad). */
169     private final T raan;
170 
171     /** Mean anomaly (rad). */
172     private final T meanAnomaly;
173 
174     /** Revolution number at epoch. */
175     private final int revolutionNumberAtEpoch;
176 
177     /** First line. */
178     private String line1;
179 
180     /** Second line. */
181     private String line2;
182 
183     /** The UTC scale. */
184     private final TimeScale utc;
185 
186     /** Driver for ballistic coefficient parameter. */
187     private final transient ParameterDriver bStarParameterDriver;
188 
189     /** Simple constructor from unparsed two lines. This constructor uses the {@link
190      * DataContext#getDefault() default data context}.
191      *
192      * <p>The static method {@link #isFormatOK(String, String)} should be called
193      * before trying to build this object.<p>
194      * @param field field utilized by default
195      * @param line1 the first element (69 char String)
196      * @param line2 the second element (69 char String)
197      * @see #FieldTLE(Field, String, String, TimeScale)
198      */
199     @DefaultDataContext
200     public FieldTLE(final Field<T> field, final String line1, final String line2) {
201         this(field, line1, line2, DataContext.getDefault().getTimeScales().getUTC());
202     }
203 
204     /** Simple constructor from unparsed two lines using the given time scale as UTC.
205      *
206      *<p>This method uses the {@link DataContext#getDefault() default data context}.
207      *
208      * <p>The static method {@link #isFormatOK(String, String)} should be called
209      * before trying to build this object.<p>
210      * @param field field utilized by default
211      * @param line1 the first element (69 char String)
212      * @param line2 the second element (69 char String)
213      * @param utc the UTC time scale.
214      */
215     public FieldTLE(final Field<T> field, final String line1, final String line2, final TimeScale utc) {
216 
217         // zero and pi for fields
218         final T zero = field.getZero();
219         final T pi   = zero.getPi();
220 
221         // identification
222         satelliteNumber = ParseUtils.parseSatelliteNumber(line1, 2, 5);
223         final int satNum2 = ParseUtils.parseSatelliteNumber(line2, 2, 5);
224         if (satelliteNumber != satNum2) {
225             throw new OrekitException(OrekitMessages.TLE_LINES_DO_NOT_REFER_TO_SAME_OBJECT,
226                                       line1, line2);
227         }
228         classification  = line1.charAt(7);
229         launchYear      = ParseUtils.parseYear(line1, 9);
230         launchNumber    = ParseUtils.parseInteger(line1, 11, 3);
231         launchPiece     = line1.substring(14, 17).trim();
232         ephemerisType   = ParseUtils.parseInteger(line1, 62, 1);
233         elementNumber   = ParseUtils.parseInteger(line1, 64, 4);
234 
235         // Date format transform (nota: 27/31250 == 86400/100000000)
236         final int    year      = ParseUtils.parseYear(line1, 18);
237         final int    dayInYear = ParseUtils.parseInteger(line1, 20, 3);
238         final long   df        = 27l * ParseUtils.parseInteger(line1, 24, 8);
239         final int    secondsA  = (int) (df / 31250l);
240         final double secondsB  = (df % 31250l) / 31250.0;
241         epoch = new FieldAbsoluteDate<>(field, new DateComponents(year, dayInYear),
242                                  new TimeComponents(secondsA, secondsB),
243                                  utc);
244 
245         // mean motion development
246         // converted from rev/day, 2 * rev/day^2 and 6 * rev/day^3 to rad/s, rad/s^2 and rad/s^3
247         meanMotion                 = pi.multiply(ParseUtils.parseDouble(line2, 52, 11)).divide(43200.0);
248         meanMotionFirstDerivative  = pi.multiply(ParseUtils.parseDouble(line1, 33, 10)).divide(1.86624e9);
249         meanMotionSecondDerivative = pi.multiply(Double.parseDouble((line1.substring(44, 45) + '.' +
250                                                                      line1.substring(45, 50) + 'e' +
251                                                                      line1.substring(50, 52)).replace(' ', '0'))).divide(5.3747712e13);
252 
253         eccentricity = zero.add(Double.parseDouble("." + line2.substring(26, 33).replace(' ', '0')));
254         inclination  = zero.add(FastMath.toRadians(ParseUtils.parseDouble(line2, 8, 8)));
255         pa           = zero.add(FastMath.toRadians(ParseUtils.parseDouble(line2, 34, 8)));
256         raan         = zero.add(FastMath.toRadians(Double.parseDouble(line2.substring(17, 25).replace(' ', '0'))));
257         meanAnomaly  = zero.add(FastMath.toRadians(ParseUtils.parseDouble(line2, 43, 8)));
258 
259         revolutionNumberAtEpoch = ParseUtils.parseInteger(line2, 63, 5);
260         final double bStarValue = Double.parseDouble((line1.substring(53, 54) + '.' +
261                         line1.substring(54, 59) + 'e' +
262                         line1.substring(59, 61)).replace(' ', '0'));
263 
264         // save the lines
265         this.line1 = line1;
266         this.line2 = line2;
267         this.utc = utc;
268 
269         this.bStarParameterDriver = new ParameterDriver(B_STAR, bStarValue, B_STAR_SCALE,
270                                                         Double.NEGATIVE_INFINITY,
271                                                         Double.POSITIVE_INFINITY);
272 
273     }
274 
275     /**
276      * <p>
277      * Simple constructor from already parsed elements. This constructor uses the
278      * {@link DataContext#getDefault() default data context}.
279      * </p>
280      *
281      * <p>
282      * The mean anomaly, the right ascension of ascending node Ω and the argument of
283      * perigee ω are normalized into the [0, 2π] interval as they can be negative.
284      * After that, a range check is performed on some of the orbital elements:
285      *
286      * <pre>
287      *     meanMotion &gt;= 0
288      *     0 &lt;= i &lt;= π
289      *     0 &lt;= Ω &lt;= 2π
290      *     0 &lt;= e &lt;= 1
291      *     0 &lt;= ω &lt;= 2π
292      *     0 &lt;= meanAnomaly &lt;= 2π
293      * </pre>
294      *
295      *
296      * @param satelliteNumber satellite number
297      * @param classification classification (U for unclassified)
298      * @param launchYear launch year (all digits)
299      * @param launchNumber launch number
300      * @param launchPiece launch piece (3 char String)
301      * @param ephemerisType type of ephemeris
302      * @param elementNumber element number
303      * @param epoch elements epoch
304      * @param meanMotion mean motion (rad/s)
305      * @param meanMotionFirstDerivative mean motion first derivative (rad/s²)
306      * @param meanMotionSecondDerivative mean motion second derivative (rad/s³)
307      * @param e eccentricity
308      * @param i inclination (rad)
309      * @param pa argument of perigee (rad)
310      * @param raan right ascension of ascending node (rad)
311      * @param meanAnomaly mean anomaly (rad)
312      * @param revolutionNumberAtEpoch revolution number at epoch
313      * @param bStar ballistic coefficient
314      * @see #FieldTLE(int, char, int, int, String, int, int, FieldAbsoluteDate, CalculusFieldElement, CalculusFieldElement,
315      * CalculusFieldElement, CalculusFieldElement, CalculusFieldElement, CalculusFieldElement, CalculusFieldElement, CalculusFieldElement, int, double, TimeScale)
316      */
317     @DefaultDataContext
318     public FieldTLE(final int satelliteNumber, final char classification,
319                final int launchYear, final int launchNumber, final String launchPiece,
320                final int ephemerisType, final int elementNumber, final FieldAbsoluteDate<T> epoch,
321                final T meanMotion, final T meanMotionFirstDerivative,
322                final T meanMotionSecondDerivative, final T e, final T i,
323                final T pa, final T raan, final T meanAnomaly,
324                final int revolutionNumberAtEpoch, final double bStar) {
325         this(satelliteNumber, classification, launchYear, launchNumber, launchPiece,
326                 ephemerisType, elementNumber, epoch, meanMotion,
327                 meanMotionFirstDerivative, meanMotionSecondDerivative, e, i, pa, raan,
328                 meanAnomaly, revolutionNumberAtEpoch, bStar,
329                 DataContext.getDefault().getTimeScales().getUTC());
330     }
331 
332     /**
333      * <p>
334      * Simple constructor from already parsed elements using the given time scale as
335      * UTC.
336      * </p>
337      * <p>
338      * The mean anomaly, the right ascension of ascending node Ω and the argument of
339      * perigee ω are normalized into the [0, 2π] interval as they can be negative.
340      * After that, a range check is performed on some of the orbital elements:
341      *
342      * <pre>
343      *     meanMotion &gt;= 0
344      *     0 &lt;= i &lt;= π
345      *     0 &lt;= Ω &lt;= 2π
346      *     0 &lt;= e &lt;= 1
347      *     0 &lt;= ω &lt;= 2π
348      *     0 &lt;= meanAnomaly &lt;= 2π
349      * </pre>
350      *
351      *
352      * @param satelliteNumber satellite number
353      * @param classification classification (U for unclassified)
354      * @param launchYear launch year (all digits)
355      * @param launchNumber launch number
356      * @param launchPiece launch piece (3 char String)
357      * @param ephemerisType type of ephemeris
358      * @param elementNumber element number
359      * @param epoch elements epoch
360      * @param meanMotion mean motion (rad/s)
361      * @param meanMotionFirstDerivative mean motion first derivative (rad/s²)
362      * @param meanMotionSecondDerivative mean motion second derivative (rad/s³)
363      * @param e eccentricity
364      * @param i inclination (rad)
365      * @param pa argument of perigee (rad)
366      * @param raan right ascension of ascending node (rad)
367      * @param meanAnomaly mean anomaly (rad)
368      * @param revolutionNumberAtEpoch revolution number at epoch
369      * @param bStar ballistic coefficient
370      * @param utc the UTC time scale.
371      */
372     public FieldTLE(final int satelliteNumber, final char classification,
373                final int launchYear, final int launchNumber, final String launchPiece,
374                final int ephemerisType, final int elementNumber, final FieldAbsoluteDate<T> epoch,
375                final T meanMotion, final T meanMotionFirstDerivative,
376                final T meanMotionSecondDerivative, final T e, final T i,
377                final T pa, final T raan, final T meanAnomaly,
378                final int revolutionNumberAtEpoch, final double bStar,
379                final TimeScale utc) {
380 
381         // pi for fields
382         final T pi = e.getPi();
383 
384         // identification
385         this.satelliteNumber = satelliteNumber;
386         this.classification  = classification;
387         this.launchYear      = launchYear;
388         this.launchNumber    = launchNumber;
389         this.launchPiece     = launchPiece;
390         this.ephemerisType   = ephemerisType;
391         this.elementNumber   = elementNumber;
392 
393         // orbital parameters
394         this.epoch = epoch;
395         // Checking mean motion range
396         this.meanMotion = meanMotion;
397         this.meanMotionFirstDerivative = meanMotionFirstDerivative;
398         this.meanMotionSecondDerivative = meanMotionSecondDerivative;
399 
400         // Checking inclination range
401         this.inclination = i;
402 
403         // Normalizing RAAN in [0,2pi] interval
404         this.raan = MathUtils.normalizeAngle(raan, pi);
405 
406         // Checking eccentricity range
407         this.eccentricity = e;
408 
409         // Normalizing PA in [0,2pi] interval
410         this.pa = MathUtils.normalizeAngle(pa, pi);
411 
412         // Normalizing mean anomaly in [0,2pi] interval
413         this.meanAnomaly = MathUtils.normalizeAngle(meanAnomaly, pi);
414 
415         this.revolutionNumberAtEpoch = revolutionNumberAtEpoch;
416         this.bStarParameterDriver = new ParameterDriver(B_STAR, bStar, B_STAR_SCALE,
417                                                        Double.NEGATIVE_INFINITY,
418                                                        Double.POSITIVE_INFINITY);
419 
420         // don't build the line until really needed
421         this.line1 = null;
422         this.line2 = null;
423         this.utc = utc;
424 
425     }
426 
427     /**
428      * Get the UTC time scale used to create this TLE.
429      *
430      * @return UTC time scale.
431      */
432     TimeScale getUtc() {
433         return utc;
434     }
435 
436     /** Get the first line.
437      * @return first line
438      */
439     public String getLine1() {
440         if (line1 == null) {
441             buildLine1();
442         }
443         return line1;
444     }
445 
446     /** Get the second line.
447      * @return second line
448      */
449     public String getLine2() {
450         if (line2 == null) {
451             buildLine2();
452         }
453         return line2;
454     }
455 
456     /** Build the line 1 from the parsed elements.
457      */
458     private void buildLine1() {
459 
460         final StringBuffer buffer = new StringBuffer();
461 
462         buffer.append('1');
463 
464         buffer.append(' ');
465         buffer.append(ParseUtils.buildSatelliteNumber(satelliteNumber, "satelliteNumber-1"));
466         buffer.append(classification);
467 
468         buffer.append(' ');
469         buffer.append(ParseUtils.addPadding("launchYear",   launchYear % 100, '0', 2, true, satelliteNumber));
470         buffer.append(ParseUtils.addPadding("launchNumber", launchNumber, '0', 3, true, satelliteNumber));
471         buffer.append(ParseUtils.addPadding("launchPiece",  launchPiece, ' ', 3, false, satelliteNumber));
472 
473         buffer.append(' ');
474         final DateTimeComponents dtc = epoch.getComponents(utc);
475         buffer.append(ParseUtils.addPadding("year", dtc.getDate().getYear() % 100, '0', 2, true, satelliteNumber));
476         buffer.append(ParseUtils.addPadding("day",  dtc.getDate().getDayOfYear(),  '0', 3, true, satelliteNumber));
477         buffer.append('.');
478         // nota: 31250/27 == 100000000/86400
479         final int fraction = (int) FastMath.rint(31250 * dtc.getTime().getSecondsInUTCDay() / 27.0);
480         buffer.append(ParseUtils.addPadding("fraction", fraction,  '0', 8, true, satelliteNumber));
481 
482         buffer.append(' ');
483         final double n1 = meanMotionFirstDerivative.divide(pa.getPi()).multiply(1.86624e9).getReal();
484         final String sn1 = ParseUtils.addPadding("meanMotionFirstDerivative",
485                                                  new DecimalFormat(".00000000", SYMBOLS).format(n1),
486                                                  ' ', 10, true, satelliteNumber);
487         buffer.append(sn1);
488 
489         buffer.append(' ');
490         final double n2 = meanMotionSecondDerivative.divide(pa.getPi()).multiply(5.3747712e13).getReal();
491         buffer.append(formatExponentMarkerFree("meanMotionSecondDerivative", n2, 5, ' ', 8, true));
492 
493         buffer.append(' ');
494         buffer.append(formatExponentMarkerFree("B*", getBStar(), 5, ' ', 8, true));
495 
496         buffer.append(' ');
497         buffer.append(ephemerisType);
498 
499         buffer.append(' ');
500         buffer.append(ParseUtils.addPadding("elementNumber", elementNumber, ' ', 4, true, satelliteNumber));
501 
502         buffer.append(Integer.toString(checksum(buffer)));
503 
504         line1 = buffer.toString();
505 
506     }
507 
508     /** Format a real number without 'e' exponent marker.
509      * @param name parameter name
510      * @param d number to format
511      * @param mantissaSize size of the mantissa (not counting initial '-' or ' ' for sign)
512      * @param c padding character
513      * @param size desired size
514      * @param rightJustified if true, the resulting string is
515      * right justified (i.e. space are added to the left)
516      * @return formatted and padded number
517      */
518     private String formatExponentMarkerFree(final String name, final double d, final int mantissaSize,
519                                             final char c, final int size, final boolean rightJustified) {
520         final double dAbs = FastMath.abs(d);
521         int exponent = (dAbs < 1.0e-9) ? -9 : (int) FastMath.ceil(FastMath.log10(dAbs));
522         long mantissa = FastMath.round(dAbs * FastMath.pow(10.0, mantissaSize - exponent));
523         if (mantissa == 0) {
524             exponent = 0;
525         } else if (mantissa > (ArithmeticUtils.pow(10, mantissaSize) - 1)) {
526             // rare case: if d has a single digit like d = 1.0e-4 with mantissaSize = 5
527             // the above computation finds exponent = -4 and mantissa = 100000 which
528             // doesn't fit in a 5 digits string
529             exponent++;
530             mantissa = FastMath.round(dAbs * FastMath.pow(10.0, mantissaSize - exponent));
531         }
532         final String sMantissa = ParseUtils.addPadding(name, (int) mantissa,
533                                                        '0', mantissaSize, true, satelliteNumber);
534         final String sExponent = Integer.toString(FastMath.abs(exponent));
535         final String formatted = (d <  0 ? '-' : ' ') + sMantissa + (exponent <= 0 ? '-' : '+') + sExponent;
536 
537         return ParseUtils.addPadding(name, formatted, c, size, rightJustified, satelliteNumber);
538 
539     }
540 
541     /** Build the line 2 from the parsed elements.
542      */
543     private void buildLine2() {
544 
545         final StringBuffer buffer = new StringBuffer();
546         final DecimalFormat f34   = new DecimalFormat("##0.0000", SYMBOLS);
547         final DecimalFormat f211  = new DecimalFormat("#0.00000000", SYMBOLS);
548 
549         buffer.append('2');
550 
551         buffer.append(' ');
552         buffer.append(ParseUtils.buildSatelliteNumber(satelliteNumber, "satelliteNumber-2"));
553 
554         buffer.append(' ');
555         buffer.append(ParseUtils.addPadding(INCLINATION, f34.format(FastMath.toDegrees(inclination).getReal()), ' ', 8, true, satelliteNumber));
556         buffer.append(' ');
557         buffer.append(ParseUtils.addPadding("raan", f34.format(FastMath.toDegrees(raan).getReal()), ' ', 8, true, satelliteNumber));
558         buffer.append(' ');
559         buffer.append(ParseUtils.addPadding(ECCENTRICITY, (int) FastMath.rint(eccentricity.getReal() * 1.0e7), '0', 7, true, satelliteNumber));
560         buffer.append(' ');
561         buffer.append(ParseUtils.addPadding("pa", f34.format(FastMath.toDegrees(pa).getReal()), ' ', 8, true, satelliteNumber));
562         buffer.append(' ');
563         buffer.append(ParseUtils.addPadding("meanAnomaly", f34.format(FastMath.toDegrees(meanAnomaly).getReal()), ' ', 8, true, satelliteNumber));
564 
565         buffer.append(' ');
566         buffer.append(ParseUtils.addPadding(MEAN_MOTION, f211.format(meanMotion.divide(pa.getPi()).multiply(43200.0).getReal()), ' ', 11, true, satelliteNumber));
567         buffer.append(ParseUtils.addPadding("revolutionNumberAtEpoch", revolutionNumberAtEpoch,
568                                             ' ', 5, true, satelliteNumber));
569 
570         buffer.append(Integer.toString(checksum(buffer)));
571 
572         line2 = buffer.toString();
573 
574     }
575 
576     /** Get the drivers for TLE propagation SGP4 and SDP4.
577      * @return drivers for SGP4 and SDP4 model parameters
578      */
579     public List<ParameterDriver> getParametersDrivers() {
580         return Collections.singletonList(bStarParameterDriver);
581     }
582 
583     /** Get model parameters.
584      * @param field field to which the elements belong
585      * @return model parameters
586      */
587     public T[] getParameters(final Field<T> field) {
588         final List<ParameterDriver> drivers = getParametersDrivers();
589         final T[] parameters = MathArrays.buildArray(field, drivers.size());
590         int i = 0;
591         for (ParameterDriver driver : drivers) {
592             parameters[i++] = field.getZero().add(driver.getValue());
593         }
594         return parameters;
595     }
596 
597     /** Get the satellite id.
598      * @return the satellite number
599      */
600     public int getSatelliteNumber() {
601         return satelliteNumber;
602     }
603 
604     /** Get the classification.
605      * @return classification
606      */
607     public char getClassification() {
608         return classification;
609     }
610 
611     /** Get the launch year.
612      * @return the launch year
613      */
614     public int getLaunchYear() {
615         return launchYear;
616     }
617 
618     /** Get the launch number.
619      * @return the launch number
620      */
621     public int getLaunchNumber() {
622         return launchNumber;
623     }
624 
625     /** Get the launch piece.
626      * @return the launch piece
627      */
628     public String getLaunchPiece() {
629         return launchPiece;
630     }
631 
632     /** Get the type of ephemeris.
633      * @return the ephemeris type (one of {@link #DEFAULT}, {@link #SGP},
634      * {@link #SGP4}, {@link #SGP8}, {@link #SDP4}, {@link #SDP8})
635      */
636     public int getEphemerisType() {
637         return ephemerisType;
638     }
639 
640     /** Get the element number.
641      * @return the element number
642      */
643     public int getElementNumber() {
644         return elementNumber;
645     }
646 
647     /** Get the TLE current date.
648      * @return the epoch
649      */
650     public FieldAbsoluteDate<T> getDate() {
651         return epoch;
652     }
653 
654     /** Get the mean motion.
655      * @return the mean motion (rad/s)
656      */
657     public T getMeanMotion() {
658         return meanMotion;
659     }
660 
661     /** Get the mean motion first derivative.
662      * @return the mean motion first derivative (rad/s²)
663      */
664     public T getMeanMotionFirstDerivative() {
665         return meanMotionFirstDerivative;
666     }
667 
668     /** Get the mean motion second derivative.
669      * @return the mean motion second derivative (rad/s³)
670      */
671     public T getMeanMotionSecondDerivative() {
672         return meanMotionSecondDerivative;
673     }
674 
675     /** Get the eccentricity.
676      * @return the eccentricity
677      */
678     public T getE() {
679         return eccentricity;
680     }
681 
682     /** Get the inclination.
683      * @return the inclination (rad)
684      */
685     public T getI() {
686         return inclination;
687     }
688 
689     /** Get the argument of perigee.
690      * @return omega (rad)
691      */
692     public T getPerigeeArgument() {
693         return pa;
694     }
695 
696     /** Get Right Ascension of the Ascending node.
697      * @return the raan (rad)
698      */
699     public T getRaan() {
700         return raan;
701     }
702 
703     /** Get the mean anomaly.
704      * @return the mean anomaly (rad)
705      */
706     public T getMeanAnomaly() {
707         return meanAnomaly;
708     }
709 
710     /** Get the revolution number.
711      * @return the revolutionNumberAtEpoch
712      */
713     public int getRevolutionNumberAtEpoch() {
714         return revolutionNumberAtEpoch;
715     }
716 
717     /** Get the ballistic coefficient.
718      * @return bStar
719      */
720     public double getBStar() {
721         return bStarParameterDriver.getValue();
722     }
723 
724     /** Get a string representation of this TLE set.
725      * <p>The representation is simply the two lines separated by the
726      * platform line separator.</p>
727      * @return string representation of this TLE set
728      */
729     public String toString() {
730         try {
731             return getLine1() + System.getProperty("line.separator") + getLine2();
732         } catch (OrekitException oe) {
733             throw new OrekitInternalError(oe);
734         }
735     }
736 
737     /**
738      * Convert Spacecraft State into TLE.
739      * This converter uses Newton method to reverse SGP4 and SDP4 propagation algorithm
740      * and generates a usable TLE version of a state.
741      * New TLE epoch is state epoch.
742      *
743      * <p>
744      * This method uses the {@link DataContext#getDefault() default data context},
745      * as well as {@link #EPSILON_DEFAULT} and {@link #MAX_ITERATIONS_DEFAULT} for method convergence.
746      *
747      * @param state Spacecraft State to convert into TLE
748      * @param templateTLE first guess used to get identification and estimate new TLE
749      * @param <T> type of the element
750      * @return TLE matching with Spacecraft State and template identification
751      * @see #stateToTLE(FieldSpacecraftState, FieldTLE, TimeScale, Frame)
752      * @see #stateToTLE(FieldSpacecraftState, FieldTLE, TimeScale, Frame, double, int)
753      * @since 11.0
754      */
755     @DefaultDataContext
756     public static <T extends CalculusFieldElement<T>> FieldTLE<T> stateToTLE(final FieldSpacecraftState<T> state, final FieldTLE<T> templateTLE) {
757         return stateToTLE(state, templateTLE,
758                           DataContext.getDefault().getTimeScales().getUTC(),
759                           DataContext.getDefault().getFrames().getTEME());
760     }
761 
762     /**
763      * Convert Spacecraft State into TLE.
764      * This converter uses Newton method to reverse SGP4 and SDP4 propagation algorithm
765      * and generates a usable TLE version of a state.
766      * New TLE epoch is state epoch.
767      *
768      * <p>
769      * This method uses {@link #EPSILON_DEFAULT} and {@link #MAX_ITERATIONS_DEFAULT}
770      * for method convergence.
771      *
772      * @param state Spacecraft State to convert into TLE
773      * @param templateTLE first guess used to get identification and estimate new TLE
774      * @param utc the UTC time scale
775      * @param teme the TEME frame to use for propagation
776      * @param <T> type of the element
777      * @return TLE matching with Spacecraft State and template identification
778      * @see #stateToTLE(FieldSpacecraftState, FieldTLE, TimeScale, Frame, double, int)
779      * @since 11.0
780      */
781     public static <T extends CalculusFieldElement<T>> FieldTLE<T> stateToTLE(final FieldSpacecraftState<T> state, final FieldTLE<T> templateTLE,
782                                                                              final TimeScale utc, final Frame teme) {
783         return stateToTLE(state, templateTLE, utc, teme, EPSILON_DEFAULT, MAX_ITERATIONS_DEFAULT);
784     }
785 
786     /**
787      * Convert Spacecraft State into TLE.
788      * This converter uses Newton method to reverse SGP4 and SDP4 propagation algorithm
789      * and generates a usable TLE version of a state.
790      * New TLE epoch is state epoch.
791      *
792      * @param state Spacecraft State to convert into TLE
793      * @param templateTLE first guess used to get identification and estimate new TLE
794      * @param utc the UTC time scale
795      * @param teme the TEME frame to use for propagation
796      * @param epsilon used to compute threshold for convergence check
797      * @param maxIterations maximum number of iterations for convergence
798      * @param <T> type of the element
799      * @return TLE matching with Spacecraft State and template identification
800      * @since 11.0
801      */
802     public static <T extends CalculusFieldElement<T>> FieldTLE<T> stateToTLE(final FieldSpacecraftState<T> state, final FieldTLE<T> templateTLE,
803                                                                              final TimeScale utc, final Frame teme,
804                                                                              final double epsilon, final int maxIterations) {
805 
806         // Gets equinoctial parameters in TEME frame from state
807         final FieldEquinoctialOrbit<T> equiOrbit = convert(state.getOrbit(), teme);
808         T sma = equiOrbit.getA();
809         T ex  = equiOrbit.getEquinoctialEx();
810         T ey  = equiOrbit.getEquinoctialEy();
811         T hx  = equiOrbit.getHx();
812         T hy  = equiOrbit.getHy();
813         T lv  = equiOrbit.getLv();
814 
815         // Rough initialization of the TLE
816         final FieldKeplerianOrbit<T> keplerianOrbit = (FieldKeplerianOrbit<T>) OrbitType.KEPLERIAN.convertType(equiOrbit);
817         FieldTLE<T> current = newTLE(keplerianOrbit, templateTLE, utc);
818 
819         // Field
820         final Field<T> field = state.getDate().getField();
821 
822         // threshold for each parameter
823         final T thrA = sma.add(1).multiply(epsilon);
824         final T thrE = FastMath.hypot(ex, ey).add(1).multiply(epsilon);
825         final T thrH = FastMath.hypot(hx, hy).add(1).multiply(epsilon);
826         final T thrV = sma.getPi().multiply(epsilon);
827 
828         int k = 0;
829         while (k++ < maxIterations) {
830 
831             // recompute the state from the current TLE
832             final FieldTLEPropagator<T> propagator = FieldTLEPropagator.selectExtrapolator(current, new InertialProvider(Rotation.IDENTITY, teme), state.getMass(), teme, templateTLE.getParameters(field));
833             final FieldOrbit<T> recovOrbit = propagator.getInitialState().getOrbit();
834             final FieldEquinoctialOrbit<T> recovEquiOrbit = (FieldEquinoctialOrbit<T>) OrbitType.EQUINOCTIAL.convertType(recovOrbit);
835 
836             // adapted parameters residuals
837             final T deltaSma = equiOrbit.getA().subtract(recovEquiOrbit.getA());
838             final T deltaEx  = equiOrbit.getEquinoctialEx().subtract(recovEquiOrbit.getEquinoctialEx());
839             final T deltaEy  = equiOrbit.getEquinoctialEy().subtract(recovEquiOrbit.getEquinoctialEy());
840             final T deltaHx  = equiOrbit.getHx().subtract(recovEquiOrbit.getHx());
841             final T deltaHy  = equiOrbit.getHy().subtract(recovEquiOrbit.getHy());
842             final T deltaLv  = MathUtils.normalizeAngle(equiOrbit.getLv().subtract(recovEquiOrbit.getLv()), field.getZero());
843 
844             // check convergence
845             if (FastMath.abs(deltaSma.getReal()) < thrA.getReal() &&
846                 FastMath.abs(deltaEx.getReal())  < thrE.getReal() &&
847                 FastMath.abs(deltaEy.getReal())  < thrE.getReal() &&
848                 FastMath.abs(deltaHx.getReal())  < thrH.getReal() &&
849                 FastMath.abs(deltaHy.getReal())  < thrH.getReal() &&
850                 FastMath.abs(deltaLv.getReal())  < thrV.getReal()) {
851 
852                 return current;
853             }
854 
855             // update state
856             sma = sma.add(deltaSma);
857             ex  = ex.add(deltaEx);
858             ey  = ey.add(deltaEy);
859             hx  = hx.add(deltaHx);
860             hy  = hy.add(deltaHy);
861             lv  = lv.add(deltaLv);
862             final FieldEquinoctialOrbit<T> newEquiOrbit =
863                                     new FieldEquinoctialOrbit<>(sma, ex, ey, hx, hy, lv, PositionAngle.TRUE,
864                                     equiOrbit.getFrame(), equiOrbit.getDate(), equiOrbit.getMu());
865             final FieldKeplerianOrbit<T> newKeplOrbit = (FieldKeplerianOrbit<T>) OrbitType.KEPLERIAN.convertType(newEquiOrbit);
866 
867             // update TLE
868             current = newTLE(newKeplOrbit, templateTLE, utc);
869         }
870 
871         throw new OrekitException(OrekitMessages.UNABLE_TO_COMPUTE_TLE, k);
872     }
873 
874     /**
875      * Converts an orbit into an equinoctial orbit expressed in TEME frame.
876      *
877      * @param orbitIn the orbit to convert
878      * @param teme the TEME frame to use for propagation
879      * @param <T> type of the element
880      * @return the converted orbit, i.e. equinoctial in TEME frame
881      */
882     private static <T extends CalculusFieldElement<T>> FieldEquinoctialOrbit<T> convert(final FieldOrbit<T> orbitIn, final Frame teme) {
883         return new FieldEquinoctialOrbit<T>(orbitIn.getPVCoordinates(teme), teme, orbitIn.getMu());
884     }
885 
886     /**
887      * Builds a new TLE from Keplerian parameters and a template for TLE data.
888      * @param keplerianOrbit the Keplerian parameters to build the TLE from
889      * @param templateTLE TLE used to get object identification
890      * @param utc the UTC time scale
891      * @param <T> type of the element
892      * @return TLE with template identification and new orbital parameters
893      */
894     private static <T extends CalculusFieldElement<T>> FieldTLE<T> newTLE(final FieldKeplerianOrbit<T> keplerianOrbit, final FieldTLE<T> templateTLE,
895                                                                           final TimeScale utc) {
896         // Keplerian parameters
897         final T meanMotion  = keplerianOrbit.getKeplerianMeanMotion();
898         final T e           = keplerianOrbit.getE();
899         final T i           = keplerianOrbit.getI();
900         final T raan        = keplerianOrbit.getRightAscensionOfAscendingNode();
901         final T pa          = keplerianOrbit.getPerigeeArgument();
902         final T meanAnomaly = keplerianOrbit.getMeanAnomaly();
903         // TLE epoch is state epoch
904         final FieldAbsoluteDate<T> epoch = keplerianOrbit.getDate();
905         // Identification
906         final int satelliteNumber = templateTLE.getSatelliteNumber();
907         final char classification = templateTLE.getClassification();
908         final int launchYear = templateTLE.getLaunchYear();
909         final int launchNumber = templateTLE.getLaunchNumber();
910         final String launchPiece = templateTLE.getLaunchPiece();
911         final int ephemerisType = templateTLE.getEphemerisType();
912         final int elementNumber = templateTLE.getElementNumber();
913         // Updates revolutionNumberAtEpoch
914         final int revolutionNumberAtEpoch = templateTLE.getRevolutionNumberAtEpoch();
915         final T dt = epoch.durationFrom(templateTLE.getDate());
916         final int newRevolutionNumberAtEpoch = (int) ((int) revolutionNumberAtEpoch + FastMath.floor(MathUtils.normalizeAngle(meanAnomaly, e.getPi()).add(dt.multiply(meanMotion)).divide(e.getPi().multiply(2.0))).getReal());
917         // Gets B*
918         final double bStar = templateTLE.getBStar();
919         // Gets Mean Motion derivatives
920         final T meanMotionFirstDerivative = templateTLE.getMeanMotionFirstDerivative();
921         final T meanMotionSecondDerivative = templateTLE.getMeanMotionSecondDerivative();
922         // Returns the new TLE
923         return new FieldTLE<>(satelliteNumber, classification, launchYear, launchNumber, launchPiece, ephemerisType,
924                        elementNumber, epoch, meanMotion, meanMotionFirstDerivative, meanMotionSecondDerivative,
925                        e, i, pa, raan, meanAnomaly, newRevolutionNumberAtEpoch, bStar, utc);
926     }
927 
928 
929     /** Check the lines format validity.
930      * @param line1 the first element
931      * @param line2 the second element
932      * @return true if format is recognized (non null lines, 69 characters length,
933      * line content), false if not
934      */
935     public static boolean isFormatOK(final String line1, final String line2) {
936         return TLE.isFormatOK(line1, line2);
937     }
938 
939     /** Compute the checksum of the first 68 characters of a line.
940      * @param line line to check
941      * @return checksum
942      */
943     private static int checksum(final CharSequence line) {
944         int sum = 0;
945         for (int j = 0; j < 68; j++) {
946             final char c = line.charAt(j);
947             if (Character.isDigit(c)) {
948                 sum += Character.digit(c, 10);
949             } else if (c == '-') {
950                 ++sum;
951             }
952         }
953         return sum % 10;
954     }
955 
956     /**
957      * Convert FieldTLE into TLE.
958      * @return TLE
959      */
960     public TLE toTLE() {
961         final TLE regularTLE = new TLE(getSatelliteNumber(), getClassification(), getLaunchYear(), getLaunchNumber(), getLaunchPiece(), getEphemerisType(),
962                                        getElementNumber(), getDate().toAbsoluteDate(), getMeanMotion().getReal(), getMeanMotionFirstDerivative().getReal(),
963                                        getMeanMotionSecondDerivative().getReal(), getE().getReal(), getI().getReal(), getPerigeeArgument().getReal(),
964                                        getRaan().getReal(), getMeanAnomaly().getReal(), getRevolutionNumberAtEpoch(), getBStar(), getUtc());
965 
966         for (int k = 0; k < regularTLE.getParametersDrivers().size(); ++k) {
967             regularTLE.getParametersDrivers().get(k).setSelected(getParametersDrivers().get(k).isSelected());
968         }
969 
970         return regularTLE;
971 
972     }
973 
974     /** Check if this tle equals the provided tle.
975      * <p>Due to the difference in precision between object and string
976      * representations of TLE, it is possible for this method to return false
977      * even if string representations returned by {@link #toString()}
978      * are equal.</p>
979      * @param o other tle
980      * @return true if this tle equals the provided tle
981      */
982     @Override
983     public boolean equals(final Object o) {
984         if (o == this) {
985             return true;
986         }
987         if (!(o instanceof FieldTLE)) {
988             return false;
989         }
990         @SuppressWarnings("unchecked")
991         final FieldTLE<T> tle = (FieldTLE<T>) o;
992         return satelliteNumber == tle.satelliteNumber &&
993                 classification == tle.classification &&
994                 launchYear == tle.launchYear &&
995                 launchNumber == tle.launchNumber &&
996                 Objects.equals(launchPiece, tle.launchPiece) &&
997                 ephemerisType == tle.ephemerisType &&
998                 elementNumber == tle.elementNumber &&
999                 Objects.equals(epoch, tle.epoch) &&
1000                 meanMotion.getReal() == tle.meanMotion.getReal() &&
1001                 meanMotionFirstDerivative.getReal() == tle.meanMotionFirstDerivative.getReal() &&
1002                 meanMotionSecondDerivative.getReal() == tle.meanMotionSecondDerivative.getReal() &&
1003                 eccentricity.getReal() == tle.eccentricity.getReal() &&
1004                 inclination.getReal() == tle.inclination.getReal() &&
1005                 pa.getReal() == tle.pa.getReal() &&
1006                 raan.getReal() == tle.raan.getReal() &&
1007                 meanAnomaly.getReal() == tle.meanAnomaly.getReal() &&
1008                 revolutionNumberAtEpoch == tle.revolutionNumberAtEpoch &&
1009                 getBStar() == tle.getBStar();
1010     }
1011 
1012     /** Get a hashcode for this tle.
1013      * @return hashcode
1014      */
1015     @Override
1016     public int hashCode() {
1017         return Objects.hash(satelliteNumber,
1018                 classification,
1019                 launchYear,
1020                 launchNumber,
1021                 launchPiece,
1022                 ephemerisType,
1023                 elementNumber,
1024                 epoch,
1025                 meanMotion,
1026                 meanMotionFirstDerivative,
1027                 meanMotionSecondDerivative,
1028                 eccentricity,
1029                 inclination,
1030                 pa,
1031                 raan,
1032                 meanAnomaly,
1033                 revolutionNumberAtEpoch,
1034                 getBStar());
1035     }
1036 
1037 }