Skip to content

CCSDS Data Formats

The Consultative Committee for Space Data Systems (CCSDS) defines standard formats for exchanging orbital data between space agencies, satellite operators, and ground systems. Brahe parses and writes all three Orbit Data Message (ODM) types from the CCSDS 502.0-B-3 standard, with automatic format detection and SI unit conversion. The typical workflow is: parse a file, access the data, and feed it into a propagator or trajectory.

The Core Workflow

The most common pattern is to parse an OEM file — received from another system or generated by a propagator — and convert it into an OrbitTrajectory for interpolation and analysis:

import brahe as bh
from brahe.ccsds import OEM

bh.initialize_eop()

# Parse an OEM file
oem = OEM.from_file("test_assets/ccsds/oem/OEMExample5.txt")
seg = oem.segments[0]
print(f"Segment: {seg.object_name}, {seg.num_states} states, frame={seg.ref_frame}")

# Convert segment 0 to an OrbitTrajectory
traj = oem.segment_to_trajectory(0)
print(f"\nTrajectory: {len(traj)} states")
print(f"  Frame: {traj.frame}")
print(f"  Start: {traj.start_epoch()}")
print(f"  End:   {traj.end_epoch()}")
print(f"  Span:  {traj.timespan():.0f} seconds")

# Access states by index
epc, state = traj.get(0)
print("\nFirst state:")
print(f"  Epoch: {epc}")
print(
    f"  Position: [{state[0] / 1e3:.3f}, {state[1] / 1e3:.3f}, {state[2] / 1e3:.3f}] km"
)
print(f"  Velocity: [{state[3]:.3f}, {state[4]:.3f}, {state[5]:.3f}] m/s")

# Interpolate at an arbitrary epoch between states
epc0, _ = traj.get(0)
epc1, _ = traj.get(1)
mid_epoch = epc0 + (epc1 - epc0) / 2.0
interp_state = traj.interpolate(mid_epoch)
print(f"\nInterpolated state at {mid_epoch}:")
print(
    f"  Position: [{interp_state[0] / 1e3:.3f}, {interp_state[1] / 1e3:.3f}, {interp_state[2] / 1e3:.3f}] km"
)

# Convert all segments at once
oem_multi = OEM.from_file("test_assets/ccsds/oem/OEMExample1.txt")
trajs = oem_multi.to_trajectories()
print(f"\nMulti-segment OEM: {len(trajs)} trajectories")
for i, t in enumerate(trajs):
    print(f"  [{i}] {len(t)} states, span={t.timespan():.0f}s")
use brahe as bh;
use brahe::ccsds::OEM;
use brahe::traits::Trajectory;

fn main() {
    bh::initialize_eop().unwrap();

    // Parse an OEM file
    let oem = OEM::from_file("test_assets/ccsds/oem/OEMExample5.txt").unwrap();
    let seg = &oem.segments[0];
    println!(
        "Segment: {}, {} states, frame={}",
        seg.metadata.object_name,
        seg.states.len(),
        seg.metadata.ref_frame
    );
    let traj = oem.segment_to_trajectory(0).unwrap();
    println!("\nTrajectory: {} states", traj.len());
    println!("  Frame: {:?}", traj.frame);
    println!("  Start: {}", traj.start_epoch().unwrap());
    println!("  End:   {}", traj.end_epoch().unwrap());
    println!("  Span:  {:.0} seconds", traj.timespan().unwrap());

    // Access states by index
    let (epoch, state) = traj.first().unwrap();
    println!("\nFirst state:");
    println!("  Epoch: {}", epoch);
    println!(
        "  Position: [{:.3}, {:.3}, {:.3}] km",
        state[0] / 1e3,
        state[1] / 1e3,
        state[2] / 1e3
    );
    println!(
        "  Velocity: [{:.3}, {:.3}, {:.3}] m/s",
        state[3], state[4], state[5]
    );

    // Convert all segments from a multi-segment OEM
    let oem_multi = OEM::from_file("test_assets/ccsds/oem/OEMExample1.txt").unwrap();
    let trajs = oem_multi.to_trajectories().unwrap();
    println!("\nMulti-segment OEM: {} trajectories", trajs.len());
    for (i, t) in trajs.iter().enumerate() {
        println!("  [{}] {} states, span={:.0}s", i, t.len(), t.timespan().unwrap());
    }
}
Output
Segment: ISS, 49 states, frame=GCRF

