Hi, I have been trying to do some comparisons between the output of a TLE propagation (with Orekit SGP4), and the output of a parsed .ocm file (a CPF of SwarmC that I got from SpaceBook).
I think I have all the blocks working, but this is my first time using the ocm parser of Orekit, so maybe there is something wrong in the way I use it. I know there is something wrong because of the shape of the CPF-based ephemerides trajectory:
Both are supposed to be in ECI (EME2000), but the blue one is not a real inertial trajectory… Some conversion is either not happening or I am doing it wrong. That is 4 days of propagation.
This is the general shape of the code (minus the imports):
@dataclass
class OrekitParser:
@classmethod
def parse_ocm(cls, file_path: str) -> Ocm:
parser = ParserBuilder().buildOcmParser()
src = DataSource(file_path)
parsed = parser.parse(src)
return Ocm.cast_(parsed)
@classmethod
def get_propagators_from_ocm(cls, ocm: Ocm) -> List[EphemerisSegmentPropagator]:
tray_blocks = list(ocm.getData().getTrajectoryBlocks())
sat_map = ocm.getSatellites()
sat_dict = {key: sat_map.get(key) for key in sat_map.keySet()}
propagators = []
for id, sat_epm in sat_dict.items():
sat_iface = EphemerisFile.SatelliteEphemeris.cast_(sat_epm)
propagators.append(sat_iface.getPropagator())
return propagators, tray_blocks
tle ="1 39453U 13067C 25192.21673508 .00005034 00000-0 11431-3 0 9997\n2 39453 87.3412 180.4819 0003176 90.8504 269.3117 15.42753470653556"
orekitTle = TLE(tleLine1, tleLine2)
pointing = FrameAlignedProvider(inertial_frame)
sgp4Propagator = SGP4(orekitTle, pointing, mass)
sgp4_positions = []
for orekit_epoch in orekit_t_grid:
orbit = sgp4Propagator.propagateOrbit(orekit_epoch)
tlePV_ECI = orbit.getPVCoordinates(EME2000)
sgp4_positions.append(tlePV_ECI.getPosition().toArray())
sgp4_positions = np.array(sgp4_positions) / 1000
NORAD_ID_TO_TEST = 39453
CPF_FILE = "data/spacebook/sata_data/ref_ephem/39453_2025-07-10_CPF.ocm"
ocm = OrekitParser.parse_ocm(CPF_FILE )
# Assuming the first propagator in the file corresponds to our satellite
orekit_propagators, tray_blocks = OrekitParser.get_propagators_from_ocm(ocm)
tray_frame = tray_blocks[0].getFrame()
orekit_propagator = orekit_propagators[0]
cpf_positions = []
for orekit_date in orekit_t_grid: #time_grid
# Get state from Orekit propagator in EME2000 frame
pv_coords = orekit_propagator.getPVCoordinates(orekit_date, tray_frame)
transform = tray_frame.getTransformTo(EME2000, orekit_date)
pv_eci = transform.transformPVCoordinates(pv_coords)
position_vector = list(pv_eci.getPosition().toArray())
cpf_positions.append(position_vector)
cpf_positions = np.array(cpf_positions)/1000.0
Both the .ocm file and the tle are from SpaceBook, but I still think the problem is in some conversion that is not being done correctly. I have tried also doing getPVCoordinates directly to EME2000 in the cpf loop, same result.
Hi, can you provide a minimum runnable code sample? It is not possible to run your code sample due to the missing imports and some variables like tleLine1 or orekit_t_grid or EME2000
Yes of course, and thanks. I was hoping the problem was so obvious that there was no need to run a test. I have completed the script from before with the imports and the time grid. This outputs the same plot as I showed:
import numpy as np
import matplotlib.pyplot as plt
from typing import List
from dataclasses import dataclass
from copy import copy
import orekit
orekit.initVM()
from orekit.pyhelpers import setup_orekit_curdir
setup_orekit_curdir()
# --- OREKIT Imports ---
# Orekit is used for parsing the CPF file and as the "truth" propagator
from org.orekit.time import AbsoluteDate, TimeScalesFactory
from org.orekit.frames import FramesFactory
from org.orekit.files.ccsds.ndm import ParserBuilder
from org.orekit.data import DataSource
from org.orekit.files.ccsds.ndm.odm.ocm import Ocm # for casting
from org.orekit.frames import FramesFactory
from org.orekit.files.general import EphemerisSegmentPropagator
from org.orekit.attitudes import FrameAlignedProvider
from org.orekit.files.general import EphemerisFile
from org.orekit.propagation.analytical.tle import TLE, SGP4
from org.orekit.utils import IERSConventions
# Define the EME2000 frame from Orekit
EME2000 = FramesFactory.getEME2000()
UTC_SCALE = TimeScalesFactory.getUTC()
ITRF = FramesFactory.getITRF(IERSConventions.IERS_2010, True)
@dataclass
class OrekitParser:
@classmethod
def parse_ocm(cls, file_path: str) -> Ocm:
parser = ParserBuilder().buildOcmParser()
src = DataSource(file_path)
parsed = parser.parse(src)
return Ocm.cast_(parsed)
@classmethod
def get_propagators_from_ocm(cls, ocm: Ocm) -> List[EphemerisSegmentPropagator]:
tray_blocks = list(ocm.getData().getTrajectoryBlocks())
sat_map = ocm.getSatellites()
sat_dict = {key: sat_map.get(key) for key in sat_map.keySet()}
propagators = []
for id, sat_epm in sat_dict.items():
sat_iface = EphemerisFile.SatelliteEphemeris.cast_(sat_epm)
propagators.append(sat_iface.getPropagator())
return propagators, tray_blocks
current_t = AbsoluteDate(2025,7,9,12,0,0.0,UTC_SCALE)
dur = 4 # days
dur_s = dur*24*3600.0
tf = current_t.shiftedBy(dur_s)
orekit_t_grid = []
while current_t.isBefore(tf):
orekit_t_grid.append(current_t)
current_t = current_t.shiftedBy(100.0)
tleLine1 = "1 39453U 13067C 25192.21673508 .00005034 00000-0 11431-3 0 9997"
tleLine2 = "2 39453 87.3412 180.4819 0003176 90.8504 269.3117 15.42753470653556"
orekitTle = TLE(tleLine1, tleLine2)
pointing = FrameAlignedProvider(EME2000)
sgp4Propagator = SGP4(orekitTle, pointing, 500.0)
sgp4_positions = []
for orekit_epoch in orekit_t_grid:
orbit = sgp4Propagator.propagateOrbit(orekit_epoch)
tlePV_ECI = orbit.getPVCoordinates(EME2000)
sgp4_positions.append(tlePV_ECI.getPosition().toArray())
sgp4_positions = np.array(sgp4_positions) / 1000
CPF_FILE = "data/spacebook/sata_data/ref_ephem/39453_2025-07-10_CPF.ocm"
ocm = OrekitParser.parse_ocm(CPF_FILE)
# Assuming the first propagator in the file corresponds to our satellite
orekit_propagators, tray_blocks = OrekitParser.get_propagators_from_ocm(ocm)
tray_frame = tray_blocks[0].getFrame()
orekit_propagator = orekit_propagators[0]
cpf_positions = []
for orekit_date in orekit_t_grid: #time_grid
# Get state from Orekit propagator in EME2000 frame
pv_coords = orekit_propagator.getPVCoordinates(orekit_date, tray_frame)
transform = tray_frame.getTransformTo(EME2000, orekit_date)
pv_eci = transform.transformPVCoordinates(pv_coords)
position_vector = list(pv_eci.getPosition().toArray())
cpf_positions.append(position_vector)
cpf_positions = np.array(cpf_positions)/1000.0
# Plotting
fig = plt.figure(figsize=(10, 7))
ax = fig.add_subplot(111, projection='3d')
# Plot CPF positions
ax.plot(
cpf_positions[:, 0],
cpf_positions[:, 1],
cpf_positions[:, 2],
label='CPF Ephemeris',
color='blue',
linewidth=2
)
# Plot SGP4 positions
ax.plot(
sgp4_positions[:, 0],
sgp4_positions[:, 1],
sgp4_positions[:, 2],
label='SGP4 Propagation',
color='red',
linestyle='--',
linewidth=2
)
# Labels and title
ax.set_xlabel("X [km]")
ax.set_ylabel("Y [km]")
ax.set_zlabel("Z [km]")
ax.set_title("CPF vs SGP4 Trajectories in ECI Frame")
ax.legend()
plt.tight_layout()
plt.show()
´´´
provides <Vector3D: {-5,822,917.084400001; 1,465,634.3330671454; 3,227,659.069465709}> which matches with the first line of the OCM file.
And Orekit seems to have properly detected the ITRF coordinate frame of the OCM ephemeris as tray_frame has the value <VersionedITRF: ITRF-2020/CIO/2010-based ITRF simple EOP>.
Maybe the issue comes from the data in the OCM file which is perhaps actually not in ITRF frame?
the OCM file shows "EPOCH_TZERO = 2025-05-01T00:00:00 ", which is too far away from your orekit_t_grid. That will may be a problem by using a ocm propagator.
Hi @yzokras. I also checked the output at EPOCH_TZERO in ITRF to see if I got the same value than in the file. That means the file is being parsed correctly right? (give that it has recognized the frame of the data as ITRF).
But then when you try to convert it to EME2000 the result is as confusing as I showed… I considered as well what you said: maybe the data in the file is wrongly labeled to be in ITRF. But if you convert the output of the TLE propagator to the same tray_frame of the OCM output, you get this:
At least visually they are the same trajectory. This leads me to believe that is what the Earth fixed version of the trajectory should look like, and that the file is ok… Which means the conversion to inertial is not happening right?
But if I retrieve the maxDate from the ocm-based propagator I get <AbsoluteDate: 2025-08-24T00:00:00.000Z>, so it should be valid in the span I am evaluating right? At least I don’t see why not. Maybe the real trajectory has already deviated from waht the file says at those dates, but I dont think so because of the creation date of the file: 2025-07-10T07:28:31
So even though the 4 days plot looks similar, they are far from being the same trajectory. I will try with another satellite from the same database. Maybe there is something wrong with this one.
I have tried with a different object with the same result. Sudenly I am very unsure of what SpaceBook is providing… I will have to try a different route to compare with the TLEs they provide, the “Reference Ephemeris” don’t look ok to me at the moment.
i have plotted tle- and cpf- positions in the first hour since 2025-05-01T00:00:00, and the trajectories diverge. But it seems in the initial epoch 2025-05-01T00:00:00, the positions agreed well, tle-[5461.94541152 2511.43784339 3214.30508222] vs. cpf-[5462.01332112 2511.58221609 3214.12440495] in EME2000.
so, the cpf maybe is not right.
and i used TLEs closed to 2025-05-01T00:00:00
the OCM seems is produced with a wrong time step! in the ocm file, it show a time step of 300s, while the actual time step should be 500s.
when i scales the time span, and plot in the ITRF, tle- vs. cpf- positions matched.
Congratulations for finding this. It is indeed a weird inconsistency.
Just to be sure: when you write the OCM has the wrong time step, is it an external OCM you load using Orekit, or an OCM that Orekit did produce with a wrong time step? In other words, is the error on Orekit side?
Wow it now makes a lot more sense… So the OCM file from SpaceBook has a problem in the time steps. I will try to communicate it, the only contact I can think of is Vallado.