Skip to content

CelesTrak Data Source

CelesTrak is a public source for satellite Two-Line Element (TLE) data, maintained by T.S. Kelso since 1985. It provides free, frequently updated orbital element sets for thousands of satellites, making it a useful resource for satellite tracking, orbit determination, and space situational awareness.

Respectful Usage

CelesTrak is freely available for public use, but users should be respectful of the service. Avoid excessive automated requests, and design your calls to take advantage of caching to minimize repeated queries. For large-scale or commercial applications, consider setting up a single download and local caching strategy to disribute ephemeris data internally.

Overview

What is CelesTrak?

CelesTrak is a public data source for satellite orbital elements, maintained by Dr. T.S. Kelso since 1985. It provides free, frequently updated Two-Line Element (TLE) data for thousands of satellites, making it an essential resource for satellite tracking, orbit determination, and space situational awareness.

TLE Format

Two-Line Elements (TLEs) are a compact text format for encoding satellite orbital parameters compatible with the SGP4/SDP4 propagation models. For more information on TLEs, see the Two-Line Elements documentation.

Caching

To minimize load on CelesTrak's servers and improve performance, brahe implements a 6-hour cache for downloaded data:

  • Cache key: Satellite group name (e.g., "starlink", "stations")
  • Cache duration: 6 hours (default, configurable)
  • Cache location: System temp directory

When you request a satellite by ID or name with a group hint, brahe checks if that group was recently downloaded and uses cached data if available. This is much faster and more respectful than making individual requests.

Customizing Cache

See the Caching documentation for details on customizing cache behavior.

Usage

Getting Ephemeris by Group

The most efficient way to get TLE data is by downloading entire groups. This minimizes API requests and leverages caching:

import brahe as bh

# Initialize EOP data
bh.initialize_eop()

# Download TLE data for the Starlink group
# This fetches all Starlink satellites in one request
tles = bh.datasets.celestrak.get_tles("starlink")

print(f"Downloaded {len(tles)} Starlink TLEs")

# Each TLE is a tuple of (name, line1, line2)
name, line1, line2 = tles[0]
print("\nFirst TLE:")
print(f"  Name: {name}")
print(f"  Line 1: {line1}")
print(f"  Line 2: {line2}")

# Expected output:
# Downloaded 8647 Starlink TLEs

# First TLE:
#   Name: STARLINK-1008
#   Line 1: 1 44714U 19074B   25306.45157821  .00002551  00000+0  19011-3 0  9997
#   Line 2: 2 44714  53.0544  37.8105 0001365  79.2826 280.8316 15.06391189329573
use brahe as bh;

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

    // Download TLE data for the Starlink group
    // This fetches all Starlink satellites in one request
    let tles = bh::datasets::celestrak::get_tles("starlink").unwrap();

    println!("Downloaded {} Starlink TLEs", tles.len());

    // Each TLE is a tuple of (name, line1, line2)
    let (name, line1, line2) = &tles[0];
    println!("\nFirst TLE:");
    println!("  Name: {}", name);
    println!("  Line 1: {}", line1);
    println!("  Line 2: {}", line2);

    // Expected output:
    // Downloaded 8647 Starlink TLEs

    // First TLE:
    //   Name: STARLINK-1008
    //   Line 1: 1 44714U 19074B   25306.45157821  .00002551  00000+0  19011-3 0  9997
    //   Line 2: 2 44714  53.0544  37.8105 0001365  79.2826 280.8316 15.06391189329573
}

Getting a Satellite by ID

To get a specific satellite, provide its NORAD ID. Always include a group hint to enable cache-efficient lookups:

import brahe as bh

# Initialize EOP data
bh.initialize_eop()

# Get ISS TLE by NORAD ID
# The group hint ("stations") allows brahe to check cached data first
name, line1, line2 = bh.datasets.celestrak.get_tle_by_id(25544, "stations")

# Parse TLE data to get epoch and orbital elements
epoch, oe = bh.keplerian_elements_from_tle(line1, line2)

