Convert Cartesian to Osculating Elements when propagating TLEs with SGP4

Hi all,

I’m trying to do something quite simple which is to propagate a TLE with SGP4 and output the osculating elements. My basic method is as follows:

  • Create TLE object from a TLE string
  • Propagate TLE with TLEPropagator class and return PVCoordinates
  • Create KeplerianOrbit object with PVCoordinates
  • Get osculating elements

However I am getting some very weird results that don’t match the original TLE when I try to retrieve the osculating elements from the keplerian orbit object. Here is my code:

public class TestRun {

    public static void main(String[] args) {
        try {

            // configure Orekit
            File home       = new File(System.getProperty("user.home"));
            File orekitData = new File(home, "orekit-data");
            if (!orekitData.exists()) {
                System.err.format(Locale.US, "Failed to find %s folder%n",
                                  orekitData.getAbsolutePath());
                System.err.format(Locale.US, "You need to download %s from the %s page and unzip it in %s for this tutorial to work%n",
                                  "orekit-data.zip", "https://www.orekit.org/forge/projects/orekit/files",
                                  home.getAbsolutePath());
                System.exit(1);
            }
            DataProvidersManager manager = DataProvidersManager.getInstance();
            manager.addProvider(new DirectoryCrawler(orekitData));
            
            // ==========================================================
            // === TEST TLE AND PRINT OUTPUT USING GETTER FUNCTIONS ==== 
            // ========================================================== 

            String TLE1 = "1 25544U 98067A   18305.05496528  .00001091  00000-0  23925-4 0  9992" ;
            String TLE2 = "2 25544  51.6422  56.6674 0004353 359.1206 352.3236 15.53882167139796" ;

                         
            TLE ISS = new TLE(TLE1,TLE2);
            System.out.println("===== DIRECT GETTERS FROM TLE =====");			// All of these are good and match TLE
            System.out.println("RAAN = " + Math.toDegrees(ISS.getRaan()));
            System.out.println("ArgP = " + Math.toDegrees(ISS.getPerigeeArgument()));
            System.out.println("Inc  = " + Math.toDegrees(ISS.getI()));
            System.out.println("RAAN = " + Math.toDegrees(ISS.getMeanAnomaly()));
            System.out.println("ECC  = " + ISS.getE());
            System.out.println("n    = " + ISS.getMeanMotion());

            //=== TRY TO  PROPGATE AND GET OSCULATING RAAN ===          	
            AbsoluteDate ISS_EPOCH = ISS.getDate();
            AbsoluteDate PROPAGATION_END = ISS_EPOCH.shiftedBy(0);  // For now let end date = epoch
                        	
            TLEPropagator TLEProp = TLEPropagator.selectExtrapolator(ISS);
            PVCoordinates PVCs = TLEProp.getPVCoordinates(ISS_EPOCH);
            KeplerianOrbit OSCULATING_ORBIT = new KeplerianOrbit(PVCs, TLEProp.getFrame(), ISS_EPOCH, TLEProp.getMU());
            
                 
            System.out.println("\n===== GETTERS FROM PROPAGATOR =====");			// Different values!
            System.out.println("RAAN = " + Math.toDegrees(OSCULATING_ORBIT.getRightAscensionOfAscendingNode()));	    // PROP VALUE	ACTUAL (TLE) VALUE
            System.out.println("ArgP = " + Math.toDegrees(OSCULATING_ORBIT.getPerigeeArgument()));						//   27.884           359.121 
            System.out.println("Inc  = " + Math.toDegrees(OSCULATING_ORBIT.getI()));
            System.out.println("RAAN = " + normalize(Math.toDegrees(OSCULATING_ORBIT.getMeanAnomaly())));	//    323               352
            System.out.println("ECC  = " + OSCULATING_ORBIT.getE());													//   0.00139          0.000435
            System.out.println("n    = " + OSCULATING_ORBIT.getKeplerianMeanMotion());
        

        } catch (OrekitException oe) {
            System.err.println(oe.getMessage());
        }
    }
    
    public static double normalize(double x)
    {
    	x = x%360;
    	if(x < 0)
    	{
    		x += 360;
    	}
    	
    	return x;
    }
}

My output is the following:

===== DIRECT GETTERS FROM TLE =====
RAAN = 56.66740000000001
ArgP = 359.1206
Inc  = 51.6422
RAAN = 352.3236
ECC  = 4.353E-4
n    = 0.0011300150000905992

===== GETTERS FROM PROPAGATOR =====
RAAN = 56.65980249825597
ArgP = 27.883237285318526
Inc  = 51.66130314202295
RAAN = 323.55441051137046
ECC  = 0.0013877786208203007
n    = 0.0011284606807158077

Clearly the Argument of perigee, RAAN and eccentricity are way off. Does anybody know how I can solve this or where I may be going wrong?

256.2684 + 103.749 = 360. Both are the same angle. If you need the range of the angle to be [0, 2pi] use MathUtils.normalizeAngle(angle, PI). You can make your code a bit simpler by using new KeplerianOrbit(propagate(ISS_EPOCH)) instead of getPVCoordinates() and the next line.

Best Regards,
Evan

1 Like

Hi Even,

Please see my edit to the post. After looking at the other orbital elements it appears that there is something else going on here not related to the elements not being normalized.

Best Regards,

hi,

I don’t know if it is the problem, but TLE orbital parameters are not exactly keplerian elements as considered in Orekit KeplerianOrbit.
The way they are written in a TLE, they cannot be used directly in a KeplerianOrbit. That’s why we have to use a specific TLE propagator to get the cartesian elements, and then get the real keplerian elements.

The Orekit Keplerian elements and the TLE elements cannot be compared.

Best regards,
Romaric

Hi @no-radical

On the TLE.java class header you can read that :

Any attempt to directly use orbital parameters like eccentricity , inclination , etc. without any reference to the TLE propagator is prone to errors.

So, the differences on the orbital elements between your two outputs are normal. You should refer to the TLE propagator to have the correct orbital elements.

Best regards,
Bryan

TLE is the input data for SGP4/SDP4 which is a mean model.
As such, it has its own equations to model the link between Cartesian and orbital elements,
which is clearly different from the Keplerian model.

This is exactly why converting back and forth between TLE / Cartesian / Keplerian should never be
attempted on a single point. When converting TLE to anything else, the classical method is to fit
the two models (SGP4 on one side and your other propagator, be it Keplerian or a full blown numerical
propagator) on a time span that is a few orbits long. In this case, the fitting takes care to at least
center one model around the other over the time span.

If for example you look at you Keplerian output not only at the same epoch as TLE but over a long
time range, you will see it evolves. As these osculating parameters are time varying, there is no
reasons they should match the TLE ones at epoch, which are (roughly) mean orbital parameters.

@luc

I understand this is an older discussion. But I was wondering how this should be done based on what you said: “When converting TLE to anything else, the classical method is to fit
the two models (SGP4 on one side and your other propagator, be it Keplerian or a full blown numerical
propagator) on a time span that is a few orbits long.”

I understand with using Orekit is even if I want to use a numerical propagator I would still need a initial state to start and in the case of convention from TLE would my initial state be becoming from TLE even for the numerical propagator? Do you mind elaborate on your explanation. Thank you.