An Orbit Ephemeris Message (OEM) carries time-ordered state vectors for spacecraft ephemeris exchange. The message is defined by the CCSDS 502.0-B-3 Orbit Data Messages standard. The typical workflow is to parse an OEM file and convert it into an OrbitTrajectory for interpolation and analysis, or to generate an OEM from a propagator for distribution.
Format version: 3.0
Originator: NASA/JPL
Classification: public, test-data
Creation date: 1996-11-04 17:22:31.000 UTC
Number of segments: 3
Segment 0:
Object name: MARS GLOBAL SURVEYOR
Object ID: 1996-062A
Center name: MARS BARYCENTER
Ref frame: J2000
Time system: UTC
Start time: 1996-12-18 12:00:00.331 UTC
Stop time: 1996-12-28 21:28:00.331 UTC
Interpolation: HERMITE
States: 4
Covariances: 0
First state vector:
Epoch: 1996-12-18 12:00:00.331 UTC
Position: [2789619.000, -280045.000, -1746755.000] m
Velocity: [4733.72000, -2495.86000, -1041.95000] m/s
All states in segment 0:
[0] 1996-12-18 12:00:00.331 UTC pos=(2789.619, -280.045, -1746.755) km
[1] 1996-12-18 12:01:00.331 UTC pos=(2783.419, -308.143, -1877.071) km
[2] 1996-12-18 12:02:00.331 UTC pos=(2776.033, -336.859, -2008.682) km
[3] 1996-12-28 21:28:00.331 UTC pos=(-3881.024, 563.959, -682.773) km
KVN output length: 4312 characters
Dict keys: ['header', 'segments']
Format version: 3
Originator: NASA/JPL
Classification: public, test-data
Creation date: 1996-11-04 17:22:31.000 UTC
Number of segments: 3
Segment 0:
Object name: MARS GLOBAL SURVEYOR
Object ID: 1996-062A
Center name: MARS BARYCENTER
Ref frame: J2000
Time system: UTC
Start time: 1996-12-18 12:00:00.331 UTC
Stop time: 1996-12-28 21:28:00.331 UTC
Interpolation: HERMITE
States: 4
Covariances: 0
First state vector:
Epoch: 1996-12-18 12:00:00.331 UTC
Position: [2789619.000, -280045.000, -1746755.000] m
Velocity: [4733.72000, -2495.86000, -1041.95000] m/s
All states in segment 0:
[0] 1996-12-18 12:00:00.331 UTC pos=(2789.619, -280.045, -1746.755) km
[1] 1996-12-18 12:01:00.331 UTC pos=(2783.419, -308.143, -1877.071) km
[2] 1996-12-18 12:02:00.331 UTC pos=(2776.033, -336.859, -2008.682) km
[3] 1996-12-28 21:28:00.331 UTC pos=(-3881.024, 563.959, -682.773) km
KVN output length: 4312 characters
The primary interoperability point for OEM data is conversion to brahe's OrbitTrajectory. Each OEM segment maps to a trajectory object, giving you Hermite interpolation at arbitrary epochs within the covered time span. REF_FRAME and CENTER_NAME are resolved jointly: REF_FRAME names the trajectory's orientation and CENTER_NAME its origin, so a segment with REF_FRAME = TOD and the default CENTER_NAME = EARTH loads as the TOD frame, while REF_FRAME = EME2000 with CENTER_NAME = MARS loads as EME2000 orientation centered on Mars (CelestialFrame.Centered(MARS, EME2000)) rather than the Earth-centered EME2000 shorthand. Use to_frame to convert to GCRF, ITRF, MCI, or another supported frame; converting to a frame centered on a different body translates through the loaded SPK kernels, while converting to a same-centered frame is a rotation only. See Axes and Centers for the full FrameAxes/CelestialFrame picture.
An OEM message begins with a header that records the format version, creation date, and originator. The bulk of the data lives in one or more segments, each of which has its own metadata block and a sequence of state vectors.
Multiple segments exist because a single file may need to cover different trajectory arcs. A maneuver boundary, a change in reference frame, or a gap in tracking data each warrant a new segment. Within a segment, the metadata block records the object identity, center body, reference frame, time system, time span, and interpolation settings. The state vectors follow — each line provides an epoch plus position and velocity (and optionally acceleration). If covariance data is available, it appears as one or more 6\(\times\)6 symmetric matrices attached to the segment, each with its own epoch and optional reference frame override.
Build an OEM programmatically by defining a header, adding segments with metadata, and populating state vectors. The resulting message can be serialized to KVN, XML, or JSON:
importnumpyasnpimportbraheasbhfrombrahe.ccsdsimportOEMbh.initialize_eop()# Create a new OEM with header infooem=OEM(originator="BRAHE_EXAMPLE")oem.classification="unclassified"oem.message_id="OEM-2024-001"# Define a LEO orbit and propagate with KeplerianPropagator (two-body)epoch=bh.Epoch.from_datetime(2024,6,15,0,0,0.0,0.0,bh.TimeSystem.UTC)oe=np.array([bh.R_EARTH+500e3,0.001,51.6,15.0,30.0,0.0])prop=bh.KeplerianPropagator.from_keplerian(epoch,oe,bh.AngleFormat.DEGREES,60.0)# Add a segment with metadatastep=60.0# 60-second spacingn_states=5stop_epoch=epoch+step*(n_states-1)seg_idx=oem.add_segment(object_name="LEO SAT",object_id="2024-100A",center_name="EARTH",ref_frame="EME2000",time_system="UTC",start_time=epoch,stop_time=stop_epoch,interpolation="LAGRANGE",interpolation_degree=7,)# Propagate to build trajectory, then bulk-add states to segmentprop.propagate_to(stop_epoch)seg=oem.segments[seg_idx]seg.add_trajectory(prop.trajectory)print(f"Created OEM with {len(oem.segments)} segment, {seg.num_states} states")# Write to KVN stringkvn=oem.to_string("KVN")print(f"\nKVN output ({len(kvn)} chars):")print(kvn[:500])# Write to fileoem.to_file("/tmp/brahe_example_oem.txt","KVN")print("\nWritten to /tmp/brahe_example_oem.txt")# Verify round-tripoem2=OEM.from_file("/tmp/brahe_example_oem.txt")print(f"Round-trip: {len(oem2.segments)} segment, {oem2.segments[0].num_states} states")
usebraheasbh;usebrahe::ccsds::{CCSDSFormat,CCSDSRefFrame,CCSDSTimeSystem,OEM,OEMMetadata,OEMSegment,OEMStateVector,};usebrahe::traits::DStateProvider;usenalgebraasna;fnmain(){bh::initialize_eop().unwrap();// Create a new OEM with header infoletmutoem=OEM::new("BRAHE_EXAMPLE".to_string());// Define a LEO orbit and propagate with KeplerianPropagator (two-body)letepoch=bh::Epoch::from_datetime(2024,6,15,0,0,0.0,0.0,bh::TimeSystem::UTC);letoe=na::SVector::<f64,6>::new(bh::R_EARTH+500e3,0.001,51.6,15.0,30.0,0.0);letprop=bh::KeplerianPropagator::from_keplerian(epoch,oe,bh::AngleFormat::Degrees,60.0).unwrap();// Create segment metadataletstep=60.0_f64;// 60-second spacingletn_states=5usize;letstop_epoch=epoch+step*(n_states-1)asf64;letmetadata=OEMMetadata::new("LEO SAT".to_string(),"2024-100A".to_string(),"EARTH".to_string(),CCSDSRefFrame::EME2000,CCSDSTimeSystem::UTC,epoch,stop_epoch,).with_interpolation("LAGRANGE".to_string(),Some(7));letmutseg=OEMSegment::new(metadata);// Populate states from the Keplerian propagatorforiin0..n_states{lett=epoch+iasf64*step;lets=prop.state(t).unwrap();seg.push_state(OEMStateVector::new(t,[s[0],s[1],s[2]],[s[3],s[4],s[5]],));}oem.push_segment(seg);println!("Created OEM with {} segment, {} states",oem.segments.len(),oem.segments[0].states.len());letkvn=oem.to_string(CCSDSFormat::KVN).unwrap();println!("\nKVN output ({} chars):",kvn.len());letpreview:String=kvn.chars().take(500).collect();println!("{}",preview);// Write to fileoem.to_file("/tmp/brahe_example_oem.txt",CCSDSFormat::KVN).unwrap();println!("\nWritten to /tmp/brahe_example_oem.txt");// Verify round-tripletoem2=OEM::from_file("/tmp/brahe_example_oem.txt").unwrap();println!("Round-trip: {} segment, {} states",oem2.segments.len(),oem2.segments[0].states.len());}
Created OEM with 1 segment, 5 states
KVN output (942 chars):
CCSDS_OEM_VERS = 3.0
CREATION_DATE = 2026-09-07T06:52:05.6877275269
ORIGINATOR = BRAHE_EXAMPLE
META_START
OBJECT_NAME = LEO SAT
OBJECT_ID = 2024-100A
CENTER_NAME = EARTH
REF_FRAME = EME2000
TIME_SYSTEM = UTC
START_TIME = 2024-06-15T00:00:00.000
STOP_TIME = 2024-06-15T00:04:00.000
INTERPOLATION = LAGRANGE
INTERPOLATION_DEGREE = 7
META_STOP
2024-06-15T00:00:00.000 6878.136300 0.000001 0.051600 0.015000000 0.030000000 0.000000000
2024-06-15T00:01:00.000 6878.13630
Written to /tmp/brahe_example_oem.txt
Round-trip: 1 segment, 5 states
Round-Trip Fidelity
Writing and re-parsing an OEM preserves all metadata, state vectors, and covariance data. Numeric precision may vary slightly due to floating-point formatting, but values are preserved within the precision of the output format.
importnumpyasnpimportbraheasbhfrombrahe.ccsdsimportOEMbh.initialize_eop()bh.initialize_sw()# Define initial stateepoch=bh.Epoch.from_datetime(2024,6,15,0,0,0.0,0.0,bh.TimeSystem.UTC)oe=np.array([bh.R_EARTH+500e3,0.001,51.6,15.0,30.0,45.0])state=bh.state_koe_to_eci(oe,bh.AngleFormat.DEGREES)params=np.array([500.0,2.0,2.2,2.0,1.3])# Create propagator with default force modelprop=bh.NumericalOrbitPropagator(epoch,state,bh.NumericalPropagationConfig.default(),bh.ForceModelConfig.default(),params,)# Propagate for 90 minutestarget_epoch=epoch+5400.0prop.propagate_to(target_epoch)print(f"Propagated from {epoch} to {prop.current_epoch()}")# Get the accumulated trajectorytraj=prop.trajectoryprint(f"Trajectory: {len(traj)} states, span={traj.timespan():.0f}s")# Build an OEM from the trajectory states using the trajectory kwargoem=OEM(originator="BRAHE_PROP")stop_epoch=prop.current_epoch()seg_idx=oem.add_segment(object_name="LEO SAT",object_id="2024-100A",center_name="EARTH",ref_frame="EME2000",time_system="UTC",start_time=epoch,stop_time=stop_epoch,interpolation="LAGRANGE",interpolation_degree=7,trajectory=traj,)seg=oem.segments[seg_idx]print(f"\nOEM: {len(oem.segments)} segment, {seg.num_states} states")# Write to KVNkvn=oem.to_string("KVN")print(f"KVN output: {len(kvn)} characters")# Verify by re-parsingoem2=OEM.from_str(kvn)print(f"Round-trip: {oem2.segments[0].num_states} states")
usebraheasbh;usebh::ccsds::{CCSDSFormat,CCSDSRefFrame,CCSDSTimeSystem,OEM,OEMMetadata,OEMSegment,OEMStateVector,};usebh::traits::{DStatePropagator,Trajectory};usenalgebraasna;fnmain(){bh::initialize_eop().unwrap();bh::initialize_sw().unwrap();// Define initial stateletepoch=bh::Epoch::from_datetime(2024,6,15,0,0,0.0,0.0,bh::TimeSystem::UTC);letoe=na::SVector::<f64,6>::new(bh::R_EARTH+500e3,0.001,51.6,15.0,30.0,45.0);letstate=bh::state_koe_to_eci(oe,bh::AngleFormat::Degrees);letparams=na::DVector::from_vec(vec![500.0,2.0,2.2,2.0,1.3]);// Create propagator with default force modelletmutprop=bh::DNumericalOrbitPropagator::builder(epoch,na::DVector::from_column_slice(state.as_slice()),bh::ForceModelConfig::default(),).params(params).build().unwrap();// Propagate for 90 minuteslettarget_epoch=epoch+5400.0;prop.propagate_to(target_epoch).unwrap();println!("Propagated from {} to {}",epoch,prop.current_epoch());// Get the accumulated trajectorylettraj=prop.trajectory();println!("Trajectory: {} states, span={:.0}s",traj.len(),traj.timespan().unwrap());// Build an OEM from the trajectory statesletmutoem=OEM::new("BRAHE_PROP".to_string());letstop_epoch=prop.current_epoch();letmetadata=OEMMetadata::new("LEO SAT".to_string(),"2024-100A".to_string(),"EARTH".to_string(),CCSDSRefFrame::EME2000,CCSDSTimeSystem::UTC,epoch,stop_epoch,).with_interpolation("LAGRANGE".to_string(),Some(7));letmutseg=OEMSegment::new(metadata);// Extract states from trajectory and add to OEMforiin0..traj.len(){let(epc,s)=traj.get(i).unwrap();seg.push_state(OEMStateVector::new(epc,[s[0],s[1],s[2]],[s[3],s[4],s[5]],));}letnum_states=seg.states.len();oem.push_segment(seg);println!("\nOEM: {} segment, {} states",oem.segments.len(),num_states);// Write to KVNletkvn=oem.to_string(CCSDSFormat::KVN).unwrap();println!("KVN output: {} characters",kvn.len());// Verify by re-parsingletoem2=OEM::from_str(&kvn).unwrap();println!("Round-trip: {} states",oem2.segments[0].states.len());}
Propagated from 2024-06-15 00:00:00.000 UTC to 2024-06-15 01:30:00.000 UTC
Trajectory: 221 states, span=5400s
OEM: 1 segment, 221 states
KVN output: 28372 characters
Round-trip: 221 states
Propagated from 2024-06-15 00:00:00.000 UTC to 2024-06-15 01:30:00.000 UTC
Trajectory: 221 states, span=5400s
OEM: 1 segment, 221 states
KVN output: 28372 characters
Round-trip: 221 states