Loading and Filtering TLE Lists

Many applications require working with multiple TLEs. Sometimes the task requires finding a correct set of TLEs within a large list or, propagating multiple TLEs of the same satellite over a long duration requires a time-ordered series of TLEs. This tutorial will introduce the convenience classes to load and filter such lists of TLEs.

Initialising the Environment

As usual, Orekit needs to be initialised first. Note that, the Orekit reference data zip directory may need to be changed with your configuration. Orekit reference data can also be downloaded as a zip file from the main Orekit website. The orekit.initVM() command initialises the Java Virtual Machine in the background.

[1]:
from pathlib import Path

# If satkit import fails, try to locate the module
import os

try:
    import satkit
except ModuleNotFoundError:
    os.chdir(os.path.join("..", ".."))
    os.getcwd()

from satkit import init_satkit, u

init_satkit(Path("data", "orekit-data", "orekit-data-reference.zip"), Path("..", ".."))

# Orekit / satkit init complete
[1]:
PosixPath('/home/egemen/Projects/PycharmProjects/satkit/data/orekit-data/orekit-data-reference.zip')

Loading and Filtering the TLE Data

Now that Orekit is initialised, we can start working with TLE data. The most common application is to load a file (or a string) containing TLE data in text format and filtering satellites with a certain parameter.

The process_paths() method provides alternate directories to look for the TLE file path. But more importantly, the TleStorage.from_path() method reads the TLE file (in plaintext) and stores the contents as a list inside the tle_storage_1 object. Its contents can be queried by the internal list tle_list. The TleSotrage object offers the filtering functionality, in this example filtering for the satellite ID 46495. The filter_by_value() method looks for exact matches in the TLE data. The enumerator TleValueFilterParams gives the list of parameters (or exact matching values) with which the TLE list can be filtered.

[2]:
from satkit import process_paths
from satkit.propagation.tle_list import (
    TleRangeFilterParams,
    TleStorage,
    TleValueFilterParams,
)

from org.orekit.time import AbsoluteDate, TimeScalesFactory

# use case 1, load from TLE file, filter for a certain satellite number
# ---------------------------------------------------------------------
alt_intermed_path = Path("docs", "tutorials")
mixed_tle_file_path_1 = Path("data", "tle_mixed_1.txt")

file_path = process_paths(mixed_tle_file_path_1, alt_intermed_path)

tle_storage_1 = TleStorage.from_path(file_path)

# Print the first element of the TLE file as an example
print(tle_storage_1.tle_list[0])

# filter for a specific satellite number
filtered_list_1 = tle_storage_1.filter_by_value(TleValueFilterParams.SAT_NR, 46495)

# Print the filtered element of the filtered TLE list
print(filtered_list_1.tle_list[0])
1 28366U 04025A   21088.48552344  .00000020  00000-0  20339-4 0  9993
2 28366  98.2305 333.6788 0106609 231.7460 127.4097 14.36733092877928
1 46495U 20068K   21089.25908752 -.00000061  00000-0  99758-7 0  9995
2 46495  97.6865  27.2569 0016962 346.9048  13.1734 15.03385776 27430

Another use case is to request the TLEs from an online source using an API, in which case there is no file involved and the contents are usually stored a string. In the following example, the TLE file is read into a string and then stored into the TleStorage object using the from_string() method. The result is equivalent to the from_file() method - the TLE storage is initialised.

The second way to filter the TLE data is to look for a range of values (rather than exact matches). The filter_by_range() method is used for this purpose, when a min_value and/or max_value is used as a keyword argument. The optional includes_bounds boolean keyword controls whether the boundaries are to be included or not. In this example, “all the TLE values after ‘2021-02-01T00:00:00.000’ (exclusive)” is extracted from the TLE list. Note the use of the enumerator TleRangeFilterParams, giving a list of parameters that can be filtered.

[3]:
# use case 2, load from TLE string, filter for a certain epoch range with lower bound only
# ----------------------------------------------------------------------------------------
with open(file_path, "r") as f:
    tle_source_str = f.read()

tle_storage_2 = TleStorage.from_string(tle_source_str)

# filter for TLEs after a specific epoch
threshold_time = AbsoluteDate("2021-02-01T00:00:00.000", TimeScalesFactory.getUTC())
filtered_list_2 = tle_storage_2.filter_by_range(TleRangeFilterParams.EPOCH, min_value=threshold_time)

# Print the first element of the filtered TLE list
print(filtered_list_2.tle_list[0])
1 28366U 04025A   21088.48552344  .00000020  00000-0  20339-4 0  9993
2 28366  98.2305 333.6788 0106609 231.7460 127.4097 14.36733092877928

More examples can be constructed like the one above. In the following examples, upper and lower date ranges as well as inclination and eccentricity ranges are shown. Note the use of u, a shorthand for the Quantity object from pint. Through this, values with units can be correctly defined and handled. The same inclination value could have been defined in radians and supplied to the function, yielding the same result.

[4]:
# use case 3, filter for a certain epoch range with upper and lower bounds
# ------------------------------------------------------------------------

