FieldTLE.java
- /* Copyright 2002-2022 CS GROUP
- * Licensed to CS GROUP (CS) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * CS licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- package org.orekit.propagation.analytical.tle;
- import java.io.Serializable;
- import java.text.DecimalFormat;
- import java.text.DecimalFormatSymbols;
- import java.util.Collections;
- import java.util.List;
- import java.util.Locale;
- import java.util.Objects;
- import org.hipparchus.CalculusFieldElement;
- import org.hipparchus.Field;
- import org.hipparchus.geometry.euclidean.threed.Rotation;
- import org.hipparchus.util.ArithmeticUtils;
- import org.hipparchus.util.FastMath;
- import org.hipparchus.util.MathArrays;
- import org.hipparchus.util.MathUtils;
- import org.orekit.annotation.DefaultDataContext;
- import org.orekit.attitudes.InertialProvider;
- import org.orekit.data.DataContext;
- import org.orekit.errors.OrekitException;
- import org.orekit.errors.OrekitInternalError;
- import org.orekit.errors.OrekitMessages;
- import org.orekit.frames.Frame;
- import org.orekit.orbits.FieldEquinoctialOrbit;
- import org.orekit.orbits.FieldKeplerianOrbit;
- import org.orekit.orbits.FieldOrbit;
- import org.orekit.orbits.OrbitType;
- import org.orekit.orbits.PositionAngle;
- import org.orekit.propagation.FieldSpacecraftState;
- import org.orekit.time.DateComponents;
- import org.orekit.time.DateTimeComponents;
- import org.orekit.time.FieldAbsoluteDate;
- import org.orekit.time.FieldTimeStamped;
- import org.orekit.time.TimeComponents;
- import org.orekit.time.TimeScale;
- import org.orekit.utils.ParameterDriver;
- /** This class is a container for a single set of TLE data.
- *
- * <p>TLE sets can be built either by providing directly the two lines, in
- * which case parsing is performed internally or by providing the already
- * parsed elements.</p>
- * <p>TLE are not transparently convertible to {@link org.orekit.orbits.Orbit Orbit}
- * instances. They are significant only with respect to their dedicated {@link
- * TLEPropagator propagator}, which also computes position and velocity coordinates.
- * Any attempt to directly use orbital parameters like {@link #getE() eccentricity},
- * {@link #getI() inclination}, etc. without any reference to the {@link TLEPropagator
- * TLE propagator} is prone to errors.</p>
- * <p>More information on the TLE format can be found on the
- * <a href="https://www.celestrak.com/">CelesTrak website.</a></p>
- * @author Fabien Maussion
- * @author Luc Maisonobe
- * @author Thomas Paulet (field translation)
- * @since 11.0
- */
- public class FieldTLE<T extends CalculusFieldElement<T>> implements FieldTimeStamped<T>, Serializable {
- /** Identifier for default type of ephemeris (SGP4/SDP4). */
- public static final int DEFAULT = 0;
- /** Identifier for SGP type of ephemeris. */
- public static final int SGP = 1;
- /** Identifier for SGP4 type of ephemeris. */
- public static final int SGP4 = 2;
- /** Identifier for SDP4 type of ephemeris. */
- public static final int SDP4 = 3;
- /** Identifier for SGP8 type of ephemeris. */
- public static final int SGP8 = 4;
- /** Identifier for SDP8 type of ephemeris. */
- public static final int SDP8 = 5;
- /** Parameter name for B* coefficient. */
- public static final String B_STAR = "BSTAR";
- /** Default value for epsilon. */
- private static final double EPSILON_DEFAULT = 1.0e-10;
- /** Default value for maxIterations. */
- private static final int MAX_ITERATIONS_DEFAULT = 100;
- /** B* scaling factor.
- * <p>
- * We use a power of 2 to avoid numeric noise introduction
- * in the multiplications/divisions sequences.
- * </p>
- */
- private static final double B_STAR_SCALE = FastMath.scalb(1.0, -20);
- /** Name of the mean motion parameter. */
- private static final String MEAN_MOTION = "meanMotion";
- /** Name of the inclination parameter. */
- private static final String INCLINATION = "inclination";
- /** Name of the eccentricity parameter. */
- private static final String ECCENTRICITY = "eccentricity";
- /** International symbols for parsing. */
- private static final DecimalFormatSymbols SYMBOLS =
- new DecimalFormatSymbols(Locale.US);
- /** Serializable UID. */
- private static final long serialVersionUID = -1596648022319057689L;
- /** The satellite number. */
- private final int satelliteNumber;
- /** Classification (U for unclassified). */
- private final char classification;
- /** Launch year. */
- private final int launchYear;
- /** Launch number. */
- private final int launchNumber;
- /** Piece of launch (from "A" to "ZZZ"). */
- private final String launchPiece;
- /** Type of ephemeris. */
- private final int ephemerisType;
- /** Element number. */
- private final int elementNumber;
- /** the TLE current date. */
- private final transient FieldAbsoluteDate<T> epoch;
- /** Mean motion (rad/s). */
- private final T meanMotion;
- /** Mean motion first derivative (rad/s²). */
- private final T meanMotionFirstDerivative;
- /** Mean motion second derivative (rad/s³). */
- private final T meanMotionSecondDerivative;
- /** Eccentricity. */
- private final T eccentricity;
- /** Inclination (rad). */
- private final T inclination;
- /** Argument of perigee (rad). */
- private final T pa;
- /** Right Ascension of the Ascending node (rad). */
- private final T raan;
- /** Mean anomaly (rad). */
- private final T meanAnomaly;
- /** Revolution number at epoch. */
- private final int revolutionNumberAtEpoch;
- /** First line. */
- private String line1;
- /** Second line. */
- private String line2;
- /** The UTC scale. */
- private final TimeScale utc;
- /** Driver for ballistic coefficient parameter. */
- private final transient ParameterDriver bStarParameterDriver;
- /** Simple constructor from unparsed two lines. This constructor uses the {@link
- * DataContext#getDefault() default data context}.
- *
- * <p>The static method {@link #isFormatOK(String, String)} should be called
- * before trying to build this object.<p>
- * @param field field utilized by default
- * @param line1 the first element (69 char String)
- * @param line2 the second element (69 char String)
- * @see #FieldTLE(Field, String, String, TimeScale)
- */
- @DefaultDataContext
- public FieldTLE(final Field<T> field, final String line1, final String line2) {
- this(field, line1, line2, DataContext.getDefault().getTimeScales().getUTC());
- }
- /** Simple constructor from unparsed two lines using the given time scale as UTC.
- *
- *<p>This method uses the {@link DataContext#getDefault() default data context}.
- *
- * <p>The static method {@link #isFormatOK(String, String)} should be called
- * before trying to build this object.<p>
- * @param field field utilized by default
- * @param line1 the first element (69 char String)
- * @param line2 the second element (69 char String)
- * @param utc the UTC time scale.
- */
- public FieldTLE(final Field<T> field, final String line1, final String line2, final TimeScale utc) {
- // zero and pi for fields
- final T zero = field.getZero();
- final T pi = zero.getPi();
- // identification
- satelliteNumber = ParseUtils.parseSatelliteNumber(line1, 2, 5);
- final int satNum2 = ParseUtils.parseSatelliteNumber(line2, 2, 5);
- if (satelliteNumber != satNum2) {
- throw new OrekitException(OrekitMessages.TLE_LINES_DO_NOT_REFER_TO_SAME_OBJECT,
- line1, line2);
- }
- classification = line1.charAt(7);
- launchYear = ParseUtils.parseYear(line1, 9);
- launchNumber = ParseUtils.parseInteger(line1, 11, 3);
- launchPiece = line1.substring(14, 17).trim();
- ephemerisType = ParseUtils.parseInteger(line1, 62, 1);
- elementNumber = ParseUtils.parseInteger(line1, 64, 4);
- // Date format transform (nota: 27/31250 == 86400/100000000)
- final int year = ParseUtils.parseYear(line1, 18);
- final int dayInYear = ParseUtils.parseInteger(line1, 20, 3);
- final long df = 27l * ParseUtils.parseInteger(line1, 24, 8);
- final int secondsA = (int) (df / 31250l);
- final double secondsB = (df % 31250l) / 31250.0;
- epoch = new FieldAbsoluteDate<>(field, new DateComponents(year, dayInYear),
- new TimeComponents(secondsA, secondsB),
- utc);
- // mean motion development
- // converted from rev/day, 2 * rev/day^2 and 6 * rev/day^3 to rad/s, rad/s^2 and rad/s^3
- meanMotion = pi.multiply(ParseUtils.parseDouble(line2, 52, 11)).divide(43200.0);
- meanMotionFirstDerivative = pi.multiply(ParseUtils.parseDouble(line1, 33, 10)).divide(1.86624e9);
- meanMotionSecondDerivative = pi.multiply(Double.parseDouble((line1.substring(44, 45) + '.' +
- line1.substring(45, 50) + 'e' +
- line1.substring(50, 52)).replace(' ', '0'))).divide(5.3747712e13);
- eccentricity = zero.add(Double.parseDouble("." + line2.substring(26, 33).replace(' ', '0')));
- inclination = zero.add(FastMath.toRadians(ParseUtils.parseDouble(line2, 8, 8)));
- pa = zero.add(FastMath.toRadians(ParseUtils.parseDouble(line2, 34, 8)));
- raan = zero.add(FastMath.toRadians(Double.parseDouble(line2.substring(17, 25).replace(' ', '0'))));
- meanAnomaly = zero.add(FastMath.toRadians(ParseUtils.parseDouble(line2, 43, 8)));
- revolutionNumberAtEpoch = ParseUtils.parseInteger(line2, 63, 5);
- final double bStarValue = Double.parseDouble((line1.substring(53, 54) + '.' +
- line1.substring(54, 59) + 'e' +
- line1.substring(59, 61)).replace(' ', '0'));
- // save the lines
- this.line1 = line1;
- this.line2 = line2;
- this.utc = utc;
- this.bStarParameterDriver = new ParameterDriver(B_STAR, bStarValue, B_STAR_SCALE,
- Double.NEGATIVE_INFINITY,
- Double.POSITIVE_INFINITY);
- }
- /**
- * <p>
- * Simple constructor from already parsed elements. This constructor uses the
- * {@link DataContext#getDefault() default data context}.
- * </p>
- *
- * <p>
- * The mean anomaly, the right ascension of ascending node Ω and the argument of
- * perigee ω are normalized into the [0, 2π] interval as they can be negative.
- * After that, a range check is performed on some of the orbital elements:
- *
- * <pre>
- * meanMotion >= 0
- * 0 <= i <= π
- * 0 <= Ω <= 2π
- * 0 <= e <= 1
- * 0 <= ω <= 2π
- * 0 <= meanAnomaly <= 2π
- * </pre>
- *
- *
- * @param satelliteNumber satellite number
- * @param classification classification (U for unclassified)
- * @param launchYear launch year (all digits)
- * @param launchNumber launch number
- * @param launchPiece launch piece (3 char String)
- * @param ephemerisType type of ephemeris
- * @param elementNumber element number
- * @param epoch elements epoch
- * @param meanMotion mean motion (rad/s)
- * @param meanMotionFirstDerivative mean motion first derivative (rad/s²)
- * @param meanMotionSecondDerivative mean motion second derivative (rad/s³)
- * @param e eccentricity
- * @param i inclination (rad)
- * @param pa argument of perigee (rad)
- * @param raan right ascension of ascending node (rad)
- * @param meanAnomaly mean anomaly (rad)
- * @param revolutionNumberAtEpoch revolution number at epoch
- * @param bStar ballistic coefficient
- * @see #FieldTLE(int, char, int, int, String, int, int, FieldAbsoluteDate, CalculusFieldElement, CalculusFieldElement,
- * CalculusFieldElement, CalculusFieldElement, CalculusFieldElement, CalculusFieldElement, CalculusFieldElement, CalculusFieldElement, int, double, TimeScale)
- */
- @DefaultDataContext
- public FieldTLE(final int satelliteNumber, final char classification,
- final int launchYear, final int launchNumber, final String launchPiece,
- final int ephemerisType, final int elementNumber, final FieldAbsoluteDate<T> epoch,
- final T meanMotion, final T meanMotionFirstDerivative,
- final T meanMotionSecondDerivative, final T e, final T i,
- final T pa, final T raan, final T meanAnomaly,
- final int revolutionNumberAtEpoch, final double bStar) {
- this(satelliteNumber, classification, launchYear, launchNumber, launchPiece,
- ephemerisType, elementNumber, epoch, meanMotion,
- meanMotionFirstDerivative, meanMotionSecondDerivative, e, i, pa, raan,
- meanAnomaly, revolutionNumberAtEpoch, bStar,
- DataContext.getDefault().getTimeScales().getUTC());
- }
- /**
- * <p>
- * Simple constructor from already parsed elements using the given time scale as
- * UTC.
- * </p>
- * <p>
- * The mean anomaly, the right ascension of ascending node Ω and the argument of
- * perigee ω are normalized into the [0, 2π] interval as they can be negative.
- * After that, a range check is performed on some of the orbital elements:
- *
- * <pre>
- * meanMotion >= 0
- * 0 <= i <= π
- * 0 <= Ω <= 2π
- * 0 <= e <= 1
- * 0 <= ω <= 2π
- * 0 <= meanAnomaly <= 2π
- * </pre>
- *
- *
- * @param satelliteNumber satellite number
- * @param classification classification (U for unclassified)
- * @param launchYear launch year (all digits)
- * @param launchNumber launch number
- * @param launchPiece launch piece (3 char String)
- * @param ephemerisType type of ephemeris
- * @param elementNumber element number
- * @param epoch elements epoch
- * @param meanMotion mean motion (rad/s)
- * @param meanMotionFirstDerivative mean motion first derivative (rad/s²)
- * @param meanMotionSecondDerivative mean motion second derivative (rad/s³)
- * @param e eccentricity
- * @param i inclination (rad)
- * @param pa argument of perigee (rad)
- * @param raan right ascension of ascending node (rad)
- * @param meanAnomaly mean anomaly (rad)
- * @param revolutionNumberAtEpoch revolution number at epoch
- * @param bStar ballistic coefficient
- * @param utc the UTC time scale.
- */
- public FieldTLE(final int satelliteNumber, final char classification,
- final int launchYear, final int launchNumber, final String launchPiece,
- final int ephemerisType, final int elementNumber, final FieldAbsoluteDate<T> epoch,
- final T meanMotion, final T meanMotionFirstDerivative,
- final T meanMotionSecondDerivative, final T e, final T i,
- final T pa, final T raan, final T meanAnomaly,
- final int revolutionNumberAtEpoch, final double bStar,
- final TimeScale utc) {
- // pi for fields
- final T pi = e.getPi();
- // identification
- this.satelliteNumber = satelliteNumber;
- this.classification = classification;
- this.launchYear = launchYear;
- this.launchNumber = launchNumber;
- this.launchPiece = launchPiece;
- this.ephemerisType = ephemerisType;
- this.elementNumber = elementNumber;
- // orbital parameters
- this.epoch = epoch;
- // Checking mean motion range
- this.meanMotion = meanMotion;
- this.meanMotionFirstDerivative = meanMotionFirstDerivative;
- this.meanMotionSecondDerivative = meanMotionSecondDerivative;
- // Checking inclination range
- this.inclination = i;
- // Normalizing RAAN in [0,2pi] interval
- this.raan = MathUtils.normalizeAngle(raan, pi);
- // Checking eccentricity range
- this.eccentricity = e;
- // Normalizing PA in [0,2pi] interval
- this.pa = MathUtils.normalizeAngle(pa, pi);
- // Normalizing mean anomaly in [0,2pi] interval
- this.meanAnomaly = MathUtils.normalizeAngle(meanAnomaly, pi);
- this.revolutionNumberAtEpoch = revolutionNumberAtEpoch;
- this.bStarParameterDriver = new ParameterDriver(B_STAR, bStar, B_STAR_SCALE,
- Double.NEGATIVE_INFINITY,
- Double.POSITIVE_INFINITY);
- // don't build the line until really needed
- this.line1 = null;
- this.line2 = null;
- this.utc = utc;
- }
- /**
- * Get the UTC time scale used to create this TLE.
- *
- * @return UTC time scale.
- */
- TimeScale getUtc() {
- return utc;
- }
- /** Get the first line.
- * @return first line
- */
- public String getLine1() {
- if (line1 == null) {
- buildLine1();
- }
- return line1;
- }
- /** Get the second line.
- * @return second line
- */
- public String getLine2() {
- if (line2 == null) {
- buildLine2();
- }
- return line2;
- }
- /** Build the line 1 from the parsed elements.
- */
- private void buildLine1() {
- final StringBuilder buffer = new StringBuilder();
- buffer.append('1');
- buffer.append(' ');
- buffer.append(ParseUtils.buildSatelliteNumber(satelliteNumber, "satelliteNumber-1"));
- buffer.append(classification);
- buffer.append(' ');
- buffer.append(ParseUtils.addPadding("launchYear", launchYear % 100, '0', 2, true, satelliteNumber));
- buffer.append(ParseUtils.addPadding("launchNumber", launchNumber, '0', 3, true, satelliteNumber));
- buffer.append(ParseUtils.addPadding("launchPiece", launchPiece, ' ', 3, false, satelliteNumber));
- buffer.append(' ');
- final DateTimeComponents dtc = epoch.getComponents(utc);
- buffer.append(ParseUtils.addPadding("year", dtc.getDate().getYear() % 100, '0', 2, true, satelliteNumber));
- buffer.append(ParseUtils.addPadding("day", dtc.getDate().getDayOfYear(), '0', 3, true, satelliteNumber));
- buffer.append('.');
- // nota: 31250/27 == 100000000/86400
- final int fraction = (int) FastMath.rint(31250 * dtc.getTime().getSecondsInUTCDay() / 27.0);
- buffer.append(ParseUtils.addPadding("fraction", fraction, '0', 8, true, satelliteNumber));
- buffer.append(' ');
- final double n1 = meanMotionFirstDerivative.divide(pa.getPi()).multiply(1.86624e9).getReal();
- final String sn1 = ParseUtils.addPadding("meanMotionFirstDerivative",
- new DecimalFormat(".00000000", SYMBOLS).format(n1),
- ' ', 10, true, satelliteNumber);
- buffer.append(sn1);
- buffer.append(' ');
- final double n2 = meanMotionSecondDerivative.divide(pa.getPi()).multiply(5.3747712e13).getReal();
- buffer.append(formatExponentMarkerFree("meanMotionSecondDerivative", n2, 5, ' ', 8, true));
- buffer.append(' ');
- buffer.append(formatExponentMarkerFree("B*", getBStar(), 5, ' ', 8, true));
- buffer.append(' ');
- buffer.append(ephemerisType);
- buffer.append(' ');
- buffer.append(ParseUtils.addPadding("elementNumber", elementNumber, ' ', 4, true, satelliteNumber));
- buffer.append(Integer.toString(checksum(buffer)));
- line1 = buffer.toString();
- }
- /** Format a real number without 'e' exponent marker.
- * @param name parameter name
- * @param d number to format
- * @param mantissaSize size of the mantissa (not counting initial '-' or ' ' for sign)
- * @param c padding character
- * @param size desired size
- * @param rightJustified if true, the resulting string is
- * right justified (i.e. space are added to the left)
- * @return formatted and padded number
- */
- private String formatExponentMarkerFree(final String name, final double d, final int mantissaSize,
- final char c, final int size, final boolean rightJustified) {
- final double dAbs = FastMath.abs(d);
- int exponent = (dAbs < 1.0e-9) ? -9 : (int) FastMath.ceil(FastMath.log10(dAbs));
- long mantissa = FastMath.round(dAbs * FastMath.pow(10.0, mantissaSize - exponent));
- if (mantissa == 0) {
- exponent = 0;
- } else if (mantissa > (ArithmeticUtils.pow(10, mantissaSize) - 1)) {
- // rare case: if d has a single digit like d = 1.0e-4 with mantissaSize = 5
- // the above computation finds exponent = -4 and mantissa = 100000 which
- // doesn't fit in a 5 digits string
- exponent++;
- mantissa = FastMath.round(dAbs * FastMath.pow(10.0, mantissaSize - exponent));
- }
- final String sMantissa = ParseUtils.addPadding(name, (int) mantissa,
- '0', mantissaSize, true, satelliteNumber);
- final String sExponent = Integer.toString(FastMath.abs(exponent));
- final String formatted = (d < 0 ? '-' : ' ') + sMantissa + (exponent <= 0 ? '-' : '+') + sExponent;
- return ParseUtils.addPadding(name, formatted, c, size, rightJustified, satelliteNumber);
- }
- /** Build the line 2 from the parsed elements.
- */
- private void buildLine2() {
- final StringBuilder buffer = new StringBuilder();
- final DecimalFormat f34 = new DecimalFormat("##0.0000", SYMBOLS);
- final DecimalFormat f211 = new DecimalFormat("#0.00000000", SYMBOLS);
- buffer.append('2');
- buffer.append(' ');
- buffer.append(ParseUtils.buildSatelliteNumber(satelliteNumber, "satelliteNumber-2"));
- buffer.append(' ');
- buffer.append(ParseUtils.addPadding(INCLINATION, f34.format(FastMath.toDegrees(inclination).getReal()), ' ', 8, true, satelliteNumber));
- buffer.append(' ');
- buffer.append(ParseUtils.addPadding("raan", f34.format(FastMath.toDegrees(raan).getReal()), ' ', 8, true, satelliteNumber));
- buffer.append(' ');
- buffer.append(ParseUtils.addPadding(ECCENTRICITY, (int) FastMath.rint(eccentricity.getReal() * 1.0e7), '0', 7, true, satelliteNumber));
- buffer.append(' ');
- buffer.append(ParseUtils.addPadding("pa", f34.format(FastMath.toDegrees(pa).getReal()), ' ', 8, true, satelliteNumber));
- buffer.append(' ');
- buffer.append(ParseUtils.addPadding("meanAnomaly", f34.format(FastMath.toDegrees(meanAnomaly).getReal()), ' ', 8, true, satelliteNumber));
- buffer.append(' ');
- buffer.append(ParseUtils.addPadding(MEAN_MOTION, f211.format(meanMotion.divide(pa.getPi()).multiply(43200.0).getReal()), ' ', 11, true, satelliteNumber));
- buffer.append(ParseUtils.addPadding("revolutionNumberAtEpoch", revolutionNumberAtEpoch,
- ' ', 5, true, satelliteNumber));
- buffer.append(Integer.toString(checksum(buffer)));
- line2 = buffer.toString();
- }
- /** Get the drivers for TLE propagation SGP4 and SDP4.
- * @return drivers for SGP4 and SDP4 model parameters
- */
- public List<ParameterDriver> getParametersDrivers() {
- return Collections.singletonList(bStarParameterDriver);
- }
- /** Get model parameters.
- * @param field field to which the elements belong
- * @return model parameters
- */
- public T[] getParameters(final Field<T> field) {
- final List<ParameterDriver> drivers = getParametersDrivers();
- final T[] parameters = MathArrays.buildArray(field, drivers.size());
- int i = 0;
- for (ParameterDriver driver : drivers) {
- parameters[i++] = field.getZero().add(driver.getValue());
- }
- return parameters;
- }
- /** Get the satellite id.
- * @return the satellite number
- */
- public int getSatelliteNumber() {
- return satelliteNumber;
- }
- /** Get the classification.
- * @return classification
- */
- public char getClassification() {
- return classification;
- }
- /** Get the launch year.
- * @return the launch year
- */
- public int getLaunchYear() {
- return launchYear;
- }
- /** Get the launch number.
- * @return the launch number
- */
- public int getLaunchNumber() {
- return launchNumber;
- }
- /** Get the launch piece.
- * @return the launch piece
- */
- public String getLaunchPiece() {
- return launchPiece;
- }
- /** Get the type of ephemeris.
- * @return the ephemeris type (one of {@link #DEFAULT}, {@link #SGP},
- * {@link #SGP4}, {@link #SGP8}, {@link #SDP4}, {@link #SDP8})
- */
- public int getEphemerisType() {
- return ephemerisType;
- }
- /** Get the element number.
- * @return the element number
- */
- public int getElementNumber() {
- return elementNumber;
- }
- /** Get the TLE current date.
- * @return the epoch
- */
- public FieldAbsoluteDate<T> getDate() {
- return epoch;
- }
- /** Get the mean motion.
- * @return the mean motion (rad/s)
- */
- public T getMeanMotion() {
- return meanMotion;
- }
- /** Get the mean motion first derivative.
- * @return the mean motion first derivative (rad/s²)
- */
- public T getMeanMotionFirstDerivative() {
- return meanMotionFirstDerivative;
- }
- /** Get the mean motion second derivative.
- * @return the mean motion second derivative (rad/s³)
- */
- public T getMeanMotionSecondDerivative() {
- return meanMotionSecondDerivative;
- }
- /** Get the eccentricity.
- * @return the eccentricity
- */
- public T getE() {
- return eccentricity;
- }
- /** Get the inclination.
- * @return the inclination (rad)
- */
- public T getI() {
- return inclination;
- }
- /** Get the argument of perigee.
- * @return omega (rad)
- */
- public T getPerigeeArgument() {
- return pa;
- }
- /** Get Right Ascension of the Ascending node.
- * @return the raan (rad)
- */
- public T getRaan() {
- return raan;
- }
- /** Get the mean anomaly.
- * @return the mean anomaly (rad)
- */
- public T getMeanAnomaly() {
- return meanAnomaly;
- }
- /** Get the revolution number.
- * @return the revolutionNumberAtEpoch
- */
- public int getRevolutionNumberAtEpoch() {
- return revolutionNumberAtEpoch;
- }
- /** Get the ballistic coefficient.
- * @return bStar
- */
- public double getBStar() {
- return bStarParameterDriver.getValue();
- }
- /** Get a string representation of this TLE set.
- * <p>The representation is simply the two lines separated by the
- * platform line separator.</p>
- * @return string representation of this TLE set
- */
- public String toString() {
- try {
- return getLine1() + System.getProperty("line.separator") + getLine2();
- } catch (OrekitException oe) {
- throw new OrekitInternalError(oe);
- }
- }
- /**
- * Convert Spacecraft State into TLE.
- * This converter uses Newton method to reverse SGP4 and SDP4 propagation algorithm
- * and generates a usable TLE version of a state.
- * New TLE epoch is state epoch.
- *
- * <p>
- * This method uses the {@link DataContext#getDefault() default data context},
- * as well as {@link #EPSILON_DEFAULT} and {@link #MAX_ITERATIONS_DEFAULT} for method convergence.
- *
- * @param state Spacecraft State to convert into TLE
- * @param templateTLE first guess used to get identification and estimate new TLE
- * @param <T> type of the element
- * @return TLE matching with Spacecraft State and template identification
- * @see #stateToTLE(FieldSpacecraftState, FieldTLE, TimeScale, Frame)
- * @see #stateToTLE(FieldSpacecraftState, FieldTLE, TimeScale, Frame, double, int)
- * @since 11.0
- */
- @DefaultDataContext
- public static <T extends CalculusFieldElement<T>> FieldTLE<T> stateToTLE(final FieldSpacecraftState<T> state, final FieldTLE<T> templateTLE) {
- return stateToTLE(state, templateTLE,
- DataContext.getDefault().getTimeScales().getUTC(),
- DataContext.getDefault().getFrames().getTEME());
- }
- /**
- * Convert Spacecraft State into TLE.
- * This converter uses Newton method to reverse SGP4 and SDP4 propagation algorithm
- * and generates a usable TLE version of a state.
- * New TLE epoch is state epoch.
- *
- * <p>
- * This method uses {@link #EPSILON_DEFAULT} and {@link #MAX_ITERATIONS_DEFAULT}
- * for method convergence.
- *
- * @param state Spacecraft State to convert into TLE
- * @param templateTLE first guess used to get identification and estimate new TLE
- * @param utc the UTC time scale
- * @param teme the TEME frame to use for propagation
- * @param <T> type of the element
- * @return TLE matching with Spacecraft State and template identification
- * @see #stateToTLE(FieldSpacecraftState, FieldTLE, TimeScale, Frame, double, int)
- * @since 11.0
- */
- public static <T extends CalculusFieldElement<T>> FieldTLE<T> stateToTLE(final FieldSpacecraftState<T> state, final FieldTLE<T> templateTLE,
- final TimeScale utc, final Frame teme) {
- return stateToTLE(state, templateTLE, utc, teme, EPSILON_DEFAULT, MAX_ITERATIONS_DEFAULT);
- }
- /**
- * Convert Spacecraft State into TLE.
- * This converter uses Newton method to reverse SGP4 and SDP4 propagation algorithm
- * and generates a usable TLE version of a state.
- * New TLE epoch is state epoch.
- *
- * @param state Spacecraft State to convert into TLE
- * @param templateTLE first guess used to get identification and estimate new TLE
- * @param utc the UTC time scale
- * @param teme the TEME frame to use for propagation
- * @param epsilon used to compute threshold for convergence check
- * @param maxIterations maximum number of iterations for convergence
- * @param <T> type of the element
- * @return TLE matching with Spacecraft State and template identification
- * @since 11.0
- */
- public static <T extends CalculusFieldElement<T>> FieldTLE<T> stateToTLE(final FieldSpacecraftState<T> state, final FieldTLE<T> templateTLE,
- final TimeScale utc, final Frame teme,
- final double epsilon, final int maxIterations) {
- // Gets equinoctial parameters in TEME frame from state
- final FieldEquinoctialOrbit<T> equiOrbit = convert(state.getOrbit(), teme);
- T sma = equiOrbit.getA();
- T ex = equiOrbit.getEquinoctialEx();
- T ey = equiOrbit.getEquinoctialEy();
- T hx = equiOrbit.getHx();
- T hy = equiOrbit.getHy();
- T lv = equiOrbit.getLv();
- // Rough initialization of the TLE
- final FieldKeplerianOrbit<T> keplerianOrbit = (FieldKeplerianOrbit<T>) OrbitType.KEPLERIAN.convertType(equiOrbit);
- FieldTLE<T> current = newTLE(keplerianOrbit, templateTLE, utc);
- // Field
- final Field<T> field = state.getDate().getField();
- // threshold for each parameter
- final T thrA = sma.add(1).multiply(epsilon);
- final T thrE = FastMath.hypot(ex, ey).add(1).multiply(epsilon);
- final T thrH = FastMath.hypot(hx, hy).add(1).multiply(epsilon);
- final T thrV = sma.getPi().multiply(epsilon);
- int k = 0;
- while (k++ < maxIterations) {
- // recompute the state from the current TLE
- final FieldTLEPropagator<T> propagator = FieldTLEPropagator.selectExtrapolator(current, new InertialProvider(Rotation.IDENTITY, teme), state.getMass(), teme, templateTLE.getParameters(field));
- final FieldOrbit<T> recovOrbit = propagator.getInitialState().getOrbit();
- final FieldEquinoctialOrbit<T> recovEquiOrbit = (FieldEquinoctialOrbit<T>) OrbitType.EQUINOCTIAL.convertType(recovOrbit);
- // adapted parameters residuals
- final T deltaSma = equiOrbit.getA().subtract(recovEquiOrbit.getA());
- final T deltaEx = equiOrbit.getEquinoctialEx().subtract(recovEquiOrbit.getEquinoctialEx());
- final T deltaEy = equiOrbit.getEquinoctialEy().subtract(recovEquiOrbit.getEquinoctialEy());
- final T deltaHx = equiOrbit.getHx().subtract(recovEquiOrbit.getHx());
- final T deltaHy = equiOrbit.getHy().subtract(recovEquiOrbit.getHy());
- final T deltaLv = MathUtils.normalizeAngle(equiOrbit.getLv().subtract(recovEquiOrbit.getLv()), field.getZero());
- // check convergence
- if (FastMath.abs(deltaSma.getReal()) < thrA.getReal() &&
- FastMath.abs(deltaEx.getReal()) < thrE.getReal() &&
- FastMath.abs(deltaEy.getReal()) < thrE.getReal() &&
- FastMath.abs(deltaHx.getReal()) < thrH.getReal() &&
- FastMath.abs(deltaHy.getReal()) < thrH.getReal() &&
- FastMath.abs(deltaLv.getReal()) < thrV.getReal()) {
- return current;
- }
- // update state
- sma = sma.add(deltaSma);
- ex = ex.add(deltaEx);
- ey = ey.add(deltaEy);
- hx = hx.add(deltaHx);
- hy = hy.add(deltaHy);
- lv = lv.add(deltaLv);
- final FieldEquinoctialOrbit<T> newEquiOrbit =
- new FieldEquinoctialOrbit<>(sma, ex, ey, hx, hy, lv, PositionAngle.TRUE,
- equiOrbit.getFrame(), equiOrbit.getDate(), equiOrbit.getMu());
- final FieldKeplerianOrbit<T> newKeplOrbit = (FieldKeplerianOrbit<T>) OrbitType.KEPLERIAN.convertType(newEquiOrbit);
- // update TLE
- current = newTLE(newKeplOrbit, templateTLE, utc);
- }
- throw new OrekitException(OrekitMessages.UNABLE_TO_COMPUTE_TLE, k);
- }
- /**
- * Converts an orbit into an equinoctial orbit expressed in TEME frame.
- *
- * @param orbitIn the orbit to convert
- * @param teme the TEME frame to use for propagation
- * @param <T> type of the element
- * @return the converted orbit, i.e. equinoctial in TEME frame
- */
- private static <T extends CalculusFieldElement<T>> FieldEquinoctialOrbit<T> convert(final FieldOrbit<T> orbitIn, final Frame teme) {
- return new FieldEquinoctialOrbit<T>(orbitIn.getPVCoordinates(teme), teme, orbitIn.getMu());
- }
- /**
- * Builds a new TLE from Keplerian parameters and a template for TLE data.
- * @param keplerianOrbit the Keplerian parameters to build the TLE from
- * @param templateTLE TLE used to get object identification
- * @param utc the UTC time scale
- * @param <T> type of the element
- * @return TLE with template identification and new orbital parameters
- */
- private static <T extends CalculusFieldElement<T>> FieldTLE<T> newTLE(final FieldKeplerianOrbit<T> keplerianOrbit, final FieldTLE<T> templateTLE,
- final TimeScale utc) {
- // Keplerian parameters
- final T meanMotion = keplerianOrbit.getKeplerianMeanMotion();
- final T e = keplerianOrbit.getE();
- final T i = keplerianOrbit.getI();
- final T raan = keplerianOrbit.getRightAscensionOfAscendingNode();
- final T pa = keplerianOrbit.getPerigeeArgument();
- final T meanAnomaly = keplerianOrbit.getMeanAnomaly();
- // TLE epoch is state epoch
- final FieldAbsoluteDate<T> epoch = keplerianOrbit.getDate();
- // Identification
- final int satelliteNumber = templateTLE.getSatelliteNumber();
- final char classification = templateTLE.getClassification();
- final int launchYear = templateTLE.getLaunchYear();
- final int launchNumber = templateTLE.getLaunchNumber();
- final String launchPiece = templateTLE.getLaunchPiece();
- final int ephemerisType = templateTLE.getEphemerisType();
- final int elementNumber = templateTLE.getElementNumber();
- // Updates revolutionNumberAtEpoch
- final int revolutionNumberAtEpoch = templateTLE.getRevolutionNumberAtEpoch();
- final T dt = epoch.durationFrom(templateTLE.getDate());
- final int newRevolutionNumberAtEpoch = (int) ((int) revolutionNumberAtEpoch + FastMath.floor(MathUtils.normalizeAngle(meanAnomaly, e.getPi()).add(dt.multiply(meanMotion)).divide(e.getPi().multiply(2.0))).getReal());
- // Gets B*
- final double bStar = templateTLE.getBStar();
- // Gets Mean Motion derivatives
- final T meanMotionFirstDerivative = templateTLE.getMeanMotionFirstDerivative();
- final T meanMotionSecondDerivative = templateTLE.getMeanMotionSecondDerivative();
- // Returns the new TLE
- return new FieldTLE<>(satelliteNumber, classification, launchYear, launchNumber, launchPiece, ephemerisType,
- elementNumber, epoch, meanMotion, meanMotionFirstDerivative, meanMotionSecondDerivative,
- e, i, pa, raan, meanAnomaly, newRevolutionNumberAtEpoch, bStar, utc);
- }
- /** Check the lines format validity.
- * @param line1 the first element
- * @param line2 the second element
- * @return true if format is recognized (non null lines, 69 characters length,
- * line content), false if not
- */
- public static boolean isFormatOK(final String line1, final String line2) {
- return TLE.isFormatOK(line1, line2);
- }
- /** Compute the checksum of the first 68 characters of a line.
- * @param line line to check
- * @return checksum
- */
- private static int checksum(final CharSequence line) {
- int sum = 0;
- for (int j = 0; j < 68; j++) {
- final char c = line.charAt(j);
- if (Character.isDigit(c)) {
- sum += Character.digit(c, 10);
- } else if (c == '-') {
- ++sum;
- }
- }
- return sum % 10;
- }
- /**
- * Convert FieldTLE into TLE.
- * @return TLE
- */
- public TLE toTLE() {
- final TLE regularTLE = new TLE(getSatelliteNumber(), getClassification(), getLaunchYear(), getLaunchNumber(), getLaunchPiece(), getEphemerisType(),
- getElementNumber(), getDate().toAbsoluteDate(), getMeanMotion().getReal(), getMeanMotionFirstDerivative().getReal(),
- getMeanMotionSecondDerivative().getReal(), getE().getReal(), getI().getReal(), getPerigeeArgument().getReal(),
- getRaan().getReal(), getMeanAnomaly().getReal(), getRevolutionNumberAtEpoch(), getBStar(), getUtc());
- for (int k = 0; k < regularTLE.getParametersDrivers().size(); ++k) {
- regularTLE.getParametersDrivers().get(k).setSelected(getParametersDrivers().get(k).isSelected());
- }
- return regularTLE;
- }
- /** Check if this tle equals the provided tle.
- * <p>Due to the difference in precision between object and string
- * representations of TLE, it is possible for this method to return false
- * even if string representations returned by {@link #toString()}
- * are equal.</p>
- * @param o other tle
- * @return true if this tle equals the provided tle
- */
- @Override
- public boolean equals(final Object o) {
- if (o == this) {
- return true;
- }
- if (!(o instanceof FieldTLE)) {
- return false;
- }
- @SuppressWarnings("unchecked")
- final FieldTLE<T> tle = (FieldTLE<T>) o;
- return satelliteNumber == tle.satelliteNumber &&
- classification == tle.classification &&
- launchYear == tle.launchYear &&
- launchNumber == tle.launchNumber &&
- Objects.equals(launchPiece, tle.launchPiece) &&
- ephemerisType == tle.ephemerisType &&
- elementNumber == tle.elementNumber &&
- Objects.equals(epoch, tle.epoch) &&
- meanMotion.getReal() == tle.meanMotion.getReal() &&
- meanMotionFirstDerivative.getReal() == tle.meanMotionFirstDerivative.getReal() &&
- meanMotionSecondDerivative.getReal() == tle.meanMotionSecondDerivative.getReal() &&
- eccentricity.getReal() == tle.eccentricity.getReal() &&
- inclination.getReal() == tle.inclination.getReal() &&
- pa.getReal() == tle.pa.getReal() &&
- raan.getReal() == tle.raan.getReal() &&
- meanAnomaly.getReal() == tle.meanAnomaly.getReal() &&
- revolutionNumberAtEpoch == tle.revolutionNumberAtEpoch &&
- getBStar() == tle.getBStar();
- }
- /** Get a hashcode for this tle.
- * @return hashcode
- */
- @Override
- public int hashCode() {
- return Objects.hash(satelliteNumber,
- classification,
- launchYear,
- launchNumber,
- launchPiece,
- ephemerisType,
- elementNumber,
- epoch,
- meanMotion,
- meanMotionFirstDerivative,
- meanMotionSecondDerivative,
- eccentricity,
- inclination,
- pa,
- raan,
- meanAnomaly,
- revolutionNumberAtEpoch,
- getBStar());
- }
- }