TLE Fitting
Fit a TLE to a set of satellite states (position and velocity)
This uses Levenberg-Marquart non-linear least-squares fitting to tune TLE parameters to minimize the difference between the state positions and the SGP4-computed positions
Note that a TLE represents states in the TEME frame. Inputs are rotated into TEME frame from GCRF
[1]:
# Imports
import satkit as sk
import numpy as np
import math as m
# Create a high-precision state
# Altitude for circular orbit
altitude = 450e3
# Radius & velocity
r0 = altitude + sk.consts.earth_radius
v0 = m.sqrt(sk.consts.mu_earth / r0)
# Inclination
inclination = 15 * m.pi / 180.0
# Create the state (3D position in meters, 3D velocity in meters / second)
state0 = np.array([r0, 0, 0, 0, v0 * m.cos(inclination), v0 * m.sin(inclination)])
# Make up an epoch
time0 = sk.time(2024, 3, 15, 13, 0, 0)
# Propagate the state forward by a day with high-precision propagator
res = sk.propagate(state0, time0, time0 + sk.duration(days=1.0))
# Get interpolated states every 10 minutes
times = [time0 + sk.duration(minutes=i) for i in range(0, 1440, 10)]
states = [res.interp(t) for t in times]
# Fit the TLE
(tle, fitresults) = sk.TLE.fit_from_states(states, times, time0 + sk.duration(days=0.5)) # type: ignore
# Print the result
print(tle)
print(fitresults['success'])
TLE: none
NORAD ID: 00000,
Launch Year: 2000,
Epoch: 2024-03-16T01:00:00.000000Z,
Mean Motion Dot: 0 revs / day^2,
Mean Motion Dot Dot: 0 revs / day^3,
Drag: -0.0001532958087389173,
Inclination: 14.991373023845723 deg,
RAAN: 355.99763657441315 deg,
eccen: 0.0013613550403152985,
Arg of Perigee: 199.31611943912216 deg,
Mean Anomaly: 62.01096415449389 deg,
Mean Motion: 15.408691923872402 revs / day
Rev #: 0
Convergence in parameter value
[2]:
# Compute position errors (differences between TLE & state)
# Get the positions from sgp4
(pteme, vteme) = sk.sgp4(tle, times)
# Rotate positions from TEME to GCRF frame
pgcrf = [sk.frametransform.qteme2gcrf(t) * p for t, p in zip(times, pteme)]
# Take difference between state vector and SGP4 positions, and compute norm
pdiff = [p - s[0:3] for p, s in zip(pgcrf, states)]
pdiff = np.array([np.linalg.norm(p) for p in pdiff])
# Plot position errors
import plotly.graph_objects as go
fig = go.Figure()
fig.add_trace(go.Scatter(x=[t.datetime() for t in times], y=pdiff, mode='lines', name='Position Error',
line=dict(color='black', width=2)))
fig.update_layout(title='TLE Fitting Position Errors',
xaxis_title='Time',
yaxis_title='Position Error (m)')
fig.update_xaxes(showline=True, linewidth=2, linecolor="black", mirror=True)
fig.update_yaxes(showline=True, linewidth=2, linecolor="black", mirror=True)
fig.update_layout(
xaxis=dict(
gridcolor="#dddddd",
gridwidth=1,
),
yaxis=dict(
gridcolor="#dddddd",
gridwidth=1,
),
)
fig.show()