Converting classical orbit elements to TLEs : Orekit + astrodynamics fundamentals

Hi,

I skimmed through several resources but wasn’t able to get a clear answer to my questions. Hence reaching out here for some clarity.

I am trying to convert a set of 6 orbit elements (COEs) to TLEs but while doing so i am running into some issues and i am not sure if my approach is right. Any advice will be much appreciated.

For the conversion to TLEs since we need mean elements, I first computed the cartesian position (R) and velocity (V) vectors from the COEs.

Now since “OsculatingToMeanElementsConverter” requires SpacecraftState, satelliteRevolution, propagator, positionScale, i have:

pv = PVCoordinates(Vector3D(float(R[0]),float(R[1]),float(R[2])), Vector3D(float(V[0]),float(V[1]),float(V[2])))

state = SpacecraftState(CartesianOrbit(pv, frame, epoch_date, mu))

minStep = 1e-3
maxStep = 1000
abs_tol = 1e-4
rel_tol = 1e-8

integrator = DormandPrince853Integrator(minStep, maxStep, abs_tol, rel_tol)
propagator = DSSTPropagator(integrator, PropagationType.MEAN)

satelliteRevolution = 2
positionScale = 0.1
osc_to_mean_converter = OsculatingToMeanElementsConverter(state, satelliteRevolution, propagator, positionScale)
mean_state = osc_to_mean_converter.convert()

Now from this mean state, to construct the TLE, I need MeanMotion, meanMotionFirstDerivative, meanMotionSecondDerivative, eccentricity, Argument of Perigee, RAAN, Mean Anomaly. BStar.

My questions are:

  1. The orbit element parameters in a TLE (eccentricity, inclination, AOP, RAAN), are these the mean values or the osculating values? If these are osculating then i already have most of them from my initial COE and i can compute MeanMotion by using mean_state.getKeplerianMeanMotion(). However how do i compute meanMotionFirstDerivative, meanMotionSecondDerivative, and Bstar?

  2. This might be a silly question but from my understanding so far, the orbit elements in the TLE seem to be the classical orbit elements (osculating values). If that is the case, why do we even need to compute the mean elements at that instant to compute the TLEs?

  3. If the orbit elements are mean orbit elements, how do i get the RAAN and AOP from the mean state that i ahve computed? Using mean_state.getRightAscensionOfAscendingNode() and mean_state.getPerigeeArgument() give error:
    'org.orekit.propagation.SpacecraftState' object has no attribute 'getRightAscensionOfAscendingNode' and 'getPerigeeArgument()'. And how do i compute meanMotionFirstDerivative, meanMotionSecondDerivative, and Bstar?

From this i am hoping to learn about what TLEs represent and also how to best use orekit to convert from COE to TLE. Any help will be much appreciated.

Eagerly waiting to hear back.

Hi Stella28,
To address your numbered questions,

  1. The TLE parameters are mean values as defined in the SGP4 theory. The mean motion is related to the “energy” semi-major axis but the eccentricity and argument of perigee reflect the motion about a stable eccentricity vector that is altitude and inclination dependent.
  2. Theoretically, DSST elements can be converted to TLE elements with simple algebraic expressions.
    I have only recently started dipping my toes in the orekit pond, so I am not certain whether the DSST propagator will interpret the input state as an osculating or mean state.
    In general, and depending on accuracy requirements, the procedure for getting TLE parameters is to propagate the osculating state with a numerical propagator of appropriate fidelity and fit the TLE parameters to the resulting trajectory. Perhaps some of the more experienced member can suggest utilities in OREKIT to assist with that.

Best,
Mike

1 Like

Hi Stella,

The elements in TLE are mean values, if you want to generate a TLE from a set of COEs, you need to do the orbit determination which you can refer to the Orekit tutorials TLEBasedOrbitDetermination.

In this tutorial, the configuration file in YAML format has been defined, you can change the measurementFiles field to your COE files with specific format like PV measurements.

All the elements in TLE will be estimated in this tutorial. If you want to estimate Bstar, you can add the code templateTLE.getParametersDrivers().get(0).setSelected(true); in init function.

TLE can not be estimated by the DSSTPropagator but the TLEPropagator which use the SGP4/SDP4 theory to propagate the orbit and calculate partial derivatives.

Also, a single point can be used to generate a TLE, you can refer to the API tle.generation.FixedPointTleGenerationAlgorithm.generate. But the Bstar won’t be estimated if you use single point.

1 Like

@gmar @JinchengZhu Thank you very much for your responses. Appreciate the time you are taking out to make this platform interactive and informative.

@JinchengZhu , Thank you for the recommendation on the FixedPointTleGenerationAlgorithm. I didn’t know this existed before you had pointed out.

I tried implementing this for my purpose but kept running into the following error:
org.orekit.errors.OrekitException: too large eccentricity for propagation model: e = 1

I am working with a state corresponding to a circular orbit and wasn’t expecting this error. Any idea why this might be happening?

Below is a snippet of my code:

utc = TimeScalesFactory.getUTC()
epoch_date = AbsoluteDate(epoch_date[0], epoch_date[1], epoch_date[2], epoch_date[3], epoch_date[4], epoch_date[5], utc)

orbit = CartesianOrbit(pv0, frame, epoch_date, mu)
initialState = SpacecraftState(orbit)

