Skip to content

CDM — Conjunction Data Message

A Conjunction Data Message (CDM), defined by the CCSDS 508.0-B-1 Conjunction Data Message standard, describes a close approach between two space objects, providing state vectors, covariance matrices, and collision probability data at the Time of Closest Approach (TCA). It is the standard format used by conjunction assessment services (e.g., 18th Space Defense Squadron) to communicate collision risk to satellite operators.

Parsing a CDM

Parse from KVN, XML, or JSON files and access conjunction data:

from brahe.ccsds import CDM

cdm = CDM.from_file("conjunction.cdm")

# Conjunction-level data
print(f"TCA: {cdm.tca}")
print(f"Miss distance: {cdm.miss_distance} m")
print(f"Collision probability: {cdm.collision_probability}")

# Object states (in meters and m/s)
print(f"Object 1: {cdm.object1_name}")
print(f"Object 1 state: {cdm.object1_state}")
print(f"Object 2: {cdm.object2_name}")
print(f"Object 2 state: {cdm.object2_state}")

# Covariance matrix (6x6, in m², m²/s, m²/s²)
cov = cdm.object1_covariance
print(f"Position variance (R,R): {cov[0][0]} m²")
use brahe::ccsds::CDM;

let cdm = CDM::from_file("conjunction.cdm").unwrap();

println!("TCA: {:?}", cdm.tca());
println!("Miss distance: {} m", cdm.miss_distance());
println!("Collision probability: {:?}", cdm.collision_probability());

// Object states as 6-element vectors [x, y, z, vx, vy, vz]
let s1 = cdm.object1_state();
let s2 = cdm.object2_state();

// 6x6 RTN covariance submatrix
let cov = cdm.object1_rtn_covariance_6x6();

Creating a CDM

Build a CDM programmatically by constructing state vectors, covariance matrices, and object metadata, then combining them into a message:

import numpy as np

import brahe as bh
from brahe.ccsds import CDM, CDMObject, CDMRTNCovariance, CDMStateVector

# Define state vectors at TCA for both objects (meters, m/s)
sv1 = CDMStateVector(
    position=[bh.R_EARTH + 500e3, 0.0, 0.0],
    velocity=[0.0, 7612.0, 0.0],
)
sv2 = CDMStateVector(
    position=[bh.R_EARTH + 500.5e3, 10.0, -5.0],
    velocity=[0.0, -7612.0, 0.0],
)

# Define 6x6 RTN covariance matrices (m², m²/s, m²/s²)
cov1 = CDMRTNCovariance(matrix=(np.eye(6) * 1e4).tolist())
cov2 = CDMRTNCovariance(matrix=(np.eye(6) * 2e4).tolist())

# Build object metadata + data
obj1 = CDMObject(
    designator="12345",
    catalog_name="SATCAT",
    name="SATELLITE A",
    international_designator="2020-001A",
    ephemeris_name="NONE",
    covariance_method="CALCULATED",
    maneuverable="YES",
    ref_frame="EME2000",
    state_vector=sv1,
    rtn_covariance=cov1,
)
obj2 = CDMObject(
    designator="67890",
    catalog_name="SATCAT",
    name="DEBRIS FRAGMENT",
    international_designator="2019-050ZZ",
    ephemeris_name="NONE",
    covariance_method="CALCULATED",
    maneuverable="NO",
    ref_frame="EME2000",
    state_vector=sv2,
    rtn_covariance=cov2,
)

# Create CDM message
tca = bh.Epoch.from_datetime(2024, 6, 15, 14, 30, 0.0, 0.0, bh.TimeSystem.UTC)
cdm = CDM(
    originator="BRAHE_EXAMPLE",
    message_id="CDM-2024-001",
    tca=tca,
    miss_distance=502.3,
    object1=obj1,
    object2=obj2,
)

# Set optional collision probability
cdm.collision_probability = 1.5e-04
cdm.collision_probability_method = "FOSTER-1992"

print(f"CDM: {cdm.object1_name} vs {cdm.object2_name}")
print(f"Miss distance: {cdm.miss_distance} m")
print(f"Collision probability: {cdm.collision_probability}")

# Write to KVN
kvn = cdm.to_string("KVN")
print(f"\nKVN output ({len(kvn)} chars)")

# Verify round-trip
cdm2 = CDM.from_str(kvn)
print(f"Round-trip: {cdm2.object1_name} vs {cdm2.object2_name}")
use brahe as bh;
use brahe::ccsds::{
    CCSDSFormat, CCSDSRefFrame, CDM, CDMObject, CDMObjectMetadata, CDMRTNCovariance,
    CDMStateVector,
};
use nalgebra as na;

