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