Two-Line Element sets (TLEs) are a standardized format for representing satellite orbital data. Originally developed by NORAD (North American Aerospace Defense Command), TLEs encode an epoch, Keplerian orbital elements, and additional parameters needed for SGP4/SDP4 propagation into two 69-character ASCII lines.
An example of a TLE set for the International Space Station (ISS) is:
For complete API documentation, see the TLE reference.
TLE Accuracy Limitations
TLEs are designed for near-Earth satellites and have limited accuracy due to simplifications in the SGP4/SDP4 models. They ARE NOT suitable for high-precision orbit determination or long-term predictions.
NORAID ID Exhaustion
TLEs were originally designed for a maximum of 99,999 cataloged objects. However with the rise of mega-constellations and recent anti-satellite tests by Russia and China, the number of tracked objects is rapidly approaching this limit.
The Alpha-5 NORAD ID format extends the range by using letters A-Z (excluding I and O) as the leading character, allowing for up to 339,999 objects. This is a temporary solution however, and generally organizations should plan to transition to using formats like General Perturbations (GP) elements, CCSDS Orbit Ephemeris Messages (OEM), or other modern representations.
A common variant of TLEs is the Three-Line Element set (3LE), which adds a title line above the standard two lines for the object name. Brahe's TLE functions work with both TLE and 3LE formats interchangeably.
importbraheasbh# ISS TLE (NORAD ID 25544)line1="1 25544U 98067A 25302.48953544 .00013618 00000-0 24977-3 0 9995"line2="2 25544 51.6347 1.5519 0004808 353.3325 6.7599 15.49579513535999"# Validate the complete TLE set (both lines must have matching NORAD IDs)is_valid=bh.validate_tle_lines(line1,line2)print(f"TLE set valid: {is_valid}")# Validate individual linesline1_valid=bh.validate_tle_line(line1)line2_valid=bh.validate_tle_line(line2)print(f"Line 1 valid: {line1_valid}")print(f"Line 2 valid: {line2_valid}")# Expected output:# TLE set valid: True# Line 1 valid: True# Line 2 valid: True
usebraheasbh;fnmain(){bh::initialize_eop().unwrap();// ISS TLE (NORAD ID 25544)letline1="1 25544U 98067A 25302.48953544 .00013618 00000-0 24977-3 0 9995";letline2="2 25544 51.6347 1.5519 0004808 353.3325 6.7599 15.49579513535999";// Validate the complete TLE set (both lines must have matching NORAD IDs)letis_valid=bh::validate_tle_lines(line1,line2);println!("TLE set valid: {}",is_valid);// Validate individual linesletline1_valid=bh::validate_tle_line(line1);letline2_valid=bh::validate_tle_line(line2);println!("Line 1 valid: {}",line1_valid);println!("Line 2 valid: {}",line2_valid);// Expected output:// TLE set valid: true// Line 1 valid: true// Line 2 valid: true}
The validate_tle_lines function checks that both lines have the correct format, valid checksums, and matching NORAD catalog numbers.
usebraheasbh;fnmain(){bh::initialize_eop().unwrap();// ISS TLE (NORAD ID 25544)letline1="1 25544U 98067A 25302.48953544 .00013618 00000-0 24977-3 0 9995";letline2="2 25544 51.6347 1.5519 0004808 353.3325 6.7599 15.49579513535999";// Calculate checksums for each lineletchecksum1=bh::calculate_tle_line_checksum(line1);letchecksum2=bh::calculate_tle_line_checksum(line2);println!("Line 1 checksum: {}",checksum1);println!("Line 2 checksum: {}",checksum2);// Example with corrupted TLE (wrong checksum)letcorrupted_line1="1 25544U 98067A 25302.48953544 .00013618 00000-0 24977-3 0 9990";letis_corrupted_valid=bh::validate_tle_line(corrupted_line1);println!("\nCorrupted line valid: {}",is_corrupted_valid);// Expected output:// Line 1 checksum: 5// Line 2 checksum: 9//// Corrupted line valid: false}
Checksum Algorithm
The checksum is calculated by summing all digits in the line (treating minus signs as 1) and taking the result modulo 10. All other characters (letters, spaces, periods) are ignored in the checksum calculation.
importbraheasbh# ISS TLE (NORAD ID 25544)line1="1 25544U 98067A 25302.48953544 .00013618 00000-0 24977-3 0 9995"line2="2 25544 51.6347 1.5519 0004808 353.3325 6.7599 15.49579513535999"# Parse TLE to extract epoch and orbital elementsepoch,elements=bh.keplerian_elements_from_tle(line1,line2)# Extract individual orbital elements# Note: Angles are returned in degrees (exception to library convention)a=elements[0]# Semi-major axis (m)e=elements[1]# Eccentricityi=elements[2]# Inclination (deg)raan=elements[3]# Right Ascension of Ascending Node (deg)argp=elements[4]# Argument of Periapsis (deg)M=elements[5]# Mean Anomaly (deg)print(f"ISS Orbital Elements (Epoch: {epoch})")print(f" Semi-major axis: {a/1000:.3f} km")print(f" Eccentricity: {e:.6f}")print(f" Inclination: {i:.4f} deg")print(f" RAAN: {raan:.4f} deg")print(f" Arg of Perigee: {argp:.4f} deg")print(f" Mean Anomaly: {M:.4f} deg")# Expected output:# ISS Orbital Elements (Epoch: 2025-10-29 11:44:55.862 UTC)# Semi-major axis: 6796.092 km# Eccentricity: 0.000481# Inclination: 51.6347 deg# RAAN: 1.5519 deg# Arg of Perigee: 353.3325 deg# Mean Anomaly: 6.7599 deg
importbraheasbh# ISS TLE (NORAD ID 25544)line1="1 25544U 98067A 25302.48953544 .00013618 00000-0 24977-3 0 9995"# Extract epoch from line 1 (epoch is encoded in line 1 only)epoch=bh.epoch_from_tle(line1)print(f"TLE Epoch: {epoch}")print(f"Time System: {epoch.time_system}")# Expected output:# TLE Epoch: 2025-10-29 11:44:55.862 UTC# Time System: UTC
usebraheasbh;fnmain(){bh::initialize_eop().unwrap();// ISS TLE (NORAD ID 25544)letline1="1 25544U 98067A 25302.48953544 .00013618 00000-0 24977-3 0 9995";// Extract epoch from line 1 (epoch is encoded in line 1 only)letepoch=bh::epoch_from_tle(line1).unwrap();println!("TLE Epoch: {}",epoch);println!("Time System: {:?}",epoch.time_system);// Expected output:// TLE Epoch: 2025-10-29 11:44:55.862 UTC// Time System: UTC}
The epoch is always returned in the UTC time system.
importbraheasbhimportnumpyasnp# Define orbital epochepoch=bh.Epoch.from_datetime(2025,10,29,11,44,55.766182,0,bh.TimeSystem.UTC)# Define ISS orbital elements# Order: [a, e, i, raan, argp, M]# Note: Angles must be in DEGREES for TLE creation (exception to library convention)elements=np.array([6795445.0,# Semi-major axis (m)0.0004808,# Eccentricity51.6347,# Inclination (deg)1.5519,# Right Ascension of Ascending Node (deg)353.3325,# Argument of Periapsis (deg)6.7599,# Mean Anomaly (deg)])# Create TLE lines with NORAD IDnorad_id="25544"line1,line2=bh.keplerian_elements_to_tle(epoch,elements,norad_id)print("Generated TLE:")print(line1)print(line2)# Expected output:# Generated TLE:# 1 25544U 25302.48953433 .00000000 00000+0 00000+0 0 00002# 2 25544 51.6347 1.5519 0004808 353.3325 6.7599 15.49800901000006
usebraheasbh;usenalgebraasna;fnmain(){bh::initialize_eop().unwrap();// Define orbital epochletepoch=bh::Epoch::from_datetime(2025,10,29,11,44,55.766182,0.0,bh::TimeSystem::UTC);// Define ISS orbital elements// Order: [a, e, i, raan, argp, M]// Note: Angles must be in DEGREES for TLE creation (exception to library convention)letelements=na::SVector::<f64,6>::new(6795445.0,// Semi-major axis (m)0.0004808,// Eccentricity51.6347,// Inclination (deg)1.5519,// Right Ascension of Ascending Node (deg)353.3325,// Argument of Periapsis (deg)6.7599// Mean Anomaly (deg));// Create TLE lines with NORAD IDletnorad_id="25544";let(line1,line2)=bh::keplerian_elements_to_tle(&epoch,&elements,norad_id).unwrap();println!("Generated TLE:");println!("{}",line1);println!("{}",line2);// Expected output:// Generated TLE:// 1 25544U 25302.48953433 .00000000 00000+0 00000+0 0 00002// 2 25544 51.6347 1.5519 0004808 353.3325 6.7599 15.49800901000006}
Default Values
The keplerian_elements_to_tle function uses zero for fields like drag terms and derivatives. For complete control over all TLE fields, use the create_tle_lines function with its full parameter set.
Mean Element Representation
The TLE format encodes the orbital state as mean orbital elements estimated from orbit propgation using the SGP4/SDP4 models.
While the package allows for direclty creating TLEs from arbitrary Keplerian elements, the resulting TLEs WILL NOT accurate propagation results with the SGP4/SDP4 models unless the input elements are already mean elements derived from those models.
If you need to create TLEs for real satellites it's best to estimate the mean elements from observed data using orbit determination techniques using the SGP4/SDP4 models.
You can verify generated TLEs by parsing them back:
# Define orbital epochepoch=bh.Epoch.from_datetime(2025,10,29,11,44,55.766182,0,bh.TimeSystem.UTC)# Define ISS orbital elements (angles in degrees)elements=np.array([6795445.0,# Semi-major axis (m)0.0004808,# Eccentricity51.6347,# Inclination (deg)1.5519,# RAAN (deg)353.3325,# Argument of Periapsis (deg)6.7599,# Mean Anomaly (deg)])# Create TLE
// Define orbital epochletepoch=bh::Epoch::from_datetime(2025,10,29,11,44,55.766182,0.0,bh::TimeSystem::UTC);// Define ISS orbital elements (angles in degrees)letelements=na::SVector::<f64,6>::new(6795445.0,// Semi-major axis (m)0.0004808,// Eccentricity51.6347,// Inclination (deg)1.5519,// RAAN (deg)353.3325,// Argument of Periapsis (deg)6.7599// Mean Anomaly (deg));// Create TLEletnorad_id="25544";let(line1,line2)=bh::keplerian_elements_to_tle(&epoch,&elements,norad_id).unwrap();// Verify by parsing the generated TLE backlet(parsed_epoch,parsed_elements)=bh::keplerian_elements_from_tle(&line1,&line2).unwrap();println!("Verification:");println!("Epoch matches: {}",(epoch.jd()-parsed_epoch.jd()).abs()<1e-9);letelements_match=elements.iter().zip(parsed_elements.iter()).all(|(a,b)|(a-b).abs()/a.abs().max(1e-10)<1e-5);println!("Elements match: {}",elements_match);// Expected output:
importbraheasbh# Parse NORAD IDs in different formatsprint("Parsing NORAD IDs:")# Numeric format (standard)norad_numeric=bh.parse_norad_id("25544")print(f" '25544' -> {norad_numeric}")