# filtering with a max and min epoch date
min_threshold_time = AbsoluteDate("2021-03-29T00:00:00.000", TimeScalesFactory.getUTC())
max_threshold_time = AbsoluteDate("2021-03-29T13:00:00.000", TimeScalesFactory.getUTC())
filtered_list_3 = tle_storage_2.filter_by_range(TleRangeFilterParams.EPOCH, min_value=min_threshold_time,
                                                max_value=max_threshold_time)

# Print the first element of the filtered TLE list
print(filtered_list_3.tle_list[0])

# use case 4, filter for a minimum inclination
# --------------------------------------------

from satkit import u

# filtering with a min inclination
min_inclination = 90 * u.deg
filtered_list_4 = tle_storage_2.filter_by_range(TleRangeFilterParams.INCLINATION, min_value=min_inclination)

# Print the first element of the filtered TLE list
print(filtered_list_4.tle_list[0])

# use case 5, filter for a maximum eccentricity
# ---------------------------------------------

# filtering with a max eccentricity
max_e = 0.001
filtered_list_5 = tle_storage_2.filter_by_range(TleRangeFilterParams.E, max_value=max_e)

# Print the first element of the filtered TLE list
print(filtered_list_5.tle_list[0])
1 28366U 04025A   21088.48552344  .00000020  00000-0  20339-4 0  9993
2 28366  98.2305 333.6788 0106609 231.7460 127.4097 14.36733092877928
1 28366U 04025A   21088.48552344  .00000020  00000-0  20339-4 0  9993
2 28366  98.2305 333.6788 0106609 231.7460 127.4097 14.36733092877928
1 28493U 04049B   21088.19089737  .00000138  00000-0  28952-4 0  9998
2 28493  98.1078 184.0042 0003150 277.3119  82.7731 14.73658166874283

In addition to the filter_by_value and filter_by_range methods, there is a third and very powerful method to filter the TLEs through user defined functions. In filter_by_func, a user-defined function takes the TLE, runs some test and returns True or False accordingly. For example, while TLEs do not have a direct way to filter for semimajor axis, a filter can be easily written with this method. Also note the use of units, enabling a more robust and less error-prone manipulation of values.

The example illustrates three variations of a semimajor axis filtering function. The first one filters for a hardcoded maximum semimajor axis limit. The second shows a minimum semimajor axis limit given as an argument and the final example shows minimum and maximum limits provided as keyword arguments. This flexibility is thanks to the *args and **kwargs inputs to the filtering function.

[5]:
from satkit.propagation.tle import TLEUtils


# use case 6, filter with a function (semimajor axis range)
# ---------------------------------------------------------

# define the filter function and filter the list
def sma_filter_1(tle):
    """Semimajor axis filter max."""
    return True if 7000 * u.km > TLEUtils.compute_sma(tle) else False


def sma_filter_2(tle, a_min):
    """Semimajor axis filter min."""
    return True if TLEUtils.compute_sma(tle) > a_min else False


def sma_filter_3(tle, a_max, a_min):
    """Semimajor axis filter min/max."""
    return True if a_max > TLEUtils.compute_sma(tle) > a_min else False


a_7000 = 7000 * u.km
a_7100 = 7100 * u.km

filtered_list_sma_1 = tle_storage_1.filter_by_func(sma_filter_1)
filtered_list_sma_2 = tle_storage_1.filter_by_func(sma_filter_2, a_7000)
filtered_list_sma_3 = tle_storage_1.filter_by_func(
    sma_filter_3, a_max=a_7100, a_min=a_7000
)

# Print the first element of the filtered TLE list nr. 2
print(filtered_list_sma_2.tle_list[0])

1 28366U 04025A   21088.48552344  .00000020  00000-0  20339-4 0  9993
2 28366  98.2305 333.6788 0106609 231.7460 127.4097 14.36733092877928

Working with TLEs from a Single Satellite

There are many use cases where we need to work with the TLE data from a single satellite. Plotting the change in one of the parameters in time, or propagating the orbit for a long duration with multiple TLEs requires a time-ordered series of TLEs, guaranteed to be from a single satellite. The TleTimeSeries is similar to TleStorage in its functionalities, but it is initialised differently, to ensure that the TLEs belong to a single satellite.

The ideal way to initialise the TimeSeries is to generate a TleStorage from a file or another source and then generate the TleTimeSeries with a unique satellite identifier, using the to_tle_timeseries() method. The other way is to initialise the object through receiving a list of TLEs and a unique identifier. Both methods are shown below.

[6]:
from satkit.propagation.tle_list import TleTimeSeries

# Method 1: Using to_tle_timeseries
tle_timeseries_1 = TleStorage.from_path(file_path).to_tle_timeseries(28366)

# Print the first element of the filtered TLE list
print(tle_timeseries_1.tle_list[0])

# Method 2: Initialise through TLE list
tle_timeseries_2 = TleTimeSeries(tle_storage_1.tle_list, 28366)
1 28366U 04025A   21088.48552344  .00000020  00000-0  20339-4 0  9993
2 28366  98.2305 333.6788 0106609 231.7460 127.4097 14.36733092877928

Further filtering is then possible using the same methods as TleStorage given in the previous sections. For example, all the TLEs after a certain time or above a certain semimajor axis or eccentricity value can be extracted by chaining the filters.