print("ISS TLE:")
print(f"  Name: {name}")
print(f"  Epoch: {epoch}")
print(f"  Inclination: {oe[2]:.2f}°")
print(f"  RAAN: {oe[3]:.2f}°")
print(f"  Eccentricity: {oe[1]:.6f}")

# Expected output:
# ISS TLE:
#   Name: ISS (ZARYA)
#   Epoch: 2025-11-02 10:09:34.283 UTC
#   Inclination: 51.63°
#   RAAN: 342.07°
#   Eccentricity: 0.000497
use brahe as bh;

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

    // Get ISS TLE by NORAD ID
    // The group hint ("stations") allows brahe to check cached data first
    let (name, line1, line2) = bh::datasets::celestrak::get_tle_by_id(25544, Some("stations")).unwrap();

    // Parse TLE data to get epoch and orbital elements
    let (epoch, oe) = bh::keplerian_elements_from_tle(&line1, &line2).unwrap();

    println!("ISS TLE:");
    println!("  Name: {}", name);
    println!("  Epoch: {}", epoch);
    println!("  Inclination: {:.2}°", oe[2]);
    println!("  RAAN: {:.2}°", oe[3]);
    println!("  Eccentricity: {:.6}", oe[1]);

    // Expected output:
    // ISS TLE:
    //   Name: ISS (ZARYA)
    //   Epoch: 2025-11-02 10:09:34.283 UTC
    //   Inclination: 51.63°
    //   RAAN: 342.07°
    //   Eccentricity: 0.000497
}

Cache-Efficient Pattern

The most efficient workflow is:

  1. Download the group once: get_tles("stations")
  2. Query specific satellites with the group hint: get_tle_by_id(25544, "stations")

This pattern uses cached data and avoids redundant downloads.

Converting to Propagators

For most applications, you'll want to convert TLEs directly to SGP propagators. Brahe provides convenience functions that do this in one step:

import brahe as bh

# Initialize EOP data
bh.initialize_eop()

# Get ISS as a propagator with 60-second step size
# The group hint ("stations") uses cached data for efficiency
iss_prop = bh.datasets.celestrak.get_tle_by_id_as_propagator(25544, 60.0, "stations")

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

# Expected output:
# Created propagator: ISS (ZARYA)
# Epoch: 2025-11-02 10:09:34.283 UTC

# State after 1 orbit:
#   Position: [6451630.2, -2126316.1, 34427.2] m
#   Velocity: [2019.6, 5281.4, 6006.2] m/s
use brahe as bh;
use bh::traits::SStatePropagator;
use bh::utils::Identifiable;

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

    // Get ISS as a propagator with 60-second step size
    // The group hint ("stations") uses cached data for efficiency
    let mut iss_prop = bh::datasets::celestrak::get_tle_by_id_as_propagator(
        25544,
        Some("stations"),
        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]
    );

    // Expected output:
    // Created propagator: ISS (ZARYA)
    // Epoch: 2025-11-02 10:09:34.283 UTC

    // State after 1 orbit:
    //   Position: [6451630.2, -2126316.1, 34427.2] m
    //   Velocity: [2019.6, 5281.4, 6006.2] m/s
}

Getting by Name

You can also search for satellites by name. This performs a cascading search across groups:

import brahe as bh

# Initialize EOP data
bh.initialize_eop()

# Search by name (checks common groups automatically)
iss_name, iss_line1, iss_line2 = bh.datasets.celestrak.get_tle_by_name("ISS")

print("Search without group hint:")
print(f"  Found: {iss_name}")
print(f"  Line 1: {iss_line1}")
print(f"  Line 2: {iss_line2}")

# Or provide a group hint for faster lookup
iss_name2, iss_line2_1, iss_line2_2 = bh.datasets.celestrak.get_tle_by_name(
    "ISS", "stations"
)

print("\nSearch with group hint:")
print(f"  Found: {iss_name2}")
print(f"  Line 1: {iss_line2_1}")
print(f"  Line 2: {iss_line2_2}")

