Significant PV Discrepancy in KeplerianOrbit Conversion (Orekit 12.0.1) vs. STK (J2000, Two-Body) for Non-Zero RAAN

Significant PV Discrepancy in KeplerianOrbit Conversion (Orekit 12.0.1) vs. STK (J2000, Two-Body) for Non-Zero RAAN

Hello Orekit Community,

I am experiencing a significant discrepancy when converting Keplerian orbital elements to inertial PV coordinates (EME2000/J2000) using Orekit 12.0.1 compared to the results obtained from STK using the same elements and the Two-Body propagator with the J2000 coordinate system.

Problem Description:

The discrepancy primarily occurs when the Right Ascension of the Ascending Node (RAAN) is non-zero.

  • When RAAN = 0: The initial PV coordinates (position [x, y, z] and velocity [vx, vy, vz]) calculated by Orekit’s KeplerianOrbit.getPVCoordinates() at the epoch are very close to the values reported by STK for the same epoch.
  • When RAAN != 0: The initial PV coordinates calculated by Orekit show a large difference compared to STK, specifically in the y and z components of the position vector (and potentially velocity). The x component remains relatively close.

This suggests the issue might stem from how the rotation associated with RAAN (and potentially inclination) is handled during the conversion from Keplerian elements to Cartesian PV coordinates in Orekit, compared to STK’s implementation, even when both are using the standard J2000/EME2000 frame and a simple Two-Body model.

Minimal Reproducible Example (MCVE):

Here is a simple Java code snippet using Orekit 12.0.1 demonstrating the calculation for a non-zero RAAN case:

import org.hipparchus.geometry.euclidean.threed.Vector3D;
import org.orekit.data.DataContext;
import org.orekit.data.DirectoryCrawler;
import org.orekit.frames.Frame; // Added import
import org.orekit.frames.FramesFactory;
import org.orekit.orbits.KeplerianOrbit;
import org.orekit.orbits.PositionAngleType;

import org.orekit.time.AbsoluteDate;
import org.orekit.time.TimeScalesFactory;
import org.orekit.utils.Constants;
import org.orekit.utils.PVCoordinates;

import java.io.File;

public class TestPVDiscrepancy {