# Generate templateTLE lines
satNum = TLE_param_sat[0]
classification = TLE_param_sat[1]     # 'U' for unclassified
launch_year = TLE_param_sat[2]        # Year of launch
launch_number = TLE_param_sat[3]      # Launch number of the year
launch_piece = TLE_param_sat[4]       # Launch piece (e.g., 'A', 'B', etc.)
revolution_number = TLE_param_sat[5]  # Revolution number at epoch
ephem_type = 0

template_tle = TLE(
        satNum, classification, launch_year, launch_number, launch_piece,
        ephem_type, 999, epoch_date, 0, 
        0.0, 0.0, 0, 
        0, 0,
        0, 0, revolution_number, 0.0)

epsilon = 1.0E-8
max_itrs = 100
scale = 1

tle_generator = FixedPointTleGenerationAlgorithm(epsilon, max_itrs, scale)
tle = tle_generator.generate(initialState, template_tle)

Instead of using all 0s for the keplerian elements in the template TLE, i also tried using values from KeplerianOrbit(pv0, frame, epoch_date, mu) as reference. However that also gave the same error.

Any advice will be much appreciated.

Thank you.

Hi @Stella28,

Looks like you end up with a hyperbolic orbit that the SGP4 model isn’t able to handle.

Could you send us a runnable example so we can investigate the problem? (with actual values for date, pv0, TLE_param_sat[…] etc.)

Cheers,
Maxime

1 Like

@MaximeJ Here you go!

import numpy as np
import orekit
orekit.initVM()
import org.orekit as ok
from org.orekit.utils import PVCoordinates
from org.orekit.frames import FramesFactory
from org.orekit.time import AbsoluteDate, TimeScalesFactory
from org.orekit.utils import Constants
from org.orekit.orbits import CartesianOrbit
from org.orekit.propagation import SpacecraftState
from org.orekit.propagation.analytical.tle import TLE
from org.orekit.propagation.analytical.tle.generation import FixedPointTleGenerationAlgorithm

import java
from org.hipparchus.geometry.euclidean.threed import Vector3D

# Path to orbital-env-data folder
javafile = java.io.File(ORBITAL_ENVIRONMENT_DATA_PATH)
data_context = ok.data.DataContext.getDefault()
dpm = data_context.getDataProvidersManager()
dc = ok.data.DirectoryCrawler(javafile)
dpm.clearProviders()
dpm.addProvider(dc)

R = np.array([7878.137,0.0,0.0])
V = np.array([0.0, 3.55653533, 6.1600999])
pv = PVCoordinates(Vector3D(float(R[0]),float(R[1]),float(R[2])), Vector3D(float(V[0]),float(V[1]),float(V[2])))

frame = FramesFactory.getEME2000()
utc = TimeScalesFactory.getUTC()
epoch_date = AbsoluteDate(2013, 7, 1, 12, 30, 0, utc)

orbit = CartesianOrbit(pv, frame, epoch_date, Constants.WGS84_EARTH_MU)
initialState = SpacecraftState(orbit)

satNum = 10000
classification = 'U' # 'U' for unclassified
launch_year = 2010 # Year of launch
launch_number = 99 # Launch number of the year
launch_piece = 'A' # Launch piece (e.g., 'A', 'B', etc.)
revolution_number = 1 # Revolution number at epoch
ephem_type = 0

template_tle = TLE(satNum, classification, launch_year, launch_number, launch_piece,ephem_type, 999, epoch_date, 0, 0.0, 0.0, 0, 0, 0, 0, 0, revolution_number, 0.0)

tle_generator = FixedPointTleGenerationAlgorithm(1.0E-8, 200, 1)
tle = tle_generator.generate(initialState, template_tle)

@Stella28,

This is in kilometers. Orekit works only with SI units. So you should multiply R and V by 1000 to convert them in meters.
The results should be better, hopefully :wink:

1 Like

@MaximeJ That was spot on! Thank you.

I do have a few questions after looking at the resulting TLE.

My input orbit elements of the circular orbit were:
sma = 7878.137km , ecc = 0.0, inc = 60 deg, RAAN = 0.0 deg, AOP = 0.0 deg, True anomaly = 0.0 deg.

The output TLE is:

<TLE: 1 10000U 10099A   13182.52083333  .00000000  00000-0  00000-0 0  9992
2 10000  59.9847   0.1287 0009821 237.0639 123.0244 12.42885212    15>

The mean RAAN (=0.1287 deg), AOP (=237.0639 deg), Mean anomaly (=123.02 deg) look significantly different than the input values. I understand the TLE is supposed to represent the mean values at that instant, but even then the values look pretty off.

Can the issue be happening because argument of perigee and true anomaly are undefined for a circular orbit?
Because i would at least expect the mean anomaly value in the TLE to be close to the true anomaly value (since mean anomaly = true anomaly for circular orbits).

I am not entirely sure how we are constructing the mean orbit elements and the TLEs in this case internally. Any comments?

With an eccentricity equal to 0, perigee is not defined, so you need to compare AOP + anomaly (i.e.the sum of the angles) instead of the angles themselves. In the input values, you have 0 + 0 = 0 and in the output you have 237.0639 + 123.0244 = 360.0883, which is close enough to a full number of turns.

2 Likes

@luc Thank you for pointing that out. That makes total sense.

PS: Really appreciate everyone contributing to this dynamic Orekit forum. I learned a lot!