Skip to content

SGP Propagation

The SGPPropagator implements the SGP4/SDP4 propagation models for orbital prediction. SGP4 is a standard method for satellite tracking and includes simplified perturbations from Earth oblateness and atmospheric drag, making it suitable for operational satellite tracking and near-Earth orbit propagation. It is widely used with Two-Line Element (TLE) data provided by NORAD and other space tracking organizations.

For complete API documentation, see the SGPPropagator API Reference.

TLE Format Support

SGP4 propagation is based on Two-Line Element (TLE) sets, a compact data format for orbital elements. Brahe supports both traditional and modern TLE formats:

  • Classic Format: Traditional numeric NORAD catalog numbers (5 digits, up to 99999)
  • Alpha-5 Format: Extended alphanumeric catalog numbers for satellites beyond 99999

The initialization automatically detects and handles both formats.

From Ephemeris Data Sources

Rather than hard-coding TLE strings, you can query live satellite data from CelesTrak or Space-Track and get a ready-to-use propagator in a single call. This is the most common workflow for operational satellite tracking:

This example shows how to query a satellite from CelesTrak and convert it
to a propagator in a few steps, which is the most common use case.
"""

import brahe as bh

# Initialize EOP data
bh.initialize_eop()

# Get an SGP4 propagator for the ISS directly from CelesTrak
client = bh.celestrak.CelestrakClient()
iss_prop = client.get_sgp_propagator(catnr=25544, step_size=60.0)

print(f"Created propagator: {iss_prop.get_name()}")
print(f"Epoch: {iss_prop.epoch}")

# Propagate forward 1 orbit period (~93 minutes for ISS)
iss_prop.propagate_to(iss_prop.epoch + bh.orbital_period(iss_prop.semi_major_axis))
state = iss_prop.current_state()

print("\nState after 1 orbit:")
print(f"  Position: [{state[0]:.1f}, {state[1]:.1f}, {state[2]:.1f}] m")
print(f"  Velocity: [{state[3]:.1f}, {state[4]:.1f}, {state[5]:.1f}] m/s")
//! to a propagator in a few steps, which is the most common use case.
//!
//! FLAGS = ["CI-ONLY"]

#[allow(unused_imports)]
use brahe as bh;
use bh::celestrak::CelestrakClient;
use bh::traits::SStatePropagator;
use bh::utils::Identifiable;

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

    // Get an SGP4 propagator for the ISS directly from CelesTrak
    let client = CelestrakClient::new();
    let mut iss_prop = client.get_sgp_propagator_by_catnr(25544, 60.0).unwrap();

    println!("Created propagator: {}", iss_prop.get_name().unwrap_or("Unknown"));
    println!("Epoch: {}", iss_prop.epoch);

    // Propagate forward 1 orbit period (~93 minutes for ISS)
    iss_prop.propagate_to(iss_prop.epoch + bh::orbital_period(iss_prop.semi_major_axis()));
    let state = iss_prop.current_state();

    println!("\nState after 1 orbit:");
    println!(
        "  Position: [{:.1}, {:.1}, {:.1}] m",
        state[0], state[1], state[2]
    );
    println!(
        "  Velocity: [{:.1}, {:.1}, {:.1}] m/s",
        state[3], state[4], state[5]
    );

}
Output
1
2
3
4
5
6
Created propagator: ISS (ZARYA)
Epoch: 2026-03-22 03:54:40.386 UTC

State after 1 orbit:
  Position: [6696048.4, 1203822.7, 5003.6] m
  Velocity: [-847.9, 4670.8, 6006.2] m/s
1
2
3
4
5
6
Created propagator: ISS (ZARYA)
Epoch: 2026-03-22 03:54:40.386 UTC

State after 1 orbit:
  Position: [6696048.4, 1203822.7, 5003.6] m
  Velocity: [-847.9, 4670.8, 6006.2] m/s

For details on querying satellite data, see Ephemeris Data Sources.

Initialization

The SGPPropagator can also be initialized directly from TLE data. The TLE lines contain all orbital parameters needed for propagation.

From Two Line Elements (TLE)

The most common initialization uses two lines of TLE data.

import brahe as bh
import numpy as np

bh.initialize_eop()  # Required for accurate frame transformations

# ISS TLE data (example)
line1 = "1 25544U 98067A   08264.51782528 -.00002182  00000-0 -11606-4 0  2927"
line2 = "2 25544  51.6416 247.4627 0006703 130.5360 325.0288 15.72125391563537"

# Create propagator with 60-second step size
prop = bh.SGPPropagator.from_tle(line1, line2, 60.0)

print(f"NORAD ID: {prop.norad_id}")
print(f"TLE epoch: {prop.epoch}")
print(
    f"Initial position magnitude: {np.linalg.norm(prop.initial_state()[:3]) / 1e3:.1f} km"
)
use brahe as bh;
use brahe::traits::SStatePropagator;

fn main() {
    bh::initialize_eop().unwrap();  // Required for accurate frame transformations

    // ISS TLE data (example)
    let line1 = "1 25544U 98067A   08264.51782528 -.00002182  00000-0 -11606-4 0  2927";
    let line2 = "2 25544  51.6416 247.4627 0006703 130.5360 325.0288 15.72125391563537";

    // Create propagator with 60-second step size
    let prop = bh::SGPPropagator::from_tle(line1, line2, 60.0).unwrap();

    println!("NORAD ID: {}", prop.norad_id);
    println!("TLE epoch: {}", prop.epoch);
    println!("Initial position magnitude: {:.1} km",
             prop.initial_state().fixed_rows::<3>(0).norm() / 1e3);
}
Output
1
2
3
NORAD ID: 25544
TLE epoch: 2008-09-20 12:25:40.104 UTC
Initial position magnitude: 6720.2 km
1
2
3
NORAD ID: 25544
TLE epoch: 2008-09-20 12:25:40.104 UTC
Initial position magnitude: 6720.2 km

From 3-Line Elements (3LE)

Three-line TLE format includes an optional satellite name on the first line.

import brahe as bh

bh.initialize_eop()

# 3-line TLE with satellite name
name = "ISS (ZARYA)"
line1 = "1 25544U 98067A   08264.51782528 -.00002182  00000-0 -11606-4 0  2927"
line2 = "2 25544  51.6416 247.4627 0006703 130.5360 325.0288 15.72125391563537"

# Create propagator with satellite name
prop = bh.SGPPropagator.from_3le(name, line1, line2, 60.0)

print(f"Satellite name: {prop.satellite_name}")
print(f"NORAD ID: {prop.norad_id}")
use brahe as bh;

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

    // 3-line TLE with satellite name
    let name = "ISS (ZARYA)";
    let line1 = "1 25544U 98067A   08264.51782528 -.00002182  00000-0 -11606-4 0  2927";
    let line2 = "2 25544  51.6416 247.4627 0006703 130.5360 325.0288 15.72125391563537";

    // Create propagator with satellite name
    let prop = bh::SGPPropagator::from_3le(Some(name), line1, line2, 60.0).unwrap();

    println!("Satellite name: {:?}", prop.satellite_name);
    println!("NORAD ID: {}", prop.norad_id);
}
Output
Satellite name: ISS (ZARYA)
NORAD ID: 25544
Satellite name: Some("ISS (ZARYA)")
NORAD ID: 25544

Configuring Output Format

By default, SGP4 outputs states in ECI Cartesian coordinates. Use with_output_format() to configure the output frame and representation.

import brahe as bh

bh.initialize_eop()

line1 = "1 25544U 98067A   08264.51782528 -.00002182  00000-0 -11606-4 0  2927"
line2 = "2 25544  51.6416 247.4627 0006703 130.5360 325.0288 15.72125391563537"

# Create with ECEF Cartesian output
prop_ecef = bh.SGPPropagator.from_tle(line1, line2, 60.0)
prop_ecef.set_output_format(bh.OrbitFrame.ECEF, bh.OrbitRepresentation.CARTESIAN, None)

# Or with Keplerian output (ECI only)
prop_kep = bh.SGPPropagator.from_tle(line1, line2, 60.0)
prop_kep.set_output_format(
    bh.OrbitFrame.ECI, bh.OrbitRepresentation.KEPLERIAN, bh.AngleFormat.DEGREES
)

# Propagate to 1 hour after epoch
dt = 3600.0
prop_ecef.propagate_to(prop_ecef.epoch + dt)
prop_kep.propagate_to(prop_kep.epoch + dt)
print(f"ECEF position (km): {prop_ecef.current_state()[:3] / 1e3}")
state_kep = prop_kep.current_state()
print(
    f"Keplerian elements: [{state_kep[0]:.1f} km, {state_kep[1]:.4f}, {state_kep[2]:.4f}, "
    f"{state_kep[3]:.4f} deg, {state_kep[4]:.4f} deg, {state_kep[5]:.4f} deg]"
)
use brahe as bh;
use brahe::traits::{OrbitFrame, SStatePropagator, OrbitRepresentation};

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

    let line1 = "1 25544U 98067A   08264.51782528 -.00002182  00000-0 -11606-4 0  2927";
    let line2 = "2 25544  51.6416 247.4627 0006703 130.5360 325.0288 15.72125391563537";

    // Create with ECEF Cartesian output
    let mut prop_ecef = bh::SGPPropagator::from_tle(line1, line2, 60.0).unwrap()
        .with_output_format(OrbitFrame::ECEF, OrbitRepresentation::Cartesian, None);

    // Or with Keplerian output (ECI only)
    let mut prop_kep = bh::SGPPropagator::from_tle(line1, line2, 60.0).unwrap()
        .with_output_format(OrbitFrame::ECI, OrbitRepresentation::Keplerian, Some(bh::AngleFormat::Degrees));

    // Propagate to 1 hour after epoch
    let dt = 3600.0;
    prop_ecef.propagate_to(prop_ecef.epoch + dt);
    prop_kep.propagate_to(prop_kep.epoch + dt);

    let state_ecef = prop_ecef.current_state();
    println!("ECEF position (km): [{:.3}, {:.3}, {:.3}]",
             state_ecef[0] / 1e3, state_ecef[1] / 1e3, state_ecef[2] / 1e3);

    let state_kep = prop_kep.current_state();
    println!("Keplerian elements: [{:.1} km, {:.4}, {:.4}, {:.4} deg, {:.4} deg, {:.4} deg]",
             state_kep[0] / 1e3, state_kep[1], state_kep[2],
             state_kep[3], state_kep[4], state_kep[5]);
}
Output
ECEF position (km): [ 5548.63233725  2869.31027561 -2526.64252368]
Keplerian elements: [6734739.1 km, 0.0009, 51.6075, 247.0990 deg, 96.4114 deg, 235.1391 deg]
ECEF position (km): [5548.632, 2869.310, -2526.643]
Keplerian elements: [6734.7 km, 0.0009, 51.6075, 247.0990 deg, 96.4114 deg, 235.1391 deg]

Stepping Through Time

The SGP propagator uses the same stepping interface as other propagators through the OrbitPropagator trait.

Single and Multiple Steps

import brahe as bh

bh.initialize_eop()

line1 = "1 25544U 98067A   08264.51782528 -.00002182  00000-0 -11606-4 0  2927"
line2 = "2 25544  51.6416 247.4627 0006703 130.5360 325.0288 15.72125391563537"
prop = bh.SGPPropagator.from_tle(line1, line2, 60.0)

# Single step (60 seconds)
prop.step()
print(f"After 1 step: {prop.current_epoch()}")

# Multiple steps
prop.propagate_steps(10)
print(f"After 11 total steps: {len(prop.trajectory)} states")

# Step by custom duration
prop.step_by(120.0)
print(f"After custom step: {prop.current_epoch()}")
use brahe as bh;
use brahe::traits::{SStatePropagator, Trajectory};

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

    let line1 = "1 25544U 98067A   08264.51782528 -.00002182  00000-0 -11606-4 0  2927";
    let line2 = "2 25544  51.6416 247.4627 0006703 130.5360 325.0288 15.72125391563537";
    let mut prop = bh::SGPPropagator::from_tle(line1, line2, 60.0).unwrap();

    // Single step (60 seconds)
    prop.step();
    println!("After 1 step: {}", prop.current_epoch());

    // Multiple steps
    prop.propagate_steps(10);
    println!("After 11 total steps: {} states", prop.trajectory.len());

    // Step by custom duration
    prop.step_by(120.0);
    println!("After custom step: {}", prop.current_epoch());
}
Output
1
2
3
After 1 step: 2008-09-20 12:26:40.104 UTC
After 11 total steps: 12 states
After custom step: 2008-09-20 12:38:40.104 UTC
1
2
3
After 1 step: 2008-09-20 12:26:40.104 UTC
After 11 total steps: 12 states
After custom step: 2008-09-20 12:38:40.104 UTC

Propagate to Target Epoch

import brahe as bh

bh.initialize_eop()

line1 = "1 25544U 98067A   08264.51782528 -.00002182  00000-0 -11606-4 0  2927"
line2 = "2 25544  51.6416 247.4627 0006703 130.5360 325.0288 15.72125391563537"
prop = bh.SGPPropagator.from_tle(line1, line2, 60.0)

# Propagate to specific epoch
target = prop.epoch + 7200.0  # 2 hours later
prop.propagate_to(target)

print(f"Target epoch: {target}")
print(f"Current epoch: {prop.current_epoch()}")
print(f"Trajectory contains {len(prop.trajectory)} states")
use brahe as bh;
use brahe::traits::{SStatePropagator, Trajectory};

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

    let line1 = "1 25544U 98067A   08264.51782528 -.00002182  00000-0 -11606-4 0  2927";
    let line2 = "2 25544  51.6416 247.4627 0006703 130.5360 325.0288 15.72125391563537";
    let mut prop = bh::SGPPropagator::from_tle(line1, line2, 60.0).unwrap();

    // Propagate to specific epoch
    let target = prop.epoch + 7200.0;  // 2 hours later
    prop.propagate_to(target);

    println!("Target epoch: {}", target);
    println!("Current epoch: {}", prop.current_epoch());
    println!("Trajectory contains {} states", prop.trajectory.len());

}
Output
1
2
3
Target epoch: 2008-09-20 14:25:40.104 UTC
Current epoch: 2008-09-20 14:25:40.104 UTC
Trajectory contains 121 states
1
2
3
Target epoch: 2008-09-20 14:25:40.104 UTC
Current epoch: 2008-09-20 14:25:40.104 UTC
Trajectory contains 121 states

Direct State Queries

The SGP propagator implements the StateProvider trait, allowing direct state computation at arbitrary epochs without stepping. Because SGP4 uses closed-form solutions, state queries are efficient and do not require building a trajectory.

Single Epoch Queries

import brahe as bh

bh.initialize_eop()

line1 = "1 25544U 98067A   08264.51782528 -.00002182  00000-0 -11606-4 0  2927"
line2 = "2 25544  51.6416 247.4627 0006703 130.5360 325.0288 15.72125391563537"
prop = bh.SGPPropagator.from_tle(line1, line2, 60.0)

# Query state 1 orbit later (doesn't add to trajectory)
query_epoch = prop.epoch + 5400.0  # ~90 minutes

state_eci = prop.state_eci(query_epoch)  # ECI Cartesian
state_ecef = prop.state_ecef(query_epoch)  # ECEF Cartesian
state_kep = prop.state_koe_osc(
    query_epoch, bh.AngleFormat.DEGREES
)  # Osculating Keplerian

print(
    f"ECI position: [{state_eci[0] / 1e3:.1f}, {state_eci[1] / 1e3:.1f}, "
    f"{state_eci[2] / 1e3:.1f}] km"
)
print(f"Osculating semi-major axis: {state_kep[0] / 1e3:.1f} km")
use brahe as bh;
use brahe::traits::SOrbitStateProvider;

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

    let line1 = "1 25544U 98067A   08264.51782528 -.00002182  00000-0 -11606-4 0  2927";
    let line2 = "2 25544  51.6416 247.4627 0006703 130.5360 325.0288 15.72125391563537";
    let prop = bh::SGPPropagator::from_tle(line1, line2, 60.0).unwrap();

    // Query state 1 orbit later (doesn't add to trajectory)
    let query_epoch = prop.epoch + 5400.0;  // ~90 minutes

    let state_eci = prop.state_eci(query_epoch).unwrap();          // ECI Cartesian
    let _state_ecef = prop.state_ecef(query_epoch).unwrap();        // ECEF Cartesian
    let state_kep = prop.state_koe_osc(query_epoch, bh::AngleFormat::Degrees).unwrap();    // Osculating Keplerian

    println!("ECI position: [{:.1}, {:.1}, {:.1}] km",
             state_eci[0]/1e3, state_eci[1]/1e3, state_eci[2]/1e3);
    println!("Osculating semi-major axis: {:.1} km", state_kep[0]/1e3);

}
Output
ECI position: [3822.2, -1684.2, 5264.9] km
Osculating semi-major axis: 6725.4 km
ECI position: [3822.2, -1684.2, 5264.9] km
Osculating semi-major axis: 6725.4 km

Batch Queries

import brahe as bh
import numpy as np

bh.initialize_eop()

line1 = "1 25544U 98067A   08264.51782528 -.00002182  00000-0 -11606-4 0  2927"
line2 = "2 25544  51.6416 247.4627 0006703 130.5360 325.0288 15.72125391563537"
prop = bh.SGPPropagator.from_tle(line1, line2, 60.0)

# Generate states for multiple orbits
orbital_period = 5400.0  # Approximate ISS period (seconds)
query_epochs = [prop.epoch + i * orbital_period for i in range(5)]
states_eci = prop.states_eci(query_epochs)

print(f"Generated {len(states_eci)} states over {len(query_epochs)} orbits")
for i, state in enumerate(states_eci):
    altitude = (np.linalg.norm(state[:3]) - bh.R_EARTH) / 1e3
    print(f"  Orbit {i}: altitude = {altitude:.1f} km")
use brahe as bh;
use bh::utils::DOrbitStateProvider;

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

    let line1 = "1 25544U 98067A   08264.51782528 -.00002182  00000-0 -11606-4 0  2927";
    let line2 = "2 25544  51.6416 247.4627 0006703 130.5360 325.0288 15.72125391563537";
    let prop = bh::SGPPropagator::from_tle(line1, line2, 60.0).unwrap();

    // Generate states for multiple orbits
    let orbital_period = 5400.0;  // Approximate ISS period (seconds)
    let query_epochs: Vec<bh::Epoch> = (0..5)
        .map(|i| prop.epoch + i as f64 * orbital_period)
        .collect();
    let states_eci = prop.states_eci(&query_epochs).unwrap();

    println!("Generated {} states over {} orbits", states_eci.len(), query_epochs.len());
    for (i, state) in states_eci.iter().enumerate() {
        let altitude = (state.fixed_rows::<3>(0).norm() - bh::R_EARTH) / 1e3;
        println!("  Orbit {}: altitude = {:.1} km", i, altitude);
    }
}
Output
1
2
3
4
5
6
Generated 5 states over 5 orbits
  Orbit 0: altitude = 342.1 km
  Orbit 1: altitude = 342.3 km
  Orbit 2: altitude = 342.7 km
  Orbit 3: altitude = 343.3 km
  Orbit 4: altitude = 344.0 km
1
2
3
4
5
6
Generated 5 states over 5 orbits
  Orbit 0: altitude = 342.1 km
  Orbit 1: altitude = 342.3 km
  Orbit 2: altitude = 342.7 km
  Orbit 3: altitude = 343.3 km
  Orbit 4: altitude = 344.0 km

Special: PEF Frame

SGP4 natively outputs states in the TEME (True Equator Mean Equinox) frame. For specialized applications, you can access states in the intermediate PEF (Pseudo-Earth-Fixed) frame:

import brahe as bh

bh.initialize_eop()

line1 = "1 25544U 98067A   08264.51782528 -.00002182  00000-0 -11606-4 0  2927"
line2 = "2 25544  51.6416 247.4627 0006703 130.5360 325.0288 15.72125391563537"
prop = bh.SGPPropagator.from_tle(line1, line2, 60.0)

# Get state in PEF frame (TEME rotated by GMST)
state_pef = prop.state_pef(prop.epoch)
print(f"PEF position: {state_pef[:3] / 1e3}")
use brahe as bh;

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

    let line1 = "1 25544U 98067A   08264.51782528 -.00002182  00000-0 -11606-4 0  2927";
    let line2 = "2 25544  51.6416 247.4627 0006703 130.5360 325.0288 15.72125391563537";
    let prop = bh::SGPPropagator::from_tle(line1, line2, 60.0).unwrap();

    // Get state in PEF frame (TEME rotated by GMST)
    let state_pef = prop.state_pef(prop.epoch);
    println!("PEF position: {:?}", state_pef.fixed_rows::<3>(0) / 1e3);
}
Output
PEF position: [-3953.20574821  1427.51460044  5243.61453697]
PEF position: [[-3953.2057482107907, 1427.5146004367573, 5243.614536966578]]

Extracting Orbital Elements from TLE

The propagator can extract Keplerian orbital elements directly from the TLE data:

import brahe as bh

bh.initialize_eop()

line1 = "1 25544U 98067A   08264.51782528 -.00002182  00000-0 -11606-4 0  2927"
line2 = "2 25544  51.6416 247.4627 0006703 130.5360 325.0288 15.72125391563537"
prop = bh.SGPPropagator.from_tle(line1, line2, 60.0)

# Extract Keplerian elements from TLE
elements_deg = prop.get_elements(bh.AngleFormat.DEGREES)
elements_rad = prop.get_elements(bh.AngleFormat.RADIANS)

print(f"Semi-major axis: {elements_deg[0] / 1e3:.1f} km")
print(f"Eccentricity: {elements_deg[1]:.6f}")
print(f"Inclination: {elements_deg[2]:.4f} degrees")
print(f"RAAN: {elements_deg[3]:.4f} degrees")
print(f"Argument of perigee: {elements_deg[4]:.4f} degrees")
print(f"Mean anomaly: {elements_deg[5]:.4f} degrees")
use brahe as bh;

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

    let line1 = "1 25544U 98067A   08264.51782528 -.00002182  00000-0 -11606-4 0  2927";
    let line2 = "2 25544  51.6416 247.4627 0006703 130.5360 325.0288 15.72125391563537";
    let prop = bh::SGPPropagator::from_tle(line1, line2, 60.0).unwrap();

    // Extract Keplerian elements from TLE
    let elements_deg = prop.get_elements(bh::AngleFormat::Degrees).unwrap();
    let _elements_rad = prop.get_elements(bh::AngleFormat::Radians).unwrap();

    println!("Semi-major axis: {:.1} km", elements_deg[0]/1e3);
    println!("Eccentricity: {:.6}", elements_deg[1]);
    println!("Inclination: {:.4} degrees", elements_deg[2]);
    println!("RAAN: {:.4} degrees", elements_deg[3]);
    println!("Argument of perigee: {:.4} degrees", elements_deg[4]);
    println!("Mean anomaly: {:.4} degrees", elements_deg[5]);
}
Output
1
2
3
4
5
6
Semi-major axis: 6731.0 km
Eccentricity: 0.000670
Inclination: 51.6416 degrees
RAAN: 247.4627 degrees
Argument of perigee: 130.5360 degrees
Mean anomaly: 325.0288 degrees
1
2
3
4
5
6
Semi-major axis: 6731.0 km
Eccentricity: 0.000670
Inclination: 51.6416 degrees
RAAN: 247.4627 degrees
Argument of perigee: 130.5360 degrees
Mean anomaly: 325.0288 degrees

Trajectory Management

SGP propagators support the same trajectory management as Keplerian propagators, including frame conversions and memory management.

Memory Management

import brahe as bh

bh.initialize_eop()

line1 = "1 25544U 98067A   08264.51782528 -.00002182  00000-0 -11606-4 0  2927"
line2 = "2 25544  51.6416 247.4627 0006703 130.5360 325.0288 15.72125391563537"
prop = bh.SGPPropagator.from_tle(line1, line2, 60.0)

# Keep only 50 most recent states for memory efficiency
prop.set_eviction_policy_max_size(50)

# Propagate many steps
prop.propagate_steps(200)
print(f"Trajectory length: {len(prop.trajectory)}")  # Will be 50

# Alternative: Keep states within 30 minutes of current
prop.reset()
prop.set_eviction_policy_max_age(1800.0)  # 1800 seconds = 30 minutes
prop.propagate_steps(200)
print(f"Trajectory length with age policy: {len(prop.trajectory)}")
use brahe as bh;
use brahe::traits::{SStatePropagator, Trajectory};

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

    let line1 = "1 25544U 98067A   08264.51782528 -.00002182  00000-0 -11606-4 0  2927";
    let line2 = "2 25544  51.6416 247.4627 0006703 130.5360 325.0288 15.72125391563537";
    let mut prop = bh::SGPPropagator::from_tle(line1, line2, 60.0).unwrap();

    // Keep only 50 most recent states for memory efficiency
    prop.set_eviction_policy_max_size(50).unwrap();

    // Propagate many steps
    prop.propagate_steps(200);
    println!("Trajectory length: {}", prop.trajectory.len());  // Will be 50

    // Alternative: Keep states within 30 minutes of current
    prop.reset();
    prop.set_eviction_policy_max_age(1800.0).unwrap();  // 1800 seconds = 30 minutes
    prop.propagate_steps(200);
    println!("Trajectory length with age policy: {}", prop.trajectory.len());
}
Output
Trajectory length: 50
Trajectory length with age policy: 31
Trajectory length: 50
Trajectory length with age policy: 31

Limitations and Considerations

Immutable Initial Conditions

Unlike the Keplerian propagator, SGP4 initial conditions are derived from the TLE and cannot be changed. Attempting to call set_initial_conditions() will result in a panic:

import brahe as bh
import numpy as np

line1 = "1 25544U 98067A   08264.51782528 -.00002182  00000-0 -11606-4 0  2927"
line2 = "2 25544  51.6416 247.4627 0006703 130.5360 325.0288 15.72125391563537"
prop = bh.SGPPropagator.from_tle(line1, line2, 60.0)

# This will raise an error - SGP initial conditions come from TLE
# prop.set_initial_conditions(...)  # Don't do this!

# To use different orbital elements, create a KeplerianPropagator instead
1
2
3
4
// This will panic - SGP initial conditions come from TLE
// prop.set_initial_conditions(...);  // Don't do this!

// To use different orbital elements, create a KeplerianPropagator instead

Identity Tracking

Like Keplerian propagators, SGP propagators support identity tracking:

import brahe as bh

bh.initialize_eop()

line0 = "ISS (ZARYA)"
line1 = "1 25544U 98067A   08264.51782528 -.00002182  00000-0 -11606-4 0  2927"
line2 = "2 25544  51.6416 247.4627 0006703 130.5360 325.0288 15.72125391563537"

# Create propagator and set identity
prop = bh.SGPPropagator.from_3le(line0, line1, line2, 60.0)

print(f"Name: {prop.get_name()}")
print(f"ID: {prop.get_id()}")
print(f"NORAD ID from TLE: {prop.norad_id}")
use brahe as bh;
use brahe::utils::Identifiable;

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

    let line0 = "ISS (ZARYA)";
    let line1 = "1 25544U 98067A   08264.51782528 -.00002182  00000-0 -11606-4 0  2927";
    let line2 = "2 25544  51.6416 247.4627 0006703 130.5360 325.0288 15.72125391563537";

    // Create propagator and set identity
    let prop = bh::SGPPropagator::from_3le(Some(line0), line1, line2, 60.0).unwrap();

    println!("Name: {:?}", prop.get_name());
    println!("ID: {:?}", prop.get_id());
    println!("NORAD ID from TLE: {}", prop.norad_id);
}
Output
1
2
3
Name: ISS (ZARYA)
ID: 25544
NORAD ID from TLE: 25544
1
2
3
Name: Some("ISS (ZARYA)")
ID: Some(25544)
NORAD ID from TLE: 25544

See Also