fn main() {
    // Define state vectors at TCA for both objects (meters, m/s)
    let sv1 = CDMStateVector::new(
        [bh::R_EARTH + 500e3, 0.0, 0.0],
        [0.0, 7612.0, 0.0],
    );
    let sv2 = CDMStateVector::new(
        [bh::R_EARTH + 500.5e3, 10.0, -5.0],
        [0.0, -7612.0, 0.0],
    );

    // Define 6x6 RTN covariance matrices (m², m²/s, m²/s²)
    let cov1 = CDMRTNCovariance::from_6x6(na::SMatrix::<f64, 6, 6>::identity() * 1e4);
    let cov2 = CDMRTNCovariance::from_6x6(na::SMatrix::<f64, 6, 6>::identity() * 2e4);

    // Build object metadata
    let meta1 = CDMObjectMetadata::new(
        "OBJECT1".to_string(),
        "12345".to_string(),
        "SATCAT".to_string(),
        "SATELLITE A".to_string(),
        "2020-001A".to_string(),
        "NONE".to_string(),
        "CALCULATED".to_string(),
        "YES".to_string(),
        CCSDSRefFrame::EME2000,
    );
    let meta2 = CDMObjectMetadata::new(
        "OBJECT2".to_string(),
        "67890".to_string(),
        "SATCAT".to_string(),
        "DEBRIS FRAGMENT".to_string(),
        "2019-050ZZ".to_string(),
        "NONE".to_string(),
        "CALCULATED".to_string(),
        "NO".to_string(),
        CCSDSRefFrame::EME2000,
    );

    let obj1 = CDMObject::new(meta1, sv1, cov1);
    let obj2 = CDMObject::new(meta2, sv2, cov2);

    // Create CDM message
    let tca = bh::Epoch::from_datetime(2024, 6, 15, 14, 30, 0.0, 0.0, bh::TimeSystem::UTC);
    let mut cdm = CDM::new(
        "BRAHE_EXAMPLE".to_string(),
        "CDM-2024-001".to_string(),
        tca,
        502.3,
        obj1,
        obj2,
    );

    // Set optional collision probability
    cdm.relative_metadata.collision_probability = Some(1.5e-04);
    cdm.relative_metadata.collision_probability_method = Some("FOSTER-1992".to_string());

    println!(
        "CDM: {} vs {}",
        cdm.object1.metadata.object_name, cdm.object2.metadata.object_name
    );
    println!("Miss distance: {} m", cdm.miss_distance());
    println!("Collision probability: {:?}", cdm.collision_probability());

    // Write to KVN
    let kvn = cdm.to_string(CCSDSFormat::KVN).unwrap();
    println!("\nKVN output ({} chars)", kvn.len());

    // Verify round-trip
    let cdm2 = CDM::from_str(&kvn).unwrap();
    println!(
        "Round-trip: {} vs {}",
        cdm2.object1.metadata.object_name, cdm2.object2.metadata.object_name
    );
}
Output
1
2
3
4
5
6
CDM: SATELLITE A vs DEBRIS FRAGMENT
Miss distance: 502.3 m
Collision probability: 0.00015

KVN output (5852 chars)
Round-trip: SATELLITE A vs DEBRIS FRAGMENT
1
2
3
4
5
6
CDM: SATELLITE A vs DEBRIS FRAGMENT
Miss distance: 502.3 m
Collision probability: Some(0.00015)

KVN output (5852 chars)
Round-trip: SATELLITE A vs DEBRIS FRAGMENT

Building Object Metadata (Rust)

Each object's metadata (9 mandatory fields plus optional CCSDS fields) can also be constructed with CDMObjectMetadata::builder(), which is Rust-only -- there is no Python binding for this builder. The mandatory fields are set through chained named setters, and build() returns an error naming any mandatory field left unset instead of applying a default. The flat constructor used above remains available as an alternative, taking the 9 mandatory fields positionally:

use brahe::ccsds::cdm::CDMObjectMetadata;
use brahe::ccsds::common::CCSDSRefFrame;

fn main() {
    let metadata = CDMObjectMetadata::builder()
        .object("OBJECT1")
        .object_designator("12345")
        .catalog_name("SATCAT")
        .object_name("SATELLITE A")
        .international_designator("2020-001A")
        .ephemeris_name("NONE")
        .covariance_method("CALCULATED")
        .maneuverable("YES")
        .ref_frame(CCSDSRefFrame::EME2000)
        .comment("Generated for conjunction screening")
        .build()
        .unwrap();

    println!("Object: {}", metadata.object_name);
    println!("Designator: {}", metadata.object_designator);
    println!("Comments: {:?}", metadata.comments);

    // The flat constructor takes the same 9 mandatory fields positionally,
    // without naming each one -- an alternative when all values are
    // already at hand.
    let metadata_flat = CDMObjectMetadata::new(
        "OBJECT1".to_string(),
        "12345".to_string(),
        "SATCAT".to_string(),
        "SATELLITE A".to_string(),
        "2020-001A".to_string(),
        "NONE".to_string(),
        "CALCULATED".to_string(),
        "YES".to_string(),
        CCSDSRefFrame::EME2000,
    );

    assert_eq!(metadata_flat.object_name, metadata.object_name);
    println!("Example validated successfully!");
}
Output
1
2
3
4
Object: SATELLITE A
Designator: 12345
Comments: ["Generated for conjunction screening"]
Example validated successfully!

What a CDM Contains

Every CDM has a header (version, creation date, originator, message ID), relative metadata (TCA, miss distance, optional collision probability and screening volume), and exactly two object sections.

Each object section contains metadata (object identity, reference frame, covariance method, force model info), OD parameters (observation spans, residuals), additional parameters (mass, drag/SRP areas, hard-body radius), a state vector at TCA, and a covariance matrix in the RTN frame.

The covariance matrix is always specified in the Radial-Transverse-Normal (RTN) frame centered on the object. The standard 6\(\times\)6 matrix covers position and velocity uncertainty. CDM also supports extended 7\(\times\)7 through 9\(\times\)9 matrices that include drag coefficient, solar radiation pressure, and thrust uncertainty correlations.

Format Support

CDM supports three encoding formats:

  • KVN (.cdm, .txt) — keyword=value text, the most common format from conjunction screening services
  • XML (.xml) — structured XML following the CCSDS NDM XML schema
  • JSON — programmatic convenience format (not in the CCSDS standard)

All three formats are auto-detected on parse. Specify the format explicitly when writing:

1
2
3
4
cdm = CDM.from_file("conjunction.cdm")  # Auto-detect
kvn = cdm.to_string("KVN")
xml = cdm.to_string("XML")
json_str = cdm.to_string("JSON")

See Also