An Orbit Ephemeris Message (OEM) carries time-ordered state vectors for spacecraft ephemeris exchange. 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: 4372 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: 4372 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:
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:
importbraheasbhimportnumpyasnpfrombrahe.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);// 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-03-23T02:15:49.1149560477
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.
importbraheasbhimportnumpyasnpfrombrahe.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::new(epoch,na::DVector::from_column_slice(state.as_slice()),bh::NumericalPropagationConfig::default(),bh::ForceModelConfig::default(),Some(params),None,None,None,).unwrap();// Propagate for 90 minuteslettarget_epoch=epoch+5400.0;prop.propagate_to(target_epoch);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: 91 states, span=5400s
OEM: 1 segment, 91 states
KVN output: 11259 characters
Round-trip: 91 states
Propagated from 2024-06-15 00:00:00.000 UTC to 2024-06-15 01:30:00.000 UTC
Trajectory: 91 states, span=5400s
OEM: 1 segment, 91 states
KVN output: 11259 characters
Round-trip: 91 states