The Modified ITC format is the Space-Track ephemeris exchange format defined in the Spaceflight Safety Handbook for Satellite Operators (version 1.7). A file holds a short header followed by one record per epoch: a line giving the YYYYDDDHHMMSS.sss UTC epoch, position (km) and velocity (km/s), optionally followed by the lower triangle of a 6x6 position and velocity covariance (21 values, km-based units) in the frame the header names. Brahe reads and writes the format through ITC, converting positions and velocities to SI units (m, m/s) and covariance elements by \(10^6\) on input, so every quantity on an ITC object is already in SI.
Source file: MEME_100001_STARLINK-38128_2540142_Operational_1473385380_UNCLASSIFIED.txt
Created: 2026-09-11 01:55:52.000 UTC
Ephemeris start: 2026-09-11 01:42:42.000 UTC
Ephemeris stop: 2026-09-14 01:42:42.000 UTC
Step size: 60 s
State frame: EME2000
Covariance frame: UVW
Records: 4321
Has covariance: true
First epoch: 2026-09-11 01:42:42.000 UTC
Position [m]: [4244346.537, 1264325.489, 5043982.644]
Velocity [m/s]: [3595.154763, 5258.795658, -4335.035291]
1-sigma RTN position [m]: [0.681, 0.882, 1.098]
1-sigma RTN velocity [m/s]: [0.001415, 0.000721, 0.002330]
ITC.from_file infers the state frame and the object's identity from a Space-Track compliant file name: the DataType field MEME, EME2000 or J2000 all map to EME2000, and TEME and ITRF map to those frames; the decoded name is kept on source_name. ITC.from_str parses only the file text and leaves the state frame at its default, EME2000, since there is no file name to read a DataType from. The header carries the creation time, the declared ephemeris start, stop and step, a free-text source label, and the covariance frame; the state frame comes from the file name rather than the body. states holds the parsed records and covariances holds one matrix per record when the file carries covariance; has_covariance is true only when every record does, since a Modified ITC message's covariance is all-or-none.
The state frame is the file's MEME J2000.0, the mean equator and mean equinox of J2000.0, which brahe treats as CelestialFrame.EME2000; it is related to GCRF by the constant frame bias. The covariance frame UVW is RTN (radial, in-track, cross-track), with
When a message's covariance frame is UVW (RTN), converting it to a trajectory rotates each record's covariance from RTN into the state frame with the block-diagonal Jacobian \(\mathrm{blockdiag}(R, R)\) by default, following the NASA Conjunction Assessment Risk Analysis handbook (Appendix N, eq. N-13) and the CARA RIC2ECI implementation. The rotating-frame form
\[ \begin{bmatrix} R & 0 \\ R[\boldsymbol{\omega}\times] & R \end{bmatrix} \]
(Vallado, AAS 03-526) is available by passing OrbitRelativeFrameVariant.ROTATING to to_trajectory_with_covariance_variant. A covariance frame of EME2000 or ITRF skips RTN entirely: it is rotated directly between that frame and the state frame with the state-transform Jacobian, so a message can carry covariance in either frame regardless of which one is the state frame. from_trajectory and from_trajectory_with_covariance_variant invert the same rotations to build a message from a trajectory; both reject a trajectory that is not six-dimensional Cartesian, so a Keplerian-element trajectory must be converted first.
importnumpyasnpimportbraheasbhbh.initialize_eop()PATH="test_assets/starlink/MEME_100001_STARLINK-38128_2540142_Operational_1473385380_UNCLASSIFIED.txt"itc=bh.ITC.from_file(PATH)trajectory=itc.to_trajectory()start=trajectory.start_epoch()end=trajectory.end_epoch()print(f"Trajectory: {len(trajectory)} samples from {start} to {end}")mid=start+30.0state=trajectory.interpolate(mid)print(f"State at {mid} [m, m/s]: {np.array2string(state,precision=3)}")covariance=trajectory.covariance(mid)print(f"Position 1-sigma at {mid} [m]: {np.array2string(np.sqrt(np.diag(covariance))[:3],precision=3)}")itrf=trajectory.to_itrf()ecef=itrf.interpolate(mid)print(f"ITRF position at {mid} [m]: {np.array2string(ecef[:3],precision=3)}")station=bh.PointLocation(-122.4194,37.7749,0.0).with_name("San Francisco")constraint=bh.ElevationConstraint(min_elevation_deg=10.0)windows=bh.location_accesses(station,trajectory,start,end,constraint)print(f"Access windows above 10 deg: {len(windows)}")forwindowinwindows[:3]:print(f" {window.window_open} to {window.window_close} ({window.duration:.1f} s)")
Trajectory: 4321 samples from 2026-09-11 01:42:42.000 UTC to 2026-09-14 01:42:42.000 UTC
State at 2026-09-11 01:43:12.000 UTC [m, m/s]: [ 4.350e+06 1.421e+06 4.911e+06 3.426e+03 5.206e+03 -4.532e+03]
Position 1-sigma at 2026-09-11 01:43:12.000 UTC [m]: [0.795 0.967 1.029]
ITRF position at 2026-09-11 01:43:12.000 UTC [m]: [4559101.925 205850.957 4922350.492]
Access windows above 10 deg: 7
2026-09-11 10:51:51.560 UTC to 2026-09-11 10:56:43.739 UTC (292.2 s)
2026-09-12 00:07:29.304 UTC to 2026-09-12 00:12:53.331 UTC (324.0 s)
2026-09-12 09:40:16.366 UTC to 2026-09-12 09:45:21.106 UTC (304.7 s)
Trajectory: 4321 samples from 2026-09-11 01:42:42.000 UTC to 2026-09-14 01:42:42.000 UTC
State at 2026-09-11 01:43:12.000 UTC [m, m/s]: [4349669.220, 1421310.402, 4910964.606, 3425.677, 5205.836, -4531.940]
Position 1-sigma at 2026-09-11 01:43:12.000 UTC [m]: [0.795, 0.967, 1.029]
ITRF position at 2026-09-11 01:43:12.000 UTC [m]: [4559101.925, 205850.957, 4922350.492]
Access windows above 10 deg: 7
2026-09-11 10:51:51.560 UTC to 2026-09-11 10:56:43.739 UTC (292.2 s)
2026-09-12 00:07:29.304 UTC to 2026-09-12 00:12:53.331 UTC (324.0 s)
2026-09-12 09:40:16.366 UTC to 2026-09-12 09:45:21.106 UTC (304.7 s)
The resulting OrbitTrajectory interpolates position, velocity and covariance at any epoch within its span, and to_frame -- including the frame-specific shortcuts such as to_eci and to_itrf -- carries a Cartesian covariance along with the states by rotating it with the state-transform Jacobian, so a conversion to any frame keeps it. Because it is an ordinary OrbitTrajectory, it can be used directly with location_accesses to compute ground-station access windows, as in the example above, without a separate conversion step.
Records: 50, covariance: True
Span: 2026-09-11 01:49:42.000 UTC to 2026-09-11 02:38:42.000 UTC
File name: MEME_100002_STARLINK-37711_2540149_Operational_nomnvr_UNCLASSIFIED.txt
Re-read 50 records; state frame EME2000
First line: created:2026-09-11 02:00:00 UTC
Records: 50, covariance: true
Span: 2026-09-11 01:49:42.000 UTC to 2026-09-11 02:38:42.000 UTC
File name: MEME_100002_STARLINK-37711_2540149_Operational_nomnvr_UNCLASSIFIED.txt
Re-read 50 records; state frame EME2000
First line: created:2026-09-11 02:00:00 UTC
from_trajectory requires a Cartesian, six-dimensional trajectory; it fills the header's ephemeris start, stop and step from the trajectory's samples and keeps the header's state frame, covariance frame, created and ephemeris_source fields. ITC.file_name then builds a Space-Track compliant name from the message: <DataType>_<NORAD>_<Name>_<DDDHHMM>_<Category>_<Metadata>_<Classification>.<ext>, where the DataType comes from the state frame, the day-time group from the ephemeris start, and the catalog number, name, category and metadata from the arguments passed to file_name, and the classification and extension from the defaults of SpaceTrackEphemerisFileName. That type is the one behind the convention: parse reads a compliant name, constructing a SpaceTrackEphemerisFileName gives the MEME data type, UNCLASSIFIED classification and txt extension, and the with_* methods replace individual fields. Every field is rejected if it contains a path separator (/ or \) or is . or .., so a name built from untrusted input can never address a file outside its directory; the data type, metadata, classification and extension fields are additionally rejected if they contain an underscore, since that is the field delimiter -- the object name is the exception, since parse recovers it by anchoring the other six fixed-position fields from either end and taking whatever remains between them.