# Two-Line Elements (TLE)

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:

```
1 25544U 98067A   25302.48953544  .00013618  00000-0  24977-3 0  9995
2 25544  51.6347   1.5519 0004808 353.3325   6.7599 15.49579513535999
```

TLEs are still widely used for satellite tracking and orbit prediction, distributed by organizations like [CelesTrak](https://celestrak.org) and [Space-Track](https://www.space-track.org).

For additional information on the TLE format and field definitions, see the [CelesTrak TLE documentation](https://celestrak.org/columns/v04n03/) or the [Wikipedia TLE article](https://en.wikipedia.org/wiki/Two-line_element_set).

For complete API documentation, see the [TLE reference](../../library_api/orbits/tle.md).

**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.

The same TLE data in 3LE format would be:

```
ISS (ZARYA)             
1 25544U 98067A   25302.48953544  .00013618  00000-0  24977-3 0  9995
2 25544  51.6347   1.5519 0004808 353.3325   6.7599 15.49579513535999
```

## Validating TLEs

Before parsing TLE data, you can validate the format and checksums to ensure data integrity.

### Validating a TLE Set


```
import brahe as bh

# 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 lines
line1_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}")
```

The `validate_tle_lines` function checks that both lines have the correct format, valid checksums, and matching NORAD catalog numbers.

### Calculating Checksums

Each TLE line ends with a modulo-10 checksum. You can calculate this checksum to verify data integrity or when creating custom TLEs:


```
import brahe as bh

# 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"

# Calculate checksums for each line
checksum1 = bh.calculate_tle_line_checksum(line1)
checksum2 = bh.calculate_tle_line_checksum(line2)
print(f"Line 1 checksum: {checksum1}")
print(f"Line 2 checksum: {checksum2}")

# Example with corrupted TLE (wrong checksum)
corrupted_line1 = (
    "1 25544U 98067A   25302.48953544  .00013618  00000-0  24977-3 0  9990"
)
is_corrupted_valid = bh.validate_tle_line(corrupted_line1)
print(f"\nCorrupted line valid: {is_corrupted_valid}")
```

**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.

## Parsing TLEs

### Extracting Orbital Elements

The most common operation is parsing a TLE to extract the epoch and orbital elements:


```
import brahe as bh

# 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 elements
epoch, 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]  # Eccentricity
i = 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")
```

The returned elements follow the standard Brahe order: `[a, e, i, Ω, ω, M]` where:

- $a$ - Semi-major axis (meters)
- $e$ - Eccentricity (dimensionless)
- $i$ - Inclination (degrees)
- $\Omega$ - Right Ascension of Ascending Node (degrees)
- $\omega$ - Argument of Periapsis (degrees)
- $M$ - Mean Anomaly (degrees)

**Angle Units Convention**
TLE functions use **degrees** for all angles. This matches the TLE format standard and makes it easier to work with TLE data directly.

### Extracting Just the Epoch

If you only need the epoch timestamp without the full orbital elements:


```
import brahe as bh

# 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}")
```

The epoch is always returned in the UTC time system.

## Creating TLEs

### From Keplerian Elements

You can generate TLE lines from an epoch and mean orbital elements:


```
import brahe as bh
import numpy as np

# Define orbital epoch
epoch = 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,  # Eccentricity
        51.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 ID
norad_id = "25544"
line1, line2 = bh.keplerian_elements_to_tle(epoch, elements, norad_id)

print("Generated TLE:")
print(line1)
print(line2)
```

**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 epoch
epoch = 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,  # Eccentricity
        51.6347,  # Inclination (deg)
        1.5519,  # RAAN (deg)
        353.3325,  # Argument of Periapsis (deg)
        6.7599,  # Mean Anomaly (deg)
    ]
)

# Create TLE
```

## NORAD ID Formats

TLEs support two formats for NORAD catalog numbers:

- **Numeric**: 5-digit numbers (00001-99999)
- **Alpha-5**: 5-character alphanumeric (A0000-Z9999)

The Alpha-5 format extends the catalog beyond 99,999 satellites by using letters A-Z (excluding I and O to avoid confusion with 1 and 0).

### Converting Between Formats


```
import brahe as bh

print("NORAD ID Format Conversions\n")

# Convert numeric to Alpha-5 (only works for IDs >= 100000)
print("Numeric to Alpha-5:")
alpha5_low = bh.norad_id_numeric_to_alpha5(25544)
print(f"  25544 -> {alpha5_low}")

alpha5_high = bh.norad_id_numeric_to_alpha5(100000)
print(f"  100000 -> {alpha5_high}")

alpha5_higher = bh.norad_id_numeric_to_alpha5(123456)
print(f"  123456 -> {alpha5_higher}")

# Convert Alpha-5 to numeric
print("\nAlpha-5 to Numeric:")
numeric_1 = bh.norad_id_alpha5_to_numeric("A0001")
print(f"  'A0001' -> {numeric_1}")

numeric_2 = bh.norad_id_alpha5_to_numeric("L0000")
print(f"  'L0000' -> {numeric_2}")

# Round-trip conversion
print("\nRound-trip Conversion:")
original = 200000
alpha5 = bh.norad_id_numeric_to_alpha5(original)
back_to_numeric = bh.norad_id_alpha5_to_numeric(alpha5)
print(f"  {original} -> '{alpha5}' -> {back_to_numeric}")
print(f"  Match: {original == back_to_numeric}")
```

**Alpha-5 Range**
Alpha-5 format is only valid for NORAD IDs >= 100,000. The range is 100,000 (A0000) to 339,999 (Z9999).

### Parsing Mixed Formats

The `parse_norad_id` function automatically detects whether a NORAD ID is in numeric or Alpha-5 format:


```
import brahe as bh

# Parse NORAD IDs in different formats
print("Parsing NORAD IDs:")

# Numeric format (standard)
norad_numeric = bh.parse_norad_id("25544")
print(f"  '25544' -> {norad_numeric}")

```

---

## See Also

- [SGP Propagator](../../library_api/propagators/sgp_propagator.md) - Use TLEs with SGP4/SDP4 propagation
- [Keplerian Elements](../../library_api/orbits/keplerian.md) - Working with orbital elements
- [Downloading TLE Data](../../examples/visualizing_starlink.md) - How to fetch current TLEs from online sources
- [Epoch](../time/epoch.md) - Understanding time representation in Brahe