    public static void main(String[] args) {

        // --- Orekit Initialization ---
        File orekitData = new File("/path/to/my/orekit-data");
        if (!orekitData.exists()) {
            System.err.println("Error: orekit-data directory not found at " + orekitData.getAbsolutePath());
            return;
        }
        DataContext.getDefault().getDataProvidersManager().addProvider(new DirectoryCrawler(orekitData));

        // --- Input Keplerian Elements (Identical to STK Input) ---
        double semiMajorAxis = 6878.14 * 1000; // meters (approx. 500 km altitude)
        double eccentricity = 0.001;           // Near-circular
        double inclination = 45.0;             // degrees
        double raan = 36.0;                    // degrees (Non-zero RAAN case)
        double argumentOfPerigee = 0.0;        // degrees
        double trueAnomaly = 0.0;              // degrees (Initial position at ascending node)
        AbsoluteDate epoch = new AbsoluteDate(2025, 1, 1, 4, 0, 0.0, TimeScalesFactory.getUTC());

        // --- Reference Frame ---
        Frame inertialFrame = FramesFactory.getEME2000(); // Using EME2000 (J2000 equivalent)

        try {
            // --- Create KeplerianOrbit object ---
            KeplerianOrbit orbit = new KeplerianOrbit(
                    semiMajorAxis,
                    eccentricity,
                    Math.toRadians(inclination),
                    Math.toRadians(raan),
                    Math.toRadians(argumentOfPerigee),
                    Math.toRadians(trueAnomaly),
                    PositionAngleType.TRUE, // Using true anomaly
                    inertialFrame,
                    epoch,
                    Constants.WGS84_EARTH_MU
            );

            // --- Get PV Coordinates directly from the Orbit object at epoch ---
            PVCoordinates pvOrekit = orbit.getPVCoordinates(inertialFrame); // Gets PV at orbit's epoch

            // --- Output Orekit Results ---
            Vector3D posOrekit = pvOrekit.getPosition();
            Vector3D velOrekit = pvOrekit.getVelocity();

            System.out.println("--- Orekit 12.0.1 Results (EME2000) ---");
            System.out.println("Epoch: " + epoch);
            System.out.println("Input RAAN (deg): " + raan);
            System.out.println("Calculated Position (m): " + posOrekit);
            System.out.println("  X: " + posOrekit.getX());
            System.out.println("  Y: " + posOrekit.getY());
            System.out.println("  Z: " + posOrekit.getZ());
            System.out.println("Calculated Velocity (m/s): " + velOrekit);
            System.out.println("  Vx: " + velOrekit.getX());
            System.out.println("  Vy: " + velOrekit.getY());
            System.out.println("  Vz: " + velOrekit.getZ());

            // --- STK Comparison Results (Manual Input Required) ---
            System.out.println("\\n--- STK Results (J2000, Two-Body) ---");
            System.out.println("Epoch: " + epoch + " (Please verify time matches)");
            System.out.println("Input RAAN (deg): " + raan);
            double stk_x =  5564529.722; 
            double stk_y =   4042867.492 ; 
            double stk_z = -1.348  ; 
            double stk_vx =  -3164.004 ;   
            double stk_vy =  4354.879 ;   
            double stk_vz = 5382.927;   
            System.out.println("Reported Position (m): X=" + stk_x + ", Y=" + stk_y + ", Z=" + stk_z);
            System.out.println("Reported Velocity (m/s): Vx=" + stk_vx + ", Vy=" + stk_vy + ", Vz=" + stk_vz);

            // --- Calculate Differences ---
            System.out.println("\\n--- Differences (Orekit - STK) ---");
            System.out.println("  Delta X (m): " + (posOrekit.getX() - stk_x));
            System.out.println("  Delta Y (m): " + (posOrekit.getY() - stk_y)); // Expected to be large
            System.out.println("  Delta Z (m): " + (posOrekit.getZ() - stk_z)); // Expected to be large
            System.out.println("  Delta Vx (m/s): " + (velOrekit.getX() - stk_vx));
            System.out.println("  Delta Vy (m/s): " + (velOrekit.getY() - stk_vy)); // Potentially large
            System.out.println("  Delta Vz (m/s): " + (velOrekit.getZ() - stk_vz)); // Potentially large
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

  • Orekit Version: 12.0.1
  • Java Version:JDK 21
  • Operating System: Windows 11
  • STK Version: 11

Hi,

In Orekit, for some reason you need to pass the argument of perigee before the raan. That’s why you’re getting these differences. It got me a few times as well at the begining. But you can expect Orekit to be well validated for such things.

Cheers,
Romain

1 Like

Hi Romain (and everyone),

Thank you so much for the quick and accurate reply!

You were absolutely right. The issue was indeed the order of the argumentOfPerigee and raan parameters in the KeplerianOrbit constructor. I had them swapped in my code.

After correcting the order, ensuring that argumentOfPerigee (converted to radians) is passed as the fourth angle parameter and raan (converted to radians) is passed as the fifth angle parameter:

// Corrected order:
new KeplerianOrbit(
    a, e, i_rad,
    omega_rad, // Argument of Perigee (ω)
    raan_rad,  // RAAN (Ω)
    nu_rad,    // True Anomaly (ν)
    PositionAngleType.TRUE,
    inertialFrame,
    epoch,
    mu
);

I re-ran my test case (Test_pv.java comparing initial PV coordinates for non-zero RAAN) and the Orekit results now match the STK (J2000, Two-Body) results very closely, with only minor expected numerical differences.

This also resolved the large discrepancies I was seeing in the subsequent visibility analysis results when RAAN was non-zero.

I really appreciate you pointing this out – it seems like a subtle detail that can easily be overlooked, especially when comparing with other tools or documentation that might use a different convention. It’s a good reminder to always double-check the specific API documentation for parameter order.

Thanks again for the great support and the fantastic library!

Best regards,

jayden

3 Likes