# Expected output:
# Search without group hint:
#   Found: ISS (ZARYA)
#   Line 1: 1 25544U 98067A   25306.42331346  .00010070  00000+0  18610-3 0  9998
#   Line 2: 2 25544  51.6344 342.0717 0004969   8.9436 351.1640 15.49700017536601

# Search with group hint:
#   Found: ISS (ZARYA)
#   Line 1: 1 25544U 98067A   25306.42331346  .00010070  00000+0  18610-3 0  9998
#   Line 2: 2 25544  51.6344 342.0717 0004969   8.9436 351.1640 15.49700017536601
use brahe as bh;

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

    // Search by name (checks common groups automatically)
    let (iss_name, iss_line1, iss_line2) =
        bh::datasets::celestrak::get_tle_by_name("ISS", None).unwrap();

    println!("Search without group hint:");
    println!("  Found: {}", iss_name);
    println!("  Line 1: {}", &iss_line1);
    println!("  Line 2: {}", &iss_line2);

    // Or provide a group hint for faster lookup
    let (iss_name2, iss_line2_1, iss_line2_2) =
        bh::datasets::celestrak::get_tle_by_name("ISS", Some("stations")).unwrap();

    println!("\nSearch with group hint:");
    println!("  Found: {}", iss_name2);
    println!("  Line 1: {}", &iss_line2_1);
    println!("  Line 2: {}", &iss_line2_2);

    // Expected output:
    // Search without group hint:
    //   Found: ISS (ZARYA)
    //   Line 1: 1 25544U 98067A   25306.42331346  .00010070  00000+0  18610-3 0  9998
    //   Line 2: 2 25544  51.6344 342.0717 0004969   8.9436 351.1640 15.49700017536601

    // Search with group hint:
    //   Found: ISS (ZARYA)
    //   Line 1: 1 25544U 98067A   25306.42331346  .00010070  00000+0  18610-3 0  9998
    //   Line 2: 2 25544  51.6344 342.0717 0004969   8.9436 351.1640 15.49700017536601
}

Name Matching

Name searches are case-insensitive and support partial matches. If multiple satellites match, the function returns the first match.

Satellite Groups

CelesTrak organizes satellites into logical groups accessible via simple names. These groups are updated as active constellations evolve. It is best to download TLEs by group name rather than ID to minimize the number of distinct requests.

Temporal Groups

Group Description
active All active satellites
last-30-days Recently launched satellites
tle-new Newly added TLEs (last 15 days)

Communications

Group Description
starlink SpaceX Starlink constellation
oneweb OneWeb constellation
kuiper Amazon Kuiper constellation
intelsat Intelsat satellites
eutelsat Eutelsat constellation
orbcomm ORBCOMM constellation
telesat Telesat constellation
globalstar Globalstar constellation
iridium-NEXT Iridium constellation
qianfan Qianfan constellation
hulianwang Hulianwang Digui constellation

Earth Observation

Group Description
weather Weather satellites (NOAA, GOES, Metop, etc.)
earth-resources Earth observation (Landsat, Sentinel, etc.)
planet Planet Labs imaging satellites
spire Spire Global satellites
Group Description
gnss All navigation satellites (GPS, GLONASS, Galileo, BeiDou, QZSS, IRNSS)
gps-ops Operational GPS satellites only
glonass-ops Operational GLONASS satellites only
galileo European Galileo constellation
beidou Chinese BeiDou/COMPASS constellation
sbas Satellite-Based Augmentation System (WAAS/EGNOS/MSAS)

Scientific and Special Purpose

Group Description
science Scientific research satellites
noaa NOAA satellites
stations Space stations (ISS, Tiangong)
analyst Analyst satellites (tracking placeholder IDs)
visual 100 (or so) brightest objects
gpz Geostationary Protected Zone
gpz-plus Geostationary Protected Zone Plus

Note: Group names and contents evolve as missions launch, deorbit, or change status. Visit CelesTrak GP Element Sets for the current complete list.


See Also