Trajectory: 49 states
  Frame: GCRF
  Start: 2017-04-11 22:31:43.122 UTC
  End:   2017-04-12 22:31:43.122 UTC
  Span:  86400 seconds

First state:
  Epoch: 2017-04-11 22:31:43.122 UTC
  Position: [2906.275, 4076.358, 4561.364] km
  Velocity: [-6879.497, 1449.531, 3081.318] m/s

Interpolated state at 2017-04-11 22:46:43.122 UTC:
  Position: [-1917.595, 1696.341, 2465.366] km

Multi-segment OEM: 3 trajectories
  [0] 4 states, span=898080s
  [1] 4 states, span=100735s
  [2] 5 states, span=100735s
Segment: ISS, 49 states, frame=GCRF

Trajectory: 49 states
  Frame: OrbitFrame(Geocentric Celestial Reference Frame)
  Start: 2017-04-11 22:31:43.122 UTC
  End:   2017-04-12 22:31:43.122 UTC
  Span:  86400 seconds

First state:
  Epoch: 2017-04-11 22:31:43.122 UTC
  Position: [2906.275, 4076.358, 4561.364] km
  Velocity: [-6879.497, 1449.531, 3081.318] m/s

Multi-segment OEM: 3 trajectories
  [0] 4 states, span=898080s
  [1] 4 states, span=100735s
  [2] 5 states, span=100735s

This three-step pattern — parse, convert, interpolate — applies across the library. OEM data becomes trajectories, OMM data feeds SGP4 propagators, and OPM data initializes numerical propagators.

Choosing a Message Type

OEM (Orbit Ephemeris Message) carries time-ordered state vectors and is the standard format for ephemeris exchange. Use it when you have or want to produce a full trajectory — conjunction screening, trajectory handoffs between teams, or archiving definitive ephemerides. See the OEM detail page.

OMM (Orbit Mean-elements Message) is the CCSDS-standardized representation of TLE/GP data. Data sources like CelesTrak and Space-Track distribute orbital elements as OMM, making it the modern successor to fixed-width TLE format. Use it when working with GP data and SGP4 propagation. See the OMM detail page.

OPM (Orbit Parameter Message) carries a single state snapshot at one epoch, optionally with Keplerian elements, spacecraft parameters, covariance, and maneuvers. Use it when handing off initial conditions for propagation or documenting a maneuver plan. See the OPM detail page.

CDM (Conjunction Data Message) describes a close approach between two space objects, providing state vectors, covariance matrices, and collision probability at the Time of Closest Approach (TCA). Use it when processing conjunction screening data from services like the 18th Space Defense Squadron. See the CDM detail page.

OCM Not Yet Supported

The Orbit Comprehensive Message (OCM) is not yet implemented. OCM combines features of OEM, OMM, and OPM into a single flexible format and will be added in a future release.

Format Detection

Each message type can be encoded as KVN (.oem, .omm, .opm, .txt), XML (.xml), or JSON (.json). Brahe auto-detects the format when parsing by examining the first non-whitespace character of the content:

  • Starts with < or <?xml → XML
  • Starts with { or [ → JSON
  • Otherwise → KVN

Unit Conventions

CCSDS files use km and km/s for position and velocity. Brahe automatically converts these to SI base units (meters, m/s) on parse, and converts back when writing. This means:

  • All position values returned by brahe are in meters
  • All velocity values returned by brahe are in m/s
  • Covariance matrices are in m\(^2\), m\(^2\)/s, and m\(^2\)/s\(^2\)
  • Angles in OMM and OPM Keplerian elements remain in degrees (matching CCSDS/TLE convention)
  • GM values are converted from km\(^3\)/s\(^2\) to m\(^3\)/s\(^2\)

Unit Conversion

If you compare brahe output directly to values in a CCSDS file, remember the factor of 1000 for position/velocity and 10\(^9\) for GM. Covariance elements scale by 10\(^6\) (km\(^2\) → m\(^2\)).


See Also