TLE.java

  1. /* Copyright 2002-2022 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. import java.io.Serializable;
  19. import java.text.DecimalFormat;
  20. import java.text.DecimalFormatSymbols;
  21. import java.util.Collections;
  22. import java.util.List;
  23. import java.util.Locale;
  24. import java.util.Objects;
  25. import java.util.regex.Pattern;

  26. import org.hipparchus.geometry.euclidean.threed.Rotation;
  27. import org.hipparchus.util.ArithmeticUtils;
  28. import org.hipparchus.util.FastMath;
  29. import org.hipparchus.util.MathUtils;
  30. import org.orekit.annotation.DefaultDataContext;
  31. import org.orekit.attitudes.InertialProvider;
  32. import org.orekit.data.DataContext;
  33. import org.orekit.errors.OrekitException;
  34. import org.orekit.errors.OrekitMessages;
  35. import org.orekit.frames.Frame;
  36. import org.orekit.orbits.EquinoctialOrbit;
  37. import org.orekit.orbits.KeplerianOrbit;
  38. import org.orekit.orbits.Orbit;
  39. import org.orekit.orbits.OrbitType;
  40. import org.orekit.orbits.PositionAngle;
  41. import org.orekit.propagation.SpacecraftState;
  42. import org.orekit.time.AbsoluteDate;
  43. import org.orekit.time.DateComponents;
  44. import org.orekit.time.DateTimeComponents;
  45. import org.orekit.time.TimeComponents;
  46. import org.orekit.time.TimeScale;
  47. import org.orekit.time.TimeStamped;
  48. import org.orekit.utils.ParameterDriver;

  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.  */
  65. public class TLE implements TimeStamped, Serializable {

  66.     /** Identifier for SGP type of ephemeris. */
  67.     public static final int SGP = 1;

  68.     /** Identifier for SGP4 type of ephemeris. */
  69.     public static final int SGP4 = 2;

  70.     /** Identifier for SDP4 type of ephemeris. */
  71.     public static final int SDP4 = 3;

  72.     /** Identifier for SGP8 type of ephemeris. */
  73.     public static final int SGP8 = 4;

  74.     /** Identifier for SDP8 type of ephemeris. */
  75.     public static final int SDP8 = 5;

  76.     /** Identifier for default type of ephemeris (SGP4/SDP4). */
  77.     public static final int DEFAULT = 0;

  78.     /** Parameter name for B* coefficient. */
  79.     public static final String B_STAR = "BSTAR";

  80.     /** Default value for epsilon. */
  81.     private static final double EPSILON_DEFAULT = 1.0e-10;

  82.     /** Default value for maxIterations. */
  83.     private static final int MAX_ITERATIONS_DEFAULT = 100;

  84.     /** B* scaling factor.
  85.      * <p>
  86.      * We use a power of 2 to avoid numeric noise introduction
  87.      * in the multiplications/divisions sequences.
  88.      * </p>
  89.      */
  90.     private static final double B_STAR_SCALE = FastMath.scalb(1.0, -20);

  91.     /** Name of the mean motion parameter. */
  92.     private static final String MEAN_MOTION = "meanMotion";

  93.     /** Name of the inclination parameter. */
  94.     private static final String INCLINATION = "inclination";

  95.     /** Name of the eccentricity parameter. */
  96.     private static final String ECCENTRICITY = "eccentricity";

  97.     /** Pattern for line 1. */
  98.     private static final Pattern LINE_1_PATTERN =
  99.         Pattern.compile("1 [ 0-9A-Z&&[^IO]][ 0-9]{4}[A-Z] [ 0-9]{5}[ A-Z]{3} [ 0-9]{5}[.][ 0-9]{8} (?:(?:[ 0+-][.][ 0-9]{8})|(?: [ +-][.][ 0-9]{7})) " +
  100.                         "[ +-][ 0-9]{5}[+-][ 0-9] [ +-][ 0-9]{5}[+-][ 0-9] [ 0-9] [ 0-9]{4}[ 0-9]");

  101.     /** Pattern for line 2. */
  102.     private static final Pattern LINE_2_PATTERN =
  103.         Pattern.compile("2 [ 0-9A-Z&&[^IO]][ 0-9]{4} [ 0-9]{3}[.][ 0-9]{4} [ 0-9]{3}[.][ 0-9]{4} [ 0-9]{7} " +
  104.                         "[ 0-9]{3}[.][ 0-9]{4} [ 0-9]{3}[.][ 0-9]{4} [ 0-9]{2}[.][ 0-9]{13}[ 0-9]");

  105.     /** International symbols for parsing. */
  106.     private static final DecimalFormatSymbols SYMBOLS =
  107.         new DecimalFormatSymbols(Locale.US);

  108.     /** Serializable UID. */
  109.     private static final long serialVersionUID = -1596648022319057689L;

  110.     /** The satellite number. */
  111.     private final int satelliteNumber;

  112.     /** Classification (U for unclassified). */
  113.     private final char classification;

  114.     /** Launch year. */
  115.     private final int launchYear;

  116.     /** Launch number. */
  117.     private final int launchNumber;

  118.     /** Piece of launch (from "A" to "ZZZ"). */
  119.     private final String launchPiece;

  120.     /** Type of ephemeris. */
  121.     private final int ephemerisType;

  122.     /** Element number. */
  123.     private final int elementNumber;

  124.     /** the TLE current date. */
  125.     private final AbsoluteDate epoch;

  126.     /** Mean motion (rad/s). */
  127.     private final double meanMotion;

  128.     /** Mean motion first derivative (rad/s²). */
  129.     private final double meanMotionFirstDerivative;

  130.     /** Mean motion second derivative (rad/s³). */
  131.     private final double meanMotionSecondDerivative;

  132.     /** Eccentricity. */
  133.     private final double eccentricity;

  134.     /** Inclination (rad). */
  135.     private final double inclination;

  136.     /** Argument of perigee (rad). */
  137.     private final double pa;

  138.     /** Right Ascension of the Ascending node (rad). */
  139.     private final double raan;

  140.     /** Mean anomaly (rad). */
  141.     private final double meanAnomaly;

  142.     /** Revolution number at epoch. */
  143.     private final int revolutionNumberAtEpoch;

  144.     /** First line. */
  145.     private String line1;

  146.     /** Second line. */
  147.     private String line2;

  148.     /** The UTC scale. */
  149.     private final TimeScale utc;

  150.     /** Driver for ballistic coefficient parameter. */
  151.     private final transient ParameterDriver bStarParameterDriver;


  152.     /** Simple constructor from unparsed two lines. This constructor uses the {@link
  153.      * DataContext#getDefault() default data context}.
  154.      *
  155.      * <p>The static method {@link #isFormatOK(String, String)} should be called
  156.      * before trying to build this object.<p>
  157.      * @param line1 the first element (69 char String)
  158.      * @param line2 the second element (69 char String)
  159.      * @see #TLE(String, String, TimeScale)
  160.      */
  161.     @DefaultDataContext
  162.     public TLE(final String line1, final String line2) {
  163.         this(line1, line2, DataContext.getDefault().getTimeScales().getUTC());
  164.     }

  165.     /** Simple constructor from unparsed two lines using the given time scale as UTC.
  166.      *
  167.      * <p>The static method {@link #isFormatOK(String, String)} should be called
  168.      * before trying to build this object.<p>
  169.      * @param line1 the first element (69 char String)
  170.      * @param line2 the second element (69 char String)
  171.      * @param utc the UTC time scale.
  172.      * @since 10.1
  173.      */
  174.     public TLE(final String line1, final String line2, final TimeScale utc) {

  175.         // identification
  176.         satelliteNumber = ParseUtils.parseSatelliteNumber(line1, 2, 5);
  177.         final int satNum2 = ParseUtils.parseSatelliteNumber(line2, 2, 5);
  178.         if (satelliteNumber != satNum2) {
  179.             throw new OrekitException(OrekitMessages.TLE_LINES_DO_NOT_REFER_TO_SAME_OBJECT,
  180.                                       line1, line2);
  181.         }
  182.         classification  = line1.charAt(7);
  183.         launchYear      = ParseUtils.parseYear(line1, 9);
  184.         launchNumber    = ParseUtils.parseInteger(line1, 11, 3);
  185.         launchPiece     = line1.substring(14, 17).trim();
  186.         ephemerisType   = ParseUtils.parseInteger(line1, 62, 1);
  187.         elementNumber   = ParseUtils.parseInteger(line1, 64, 4);

  188.         // Date format transform (nota: 27/31250 == 86400/100000000)
  189.         final int    year      = ParseUtils.parseYear(line1, 18);
  190.         final int    dayInYear = ParseUtils.parseInteger(line1, 20, 3);
  191.         final long   df        = 27l * ParseUtils.parseInteger(line1, 24, 8);
  192.         final int    secondsA  = (int) (df / 31250l);
  193.         final double secondsB  = (df % 31250l) / 31250.0;
  194.         epoch = new AbsoluteDate(new DateComponents(year, dayInYear),
  195.                                  new TimeComponents(secondsA, secondsB),
  196.                                  utc);

  197.         // mean motion development
  198.         // converted from rev/day, 2 * rev/day^2 and 6 * rev/day^3 to rad/s, rad/s^2 and rad/s^3
  199.         meanMotion                 = ParseUtils.parseDouble(line2, 52, 11) * FastMath.PI / 43200.0;
  200.         meanMotionFirstDerivative  = ParseUtils.parseDouble(line1, 33, 10) * FastMath.PI / 1.86624e9;
  201.         meanMotionSecondDerivative = Double.parseDouble((line1.substring(44, 45) + '.' +
  202.                                                          line1.substring(45, 50) + 'e' +
  203.                                                          line1.substring(50, 52)).replace(' ', '0')) *
  204.                                      FastMath.PI / 5.3747712e13;

  205.         eccentricity = Double.parseDouble("." + line2.substring(26, 33).replace(' ', '0'));
  206.         inclination  = FastMath.toRadians(ParseUtils.parseDouble(line2, 8, 8));
  207.         pa           = FastMath.toRadians(ParseUtils.parseDouble(line2, 34, 8));
  208.         raan         = FastMath.toRadians(Double.parseDouble(line2.substring(17, 25).replace(' ', '0')));
  209.         meanAnomaly  = FastMath.toRadians(ParseUtils.parseDouble(line2, 43, 8));

  210.         revolutionNumberAtEpoch = ParseUtils.parseInteger(line2, 63, 5);
  211.         final double bStarValue = Double.parseDouble((line1.substring(53, 54) + '.' +
  212.                                     line1.substring(54, 59) + 'e' +
  213.                                     line1.substring(59, 61)).replace(' ', '0'));

  214.         // save the lines
  215.         this.line1 = line1;
  216.         this.line2 = line2;
  217.         this.utc = utc;

  218.         // create model parameter drivers
  219.         this.bStarParameterDriver = new ParameterDriver(B_STAR, bStarValue, B_STAR_SCALE,
  220.                                                         Double.NEGATIVE_INFINITY,
  221.                                                         Double.POSITIVE_INFINITY);

  222.     }

  223.     /**
  224.      * <p>
  225.      * Simple constructor from already parsed elements. This constructor uses the
  226.      * {@link DataContext#getDefault() default data context}.
  227.      * </p>
  228.      *
  229.      * <p>
  230.      * The mean anomaly, the right ascension of ascending node Ω and the argument of
  231.      * perigee ω are normalized into the [0, 2π] interval as they can be negative.
  232.      * After that, a range check is performed on some of the orbital elements:
  233.      *
  234.      * <pre>
  235.      *     meanMotion &gt;= 0
  236.      *     0 &lt;= i &lt;= π
  237.      *     0 &lt;= Ω &lt;= 2π
  238.      *     0 &lt;= e &lt;= 1
  239.      *     0 &lt;= ω &lt;= 2π
  240.      *     0 &lt;= meanAnomaly &lt;= 2π
  241.      * </pre>
  242.      *
  243.      * @param satelliteNumber satellite number
  244.      * @param classification classification (U for unclassified)
  245.      * @param launchYear launch year (all digits)
  246.      * @param launchNumber launch number
  247.      * @param launchPiece launch piece (3 char String)
  248.      * @param ephemerisType type of ephemeris
  249.      * @param elementNumber element number
  250.      * @param epoch elements epoch
  251.      * @param meanMotion mean motion (rad/s)
  252.      * @param meanMotionFirstDerivative mean motion first derivative (rad/s²)
  253.      * @param meanMotionSecondDerivative mean motion second derivative (rad/s³)
  254.      * @param e eccentricity
  255.      * @param i inclination (rad)
  256.      * @param pa argument of perigee (rad)
  257.      * @param raan right ascension of ascending node (rad)
  258.      * @param meanAnomaly mean anomaly (rad)
  259.      * @param revolutionNumberAtEpoch revolution number at epoch
  260.      * @param bStar ballistic coefficient
  261.      * @see #TLE(int, char, int, int, String, int, int, AbsoluteDate, double, double,
  262.      * double, double, double, double, double, double, int, double, TimeScale)
  263.      */
  264.     @DefaultDataContext
  265.     public TLE(final int satelliteNumber, final char classification,
  266.                final int launchYear, final int launchNumber, final String launchPiece,
  267.                final int ephemerisType, final int elementNumber, final AbsoluteDate epoch,
  268.                final double meanMotion, final double meanMotionFirstDerivative,
  269.                final double meanMotionSecondDerivative, final double e, final double i,
  270.                final double pa, final double raan, final double meanAnomaly,
  271.                final int revolutionNumberAtEpoch, final double bStar) {
  272.         this(satelliteNumber, classification, launchYear, launchNumber, launchPiece,
  273.                 ephemerisType, elementNumber, epoch, meanMotion,
  274.                 meanMotionFirstDerivative, meanMotionSecondDerivative, e, i, pa, raan,
  275.                 meanAnomaly, revolutionNumberAtEpoch, bStar,
  276.                 DataContext.getDefault().getTimeScales().getUTC());
  277.     }

  278.     /**
  279.      * <p>
  280.      * Simple constructor from already parsed elements using the given time scale as
  281.      * UTC.
  282.      * </p>
  283.      *
  284.      * <p>
  285.      * The mean anomaly, the right ascension of ascending node Ω and the argument of
  286.      * perigee ω are normalized into the [0, 2π] interval as they can be negative.
  287.      * After that, a range check is performed on some of the orbital elements:
  288.      *
  289.      * <pre>
  290.      *     meanMotion &gt;= 0
  291.      *     0 &lt;= i &lt;= π
  292.      *     0 &lt;= Ω &lt;= 2π
  293.      *     0 &lt;= e &lt;= 1
  294.      *     0 &lt;= ω &lt;= 2π
  295.      *     0 &lt;= meanAnomaly &lt;= 2π
  296.      * </pre>
  297.      *
  298.      * @param satelliteNumber satellite number
  299.      * @param classification classification (U for unclassified)
  300.      * @param launchYear launch year (all digits)
  301.      * @param launchNumber launch number
  302.      * @param launchPiece launch piece (3 char String)
  303.      * @param ephemerisType type of ephemeris
  304.      * @param elementNumber element number
  305.      * @param epoch elements epoch
  306.      * @param meanMotion mean motion (rad/s)
  307.      * @param meanMotionFirstDerivative mean motion first derivative (rad/s²)
  308.      * @param meanMotionSecondDerivative mean motion second derivative (rad/s³)
  309.      * @param e eccentricity
  310.      * @param i inclination (rad)
  311.      * @param pa argument of perigee (rad)
  312.      * @param raan right ascension of ascending node (rad)
  313.      * @param meanAnomaly mean anomaly (rad)
  314.      * @param revolutionNumberAtEpoch revolution number at epoch
  315.      * @param bStar ballistic coefficient
  316.      * @param utc the UTC time scale.
  317.      * @since 10.1
  318.      */
  319.     public TLE(final int satelliteNumber, final char classification,
  320.                final int launchYear, final int launchNumber, final String launchPiece,
  321.                final int ephemerisType, final int elementNumber, final AbsoluteDate epoch,
  322.                final double meanMotion, final double meanMotionFirstDerivative,
  323.                final double meanMotionSecondDerivative, final double e, final double i,
  324.                final double pa, final double raan, final double meanAnomaly,
  325.                final int revolutionNumberAtEpoch, final double bStar,
  326.                final TimeScale utc) {

  327.         // identification
  328.         this.satelliteNumber = satelliteNumber;
  329.         this.classification  = classification;
  330.         this.launchYear      = launchYear;
  331.         this.launchNumber    = launchNumber;
  332.         this.launchPiece     = launchPiece;
  333.         this.ephemerisType   = ephemerisType;
  334.         this.elementNumber   = elementNumber;

  335.         // orbital parameters
  336.         this.epoch = epoch;
  337.         // Checking mean motion range
  338.         this.meanMotion = meanMotion;
  339.         this.meanMotionFirstDerivative = meanMotionFirstDerivative;
  340.         this.meanMotionSecondDerivative = meanMotionSecondDerivative;

  341.         // Checking inclination range
  342.         this.inclination = i;

  343.         // Normalizing RAAN in [0,2pi] interval
  344.         this.raan = MathUtils.normalizeAngle(raan, FastMath.PI);

  345.         // Checking eccentricity range
  346.         this.eccentricity = e;

  347.         // Normalizing PA in [0,2pi] interval
  348.         this.pa = MathUtils.normalizeAngle(pa, FastMath.PI);

  349.         // Normalizing mean anomaly in [0,2pi] interval
  350.         this.meanAnomaly = MathUtils.normalizeAngle(meanAnomaly, FastMath.PI);

  351.         this.revolutionNumberAtEpoch = revolutionNumberAtEpoch;


  352.         // don't build the line until really needed
  353.         this.line1 = null;
  354.         this.line2 = null;
  355.         this.utc = utc;

  356.         // create model parameter drivers
  357.         this.bStarParameterDriver = new ParameterDriver(B_STAR, bStar, B_STAR_SCALE,
  358.                                                         Double.NEGATIVE_INFINITY,
  359.                                                         Double.POSITIVE_INFINITY);

  360.     }

  361.     /**
  362.      * Get the UTC time scale used to create this TLE.
  363.      *
  364.      * @return UTC time scale.
  365.      */
  366.     public TimeScale getUtc() {
  367.         return utc;
  368.     }

  369.     /** Get the first line.
  370.      * @return first line
  371.      */
  372.     public String getLine1() {
  373.         if (line1 == null) {
  374.             buildLine1();
  375.         }
  376.         return line1;
  377.     }

  378.     /** Get the second line.
  379.      * @return second line
  380.      */
  381.     public String getLine2() {
  382.         if (line2 == null) {
  383.             buildLine2();
  384.         }
  385.         return line2;
  386.     }

  387.     /** Build the line 1 from the parsed elements.
  388.      */
  389.     private void buildLine1() {

  390.         final StringBuilder buffer = new StringBuilder();

  391.         buffer.append('1');

  392.         buffer.append(' ');
  393.         buffer.append(ParseUtils.buildSatelliteNumber(satelliteNumber, "satelliteNumber-1"));
  394.         buffer.append(classification);

  395.         buffer.append(' ');
  396.         buffer.append(ParseUtils.addPadding("launchYear",   launchYear % 100, '0', 2, true, satelliteNumber));
  397.         buffer.append(ParseUtils.addPadding("launchNumber", launchNumber, '0', 3, true, satelliteNumber));
  398.         buffer.append(ParseUtils.addPadding("launchPiece",  launchPiece, ' ', 3, false, satelliteNumber));

  399.         buffer.append(' ');
  400.         final DateTimeComponents dtc = epoch.getComponents(utc);
  401.         buffer.append(ParseUtils.addPadding("year", dtc.getDate().getYear() % 100, '0', 2, true, satelliteNumber));
  402.         buffer.append(ParseUtils.addPadding("day",  dtc.getDate().getDayOfYear(),  '0', 3, true, satelliteNumber));
  403.         buffer.append('.');
  404.         // nota: 31250/27 == 100000000/86400
  405.         final int fraction = (int) FastMath.rint(31250 * dtc.getTime().getSecondsInUTCDay() / 27.0);
  406.         buffer.append(ParseUtils.addPadding("fraction", fraction,  '0', 8, true, satelliteNumber));

  407.         buffer.append(' ');
  408.         final double n1 = meanMotionFirstDerivative * 1.86624e9 / FastMath.PI;
  409.         final String sn1 = ParseUtils.addPadding("meanMotionFirstDerivative",
  410.                                                  new DecimalFormat(".00000000", SYMBOLS).format(n1),
  411.                                                  ' ', 10, true, satelliteNumber);
  412.         buffer.append(sn1);

  413.         buffer.append(' ');
  414.         final double n2 = meanMotionSecondDerivative * 5.3747712e13 / FastMath.PI;
  415.         buffer.append(formatExponentMarkerFree("meanMotionSecondDerivative", n2, 5, ' ', 8, true));

  416.         buffer.append(' ');
  417.         buffer.append(formatExponentMarkerFree("B*", getBStar(), 5, ' ', 8, true));

  418.         buffer.append(' ');
  419.         buffer.append(ephemerisType);

  420.         buffer.append(' ');
  421.         buffer.append(ParseUtils.addPadding("elementNumber", elementNumber, ' ', 4, true, satelliteNumber));

  422.         buffer.append(checksum(buffer));

  423.         line1 = buffer.toString();

  424.     }

  425.     /** Format a real number without 'e' exponent marker.
  426.      * @param name parameter name
  427.      * @param d number to format
  428.      * @param mantissaSize size of the mantissa (not counting initial '-' or ' ' for sign)
  429.      * @param c padding character
  430.      * @param size desired size
  431.      * @param rightJustified if true, the resulting string is
  432.      * right justified (i.e. space are added to the left)
  433.      * @return formatted and padded number
  434.      */
  435.     private String formatExponentMarkerFree(final String name, final double d, final int mantissaSize,
  436.                                             final char c, final int size, final boolean rightJustified) {
  437.         final double dAbs = FastMath.abs(d);
  438.         int exponent = (dAbs < 1.0e-9) ? -9 : (int) FastMath.ceil(FastMath.log10(dAbs));
  439.         long mantissa = FastMath.round(dAbs * FastMath.pow(10.0, mantissaSize - exponent));
  440.         if (mantissa == 0) {
  441.             exponent = 0;
  442.         } else if (mantissa > (ArithmeticUtils.pow(10, mantissaSize) - 1)) {
  443.             // rare case: if d has a single digit like d = 1.0e-4 with mantissaSize = 5
  444.             // the above computation finds exponent = -4 and mantissa = 100000 which
  445.             // doesn't fit in a 5 digits string
  446.             exponent++;
  447.             mantissa = FastMath.round(dAbs * FastMath.pow(10.0, mantissaSize - exponent));
  448.         }
  449.         final String sMantissa = ParseUtils.addPadding(name, (int) mantissa, '0', mantissaSize, true, satelliteNumber);
  450.         final String sExponent = Integer.toString(FastMath.abs(exponent));
  451.         final String formatted = (d <  0 ? '-' : ' ') + sMantissa + (exponent <= 0 ? '-' : '+') + sExponent;

  452.         return ParseUtils.addPadding(name, formatted, c, size, rightJustified, satelliteNumber);

  453.     }

  454.     /** Build the line 2 from the parsed elements.
  455.      */
  456.     private void buildLine2() {

  457.         final StringBuilder buffer = new StringBuilder();
  458.         final DecimalFormat f34   = new DecimalFormat("##0.0000", SYMBOLS);
  459.         final DecimalFormat f211  = new DecimalFormat("#0.00000000", SYMBOLS);

  460.         buffer.append('2');

  461.         buffer.append(' ');
  462.         buffer.append(ParseUtils.buildSatelliteNumber(satelliteNumber, "satelliteNumber-2"));

  463.         buffer.append(' ');
  464.         buffer.append(ParseUtils.addPadding(INCLINATION, f34.format(FastMath.toDegrees(inclination)), ' ', 8, true, satelliteNumber));
  465.         buffer.append(' ');
  466.         buffer.append(ParseUtils.addPadding("raan", f34.format(FastMath.toDegrees(raan)), ' ', 8, true, satelliteNumber));
  467.         buffer.append(' ');
  468.         buffer.append(ParseUtils.addPadding(ECCENTRICITY, (int) FastMath.rint(eccentricity * 1.0e7), '0', 7, true, satelliteNumber));
  469.         buffer.append(' ');
  470.         buffer.append(ParseUtils.addPadding("pa", f34.format(FastMath.toDegrees(pa)), ' ', 8, true, satelliteNumber));
  471.         buffer.append(' ');
  472.         buffer.append(ParseUtils.addPadding("meanAnomaly", f34.format(FastMath.toDegrees(meanAnomaly)), ' ', 8, true, satelliteNumber));

  473.         buffer.append(' ');
  474.         buffer.append(ParseUtils.addPadding(MEAN_MOTION, f211.format(meanMotion * 43200.0 / FastMath.PI), ' ', 11, true, satelliteNumber));
  475.         buffer.append(ParseUtils.addPadding("revolutionNumberAtEpoch", revolutionNumberAtEpoch, ' ', 5, true, satelliteNumber));

  476.         buffer.append(checksum(buffer));

  477.         line2 = buffer.toString();

  478.     }

  479.     /** Get the satellite id.
  480.      * @return the satellite number
  481.      */
  482.     public int getSatelliteNumber() {
  483.         return satelliteNumber;
  484.     }

  485.     /** Get the classification.
  486.      * @return classification
  487.      */
  488.     public char getClassification() {
  489.         return classification;
  490.     }

  491.     /** Get the launch year.
  492.      * @return the launch year
  493.      */
  494.     public int getLaunchYear() {
  495.         return launchYear;
  496.     }

  497.     /** Get the launch number.
  498.      * @return the launch number
  499.      */
  500.     public int getLaunchNumber() {
  501.         return launchNumber;
  502.     }

  503.     /** Get the launch piece.
  504.      * @return the launch piece
  505.      */
  506.     public String getLaunchPiece() {
  507.         return launchPiece;
  508.     }

  509.     /** Get the type of ephemeris.
  510.      * @return the ephemeris type (one of {@link #DEFAULT}, {@link #SGP},
  511.      * {@link #SGP4}, {@link #SGP8}, {@link #SDP4}, {@link #SDP8})
  512.      */
  513.     public int getEphemerisType() {
  514.         return ephemerisType;
  515.     }

  516.     /** Get the element number.
  517.      * @return the element number
  518.      */
  519.     public int getElementNumber() {
  520.         return elementNumber;
  521.     }

  522.     /** Get the TLE current date.
  523.      * @return the epoch
  524.      */
  525.     public AbsoluteDate getDate() {
  526.         return epoch;
  527.     }

  528.     /** Get the mean motion.
  529.      * @return the mean motion (rad/s)
  530.      */
  531.     public double getMeanMotion() {
  532.         return meanMotion;
  533.     }

  534.     /** Get the mean motion first derivative.
  535.      * @return the mean motion first derivative (rad/s²)
  536.      */
  537.     public double getMeanMotionFirstDerivative() {
  538.         return meanMotionFirstDerivative;
  539.     }

  540.     /** Get the mean motion second derivative.
  541.      * @return the mean motion second derivative (rad/s³)
  542.      */
  543.     public double getMeanMotionSecondDerivative() {
  544.         return meanMotionSecondDerivative;
  545.     }

  546.     /** Get the eccentricity.
  547.      * @return the eccentricity
  548.      */
  549.     public double getE() {
  550.         return eccentricity;
  551.     }

  552.     /** Get the inclination.
  553.      * @return the inclination (rad)
  554.      */
  555.     public double getI() {
  556.         return inclination;
  557.     }

  558.     /** Get the argument of perigee.
  559.      * @return omega (rad)
  560.      */
  561.     public double getPerigeeArgument() {
  562.         return pa;
  563.     }

  564.     /** Get Right Ascension of the Ascending node.
  565.      * @return the raan (rad)
  566.      */
  567.     public double getRaan() {
  568.         return raan;
  569.     }

  570.     /** Get the mean anomaly.
  571.      * @return the mean anomaly (rad)
  572.      */
  573.     public double getMeanAnomaly() {
  574.         return meanAnomaly;
  575.     }

  576.     /** Get the revolution number.
  577.      * @return the revolutionNumberAtEpoch
  578.      */
  579.     public int getRevolutionNumberAtEpoch() {
  580.         return revolutionNumberAtEpoch;
  581.     }

  582.     /** Get the ballistic coefficient.
  583.      * @return bStar
  584.      */
  585.     public double getBStar() {
  586.         return bStarParameterDriver.getValue();
  587.     }

  588.     /** Get a string representation of this TLE set.
  589.      * <p>The representation is simply the two lines separated by the
  590.      * platform line separator.</p>
  591.      * @return string representation of this TLE set
  592.      */
  593.     public String toString() {
  594.         return getLine1() + System.getProperty("line.separator") + getLine2();
  595.     }

  596.     /**
  597.      * Convert Spacecraft State into TLE.
  598.      * This converter uses Fixed Point method to reverse SGP4 and SDP4 propagation algorithm
  599.      * and generates a usable TLE version of a state.
  600.      * Equinocital orbital parameters are used in order to get a stiff method.
  601.      * New TLE epoch is state epoch.
  602.      *
  603.      * <p>
  604.      * This method uses the {@link DataContext#getDefault() default data context},
  605.      * as well as {@link #EPSILON_DEFAULT} and {@link #MAX_ITERATIONS_DEFAULT} for method convergence.
  606.      *
  607.      * @param state Spacecraft State to convert into TLE
  608.      * @param templateTLE first guess used to get identification and estimate new TLE
  609.      * @return TLE matching with Spacecraft State and template identification
  610.      * @see #stateToTLE(SpacecraftState, TLE, TimeScale, Frame)
  611.      * @see #stateToTLE(SpacecraftState, TLE, TimeScale, Frame, double, int)
  612.      * @since 11.0
  613.      */
  614.     @DefaultDataContext
  615.     public static TLE stateToTLE(final SpacecraftState state, final TLE templateTLE) {
  616.         return stateToTLE(state, templateTLE,
  617.                           DataContext.getDefault().getTimeScales().getUTC(),
  618.                           DataContext.getDefault().getFrames().getTEME());
  619.     }

  620.     /**
  621.      * Convert Spacecraft State into TLE.
  622.      * This converter uses Fixed Point method to reverse SGP4 and SDP4 propagation algorithm
  623.      * and generates a usable TLE version of a state.
  624.      * Equinocital orbital parameters are used in order to get a stiff method.
  625.      * New TLE epoch is state epoch.
  626.      *
  627.      * <p>
  628.      * This method uses {@link #EPSILON_DEFAULT} and {@link #MAX_ITERATIONS_DEFAULT}
  629.      * for method convergence.
  630.      *
  631.      * @param state Spacecraft State to convert into TLE
  632.      * @param templateTLE first guess used to get identification and estimate new TLE
  633.      * @param utc the UTC time scale
  634.      * @param teme the TEME frame to use for propagation
  635.      * @return TLE matching with Spacecraft State and template identification
  636.      * @see #stateToTLE(SpacecraftState, TLE, TimeScale, Frame, double, int)
  637.      * @since 11.0
  638.      */
  639.     public static TLE stateToTLE(final SpacecraftState state, final TLE templateTLE,
  640.                                  final TimeScale utc, final Frame teme) {
  641.         return stateToTLE(state, templateTLE, utc, teme, EPSILON_DEFAULT, MAX_ITERATIONS_DEFAULT);
  642.     }

  643.     /**
  644.      * Convert Spacecraft State into TLE.
  645.      * This converter uses Newton method to reverse SGP4 and SDP4 propagation algorithm
  646.      * and generates a usable TLE version of a state.
  647.      * New TLE epoch is state epoch.
  648.      *
  649.      * @param state Spacecraft State to convert into TLE
  650.      * @param templateTLE first guess used to get identification and estimate new TLE
  651.      * @param utc the UTC time scale
  652.      * @param teme the TEME frame to use for propagation
  653.      * @param epsilon used to compute threshold for convergence check
  654.      * @param maxIterations maximum number of iterations for convergence
  655.      * @return TLE matching with Spacecraft State and template identification
  656.      * @since 11.0
  657.      */
  658.     public static TLE stateToTLE(final SpacecraftState state, final TLE templateTLE,
  659.                                  final TimeScale utc, final Frame teme,
  660.                                  final double epsilon, final int maxIterations) {

  661.         // Gets equinoctial parameters in TEME frame from state
  662.         final EquinoctialOrbit equiOrbit = convert(state.getOrbit(), teme);
  663.         double sma = equiOrbit.getA();
  664.         double ex  = equiOrbit.getEquinoctialEx();
  665.         double ey  = equiOrbit.getEquinoctialEy();
  666.         double hx  = equiOrbit.getHx();
  667.         double hy  = equiOrbit.getHy();
  668.         double lv  = equiOrbit.getLv();

  669.         // Rough initialization of the TLE
  670.         final KeplerianOrbit keplerianOrbit = (KeplerianOrbit) OrbitType.KEPLERIAN.convertType(equiOrbit);
  671.         TLE current = newTLE(keplerianOrbit, templateTLE, utc);

  672.         // threshold for each parameter
  673.         final double thrA = epsilon * (1 + sma);
  674.         final double thrE = epsilon * (1 + FastMath.hypot(ex, ey));
  675.         final double thrH = epsilon * (1 + FastMath.hypot(hx, hy));
  676.         final double thrV = epsilon * FastMath.PI;

  677.         int k = 0;
  678.         while (k++ < maxIterations) {

  679.             // recompute the state from the current TLE
  680.             final TLEPropagator propagator = TLEPropagator.selectExtrapolator(current, new InertialProvider(Rotation.IDENTITY, teme), state.getMass(), teme);
  681.             final Orbit recovOrbit = propagator.getInitialState().getOrbit();
  682.             final EquinoctialOrbit recovEquiOrbit = (EquinoctialOrbit) OrbitType.EQUINOCTIAL.convertType(recovOrbit);

  683.             // adapted parameters residuals
  684.             final double deltaSma = equiOrbit.getA() - recovEquiOrbit.getA();
  685.             final double deltaEx  = equiOrbit.getEquinoctialEx() - recovEquiOrbit.getEquinoctialEx();
  686.             final double deltaEy  = equiOrbit.getEquinoctialEy() - recovEquiOrbit.getEquinoctialEy();
  687.             final double deltaHx  = equiOrbit.getHx() - recovEquiOrbit.getHx();
  688.             final double deltaHy  = equiOrbit.getHy() - recovEquiOrbit.getHy();
  689.             final double deltaLv  = MathUtils.normalizeAngle(equiOrbit.getLv() - recovEquiOrbit.getLv(), 0.0);

  690.             // check convergence
  691.             if (FastMath.abs(deltaSma) < thrA &&
  692.                 FastMath.abs(deltaEx)  < thrE &&
  693.                 FastMath.abs(deltaEy)  < thrE &&
  694.                 FastMath.abs(deltaHx)  < thrH &&
  695.                 FastMath.abs(deltaHy)  < thrH &&
  696.                 FastMath.abs(deltaLv)  < thrV) {

  697.                 // Verify if parameters are estimated
  698.                 for (final ParameterDriver templateDrivers : templateTLE.getParametersDrivers()) {
  699.                     if (templateDrivers.isSelected()) {
  700.                         // Set to selected for the new TLE
  701.                         current.getParameterDriver(templateDrivers.getName()).setSelected(true);
  702.                     }
  703.                 }

  704.                 // Return
  705.                 return current;
  706.             }

  707.             // update state
  708.             sma += deltaSma;
  709.             ex  += deltaEx;
  710.             ey  += deltaEy;
  711.             hx  += deltaHx;
  712.             hy  += deltaHy;
  713.             lv  += deltaLv;
  714.             final EquinoctialOrbit newEquiOrbit =
  715.                                     new EquinoctialOrbit(sma, ex, ey, hx, hy, lv, PositionAngle.TRUE,
  716.                                     equiOrbit.getFrame(), equiOrbit.getDate(), equiOrbit.getMu());
  717.             final KeplerianOrbit newKeplOrbit = (KeplerianOrbit) OrbitType.KEPLERIAN.convertType(newEquiOrbit);

  718.             // update TLE
  719.             current = newTLE(newKeplOrbit, templateTLE, utc);
  720.         }

  721.         throw new OrekitException(OrekitMessages.UNABLE_TO_COMPUTE_TLE, k);
  722.     }

  723.     /**
  724.      * Converts an orbit into an equinoctial orbit expressed in TEME frame.
  725.      *
  726.      * @param orbitIn the orbit to convert
  727.      * @param teme the TEME frame to use for propagation
  728.      * @return the converted orbit, i.e. equinoctial in TEME frame
  729.      */
  730.     private static EquinoctialOrbit convert(final Orbit orbitIn, final Frame teme) {
  731.         return new EquinoctialOrbit(orbitIn.getPVCoordinates(teme), teme, orbitIn.getMu());
  732.     }

  733.     /**
  734.      * Builds a new TLE from Keplerian parameters and a template for TLE data.
  735.      * @param keplerianOrbit the Keplerian parameters to build the TLE from
  736.      * @param templateTLE TLE used to get object identification
  737.      * @param utc the UTC time scale
  738.      * @return TLE with template identification and new orbital parameters
  739.      */
  740.     private static TLE newTLE(final KeplerianOrbit keplerianOrbit, final TLE templateTLE,
  741.                               final TimeScale utc) {
  742.         // Keplerian parameters
  743.         final double meanMotion  = keplerianOrbit.getKeplerianMeanMotion();
  744.         final double e           = keplerianOrbit.getE();
  745.         final double i           = keplerianOrbit.getI();
  746.         final double raan        = keplerianOrbit.getRightAscensionOfAscendingNode();
  747.         final double pa          = keplerianOrbit.getPerigeeArgument();
  748.         final double meanAnomaly = keplerianOrbit.getMeanAnomaly();
  749.         // TLE epoch is state epoch
  750.         final AbsoluteDate epoch = keplerianOrbit.getDate();
  751.         // Identification
  752.         final int satelliteNumber = templateTLE.getSatelliteNumber();
  753.         final char classification = templateTLE.getClassification();
  754.         final int launchYear = templateTLE.getLaunchYear();
  755.         final int launchNumber = templateTLE.getLaunchNumber();
  756.         final String launchPiece = templateTLE.getLaunchPiece();
  757.         final int ephemerisType = templateTLE.getEphemerisType();
  758.         final int elementNumber = templateTLE.getElementNumber();
  759.         // Updates revolutionNumberAtEpoch
  760.         final int revolutionNumberAtEpoch = templateTLE.getRevolutionNumberAtEpoch();
  761.         final double dt = epoch.durationFrom(templateTLE.getDate());
  762.         final int newRevolutionNumberAtEpoch = (int) (revolutionNumberAtEpoch + FastMath.floor((MathUtils.normalizeAngle(meanAnomaly, FastMath.PI) + dt * meanMotion) / (2 * FastMath.PI)));
  763.         // Gets B*
  764.         final double bStar = templateTLE.getBStar();
  765.         // Gets Mean Motion derivatives
  766.         final double meanMotionFirstDerivative = templateTLE.getMeanMotionFirstDerivative();
  767.         final double meanMotionSecondDerivative = templateTLE.getMeanMotionSecondDerivative();
  768.         // Returns the new TLE
  769.         return new TLE(satelliteNumber, classification, launchYear, launchNumber, launchPiece, ephemerisType,
  770.                        elementNumber, epoch, meanMotion, meanMotionFirstDerivative, meanMotionSecondDerivative,
  771.                        e, i, pa, raan, meanAnomaly, newRevolutionNumberAtEpoch, bStar, utc);
  772.     }

  773.     /** Check the lines format validity.
  774.      * @param line1 the first element
  775.      * @param line2 the second element
  776.      * @return true if format is recognized (non null lines, 69 characters length,
  777.      * line content), false if not
  778.      */
  779.     public static boolean isFormatOK(final String line1, final String line2) {

  780.         if (line1 == null || line1.length() != 69 ||
  781.             line2 == null || line2.length() != 69) {
  782.             return false;
  783.         }

  784.         if (!(LINE_1_PATTERN.matcher(line1).matches() &&
  785.               LINE_2_PATTERN.matcher(line2).matches())) {
  786.             return false;
  787.         }

  788.         // check sums
  789.         final int checksum1 = checksum(line1);
  790.         if (Integer.parseInt(line1.substring(68)) != (checksum1 % 10)) {
  791.             throw new OrekitException(OrekitMessages.TLE_CHECKSUM_ERROR,
  792.                                       1, Integer.toString(checksum1 % 10), line1.substring(68), line1);
  793.         }

  794.         final int checksum2 = checksum(line2);
  795.         if (Integer.parseInt(line2.substring(68)) != (checksum2 % 10)) {
  796.             throw new OrekitException(OrekitMessages.TLE_CHECKSUM_ERROR,
  797.                                       2, Integer.toString(checksum2 % 10), line2.substring(68), line2);
  798.         }

  799.         return true;

  800.     }

  801.     /** Compute the checksum of the first 68 characters of a line.
  802.      * @param line line to check
  803.      * @return checksum
  804.      */
  805.     private static int checksum(final CharSequence line) {
  806.         int sum = 0;
  807.         for (int j = 0; j < 68; j++) {
  808.             final char c = line.charAt(j);
  809.             if (Character.isDigit(c)) {
  810.                 sum += Character.digit(c, 10);
  811.             } else if (c == '-') {
  812.                 ++sum;
  813.             }
  814.         }
  815.         return sum % 10;
  816.     }

  817.     /** Check if this tle equals the provided tle.
  818.      * <p>Due to the difference in precision between object and string
  819.      * representations of TLE, it is possible for this method to return false
  820.      * even if string representations returned by {@link #toString()}
  821.      * are equal.</p>
  822.      * @param o other tle
  823.      * @return true if this tle equals the provided tle
  824.      */
  825.     @Override
  826.     public boolean equals(final Object o) {
  827.         if (o == this) {
  828.             return true;
  829.         }
  830.         if (!(o instanceof TLE)) {
  831.             return false;
  832.         }
  833.         final TLE tle = (TLE) o;
  834.         return satelliteNumber == tle.satelliteNumber &&
  835.                 classification == tle.classification &&
  836.                 launchYear == tle.launchYear &&
  837.                 launchNumber == tle.launchNumber &&
  838.                 Objects.equals(launchPiece, tle.launchPiece) &&
  839.                 ephemerisType == tle.ephemerisType &&
  840.                 elementNumber == tle.elementNumber &&
  841.                 Objects.equals(epoch, tle.epoch) &&
  842.                 meanMotion == tle.meanMotion &&
  843.                 meanMotionFirstDerivative == tle.meanMotionFirstDerivative &&
  844.                 meanMotionSecondDerivative == tle.meanMotionSecondDerivative &&
  845.                 eccentricity == tle.eccentricity &&
  846.                 inclination == tle.inclination &&
  847.                 pa == tle.pa &&
  848.                 raan == tle.raan &&
  849.                 meanAnomaly == tle.meanAnomaly &&
  850.                 revolutionNumberAtEpoch == tle.revolutionNumberAtEpoch &&
  851.                 getBStar() == tle.getBStar();
  852.     }

  853.     /** Get a hashcode for this tle.
  854.      * @return hashcode
  855.      */
  856.     @Override
  857.     public int hashCode() {
  858.         return Objects.hash(satelliteNumber,
  859.                 classification,
  860.                 launchYear,
  861.                 launchNumber,
  862.                 launchPiece,
  863.                 ephemerisType,
  864.                 elementNumber,
  865.                 epoch,
  866.                 meanMotion,
  867.                 meanMotionFirstDerivative,
  868.                 meanMotionSecondDerivative,
  869.                 eccentricity,
  870.                 inclination,
  871.                 pa,
  872.                 raan,
  873.                 meanAnomaly,
  874.                 revolutionNumberAtEpoch,
  875.                 getBStar());
  876.     }

  877.     /** Get the drivers for TLE propagation SGP4 and SDP4.
  878.      * @return drivers for SGP4 and SDP4 model parameters
  879.      */
  880.     public List<ParameterDriver> getParametersDrivers() {
  881.         return Collections.singletonList(bStarParameterDriver);
  882.     }

  883.     /** Get parameter driver from its name.
  884.      * @param name parameter name
  885.      * @return parameter driver
  886.      * @since 11.1
  887.      */
  888.     public ParameterDriver getParameterDriver(final String name) {
  889.         // Loop on known drivers
  890.         for (final ParameterDriver driver : getParametersDrivers()) {
  891.             if (name.equals(driver.getName())) {
  892.                 // we have found a parameter with that name
  893.                 return driver;
  894.             }
  895.         }

  896.         // build the list of supported parameters
  897.         final StringBuilder sBuilder = new StringBuilder();
  898.         for (final ParameterDriver driver : getParametersDrivers()) {
  899.             if (sBuilder.length() > 0) {
  900.                 sBuilder.append(", ");
  901.             }
  902.             sBuilder.append(driver.getName());
  903.         }
  904.         throw new OrekitException(OrekitMessages.UNSUPPORTED_PARAMETER_NAME,
  905.                                   name, sBuilder.toString());

  906.     }

  907.     /** Replace the instance with a data transfer object for serialization.
  908.      * @return data transfer object that will be serialized
  909.      */
  910.     private Object writeReplace() {
  911.         return new DataTransferObject(line1, line2, utc);
  912.     }

  913.     /** Internal class used only for serialization. */
  914.     private static class DataTransferObject implements Serializable {

  915.         /** Serializable UID. */
  916.         private static final long serialVersionUID = -1596648022319057689L;

  917.         /** First line. */
  918.         private String line1;

  919.         /** Second line. */
  920.         private String line2;

  921.         /** The UTC scale. */
  922.         private final TimeScale utc;

  923.         /** Simple constructor.
  924.          * @param line1 the first element (69 char String)
  925.          * @param line2 the second element (69 char String)
  926.          * @param utc the UTC time scale
  927.          */
  928.         DataTransferObject(final String line1, final String line2, final TimeScale utc) {
  929.             this.line1 = line1;
  930.             this.line2 = line2;
  931.             this.utc   = utc;
  932.         }

  933.         /** Replace the deserialized data transfer object with a {@link TLE}.
  934.          * @return replacement {@link TLE}
  935.          */
  936.         private Object readResolve() {
  937.             return new TLE(line1, line2, utc);
  938.         }

  939.     }

  940. }