Skip to content

Commit

Permalink
First commit
Browse files Browse the repository at this point in the history
  • Loading branch information
dcajacob committed Dec 19, 2022
0 parents commit 88d92dc
Show file tree
Hide file tree
Showing 33 changed files with 166,785 additions and 0 deletions.
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
TLE Tailor

TLE Tailor is a set of pure-python tools to generate Two Line Element Sets (TLEs) for Earth-orbiting satellites.

Generating TLEs is more difficult than it seems. TLEs are mean to be used with the SGP4 orbit propagation model. SGP4 uses mean orbital elements, which are not the same as classical / keplerian / osculating orbital elements. This causes a lot of confusion for folks. There is no direct conversion from a state vector to a TLE. Rather, we must perform trajectory sampling and find a set of TLE elements that best fit the trajectory. To do this, we use non-linear leastsquares in a process called differential correction. Sounds scary right? Well, that's what this projectis meant to solve.

Currently supported methods:

* Generate a TLE from ephemeris (e.g. GPS data from a satellite)
* Generate a TLE from a pre-launch or post-launch state vector
* Shift the epoch time of a TLE
* Match a TLE to a SpaceTrack TLE

Why do I need this?

* Confidently identify and contact your satellite after launch

How do I use this?
* Before launch or shortly after, if you're given a predicted or measured state vector by your launch provider, you can make a TLE to simplify your LEOP tracking
* If you make contact with your satellite and download some GPS data, you can use it to generate a TLE. This will allow you to confidently identify your satellite for 18 SDS
* You can continue to make supplemental TLEs using your GPS data

Caveats:
* This is a brand new project and although it works, there may be significant changes as time goes on

Future Work:
* Additional ephemeris type supported, e.g. Range, Rate, Az, El, etc.
* This could potentially use measurements from operators' ground stations / strf https://github.com/cbassa/strf to generate a TLE

Notes:
* Differential correction usually uses finite differencing to calculate the Jacobian of complex or black-box models. We might call SGP4 a gre-box, since we have the code (although the AFSPC version may be different). Finite differencing is a numerical method to dodge this complication and works great. But I have also implemented a neat way to automatically derive the analytical Jacobian using JAX's autograd capability. This can significantly speed up the calculations, although it incurs an upfront JIT compiling penalty, so it's best for cases where you're processing a lot of data or TLEs.

References:
* [Mention Vallado and his previous work]
999 changes: 999 additions & 0 deletions TLEFit - COE - FD.ipynb

Large diffs are not rendered by default.

630 changes: 630 additions & 0 deletions TLEFit - COE - JAX.ipynb

Large diffs are not rendered by default.

708 changes: 708 additions & 0 deletions TLEFit - EQN - FD.ipynb

Large diffs are not rendered by default.

2,979 changes: 2,979 additions & 0 deletions TLEFit - EQN - GPS FD - GPS Sat.ipynb

Large diffs are not rendered by default.

1,484 changes: 1,484 additions & 0 deletions TLEFit - EQN - GPS FD - Icesat.ipynb

Large diffs are not rendered by default.

3,025 changes: 3,025 additions & 0 deletions TLEFit - EQN - GPS JAX - GPS Sat.ipynb

Large diffs are not rendered by default.

1,567 changes: 1,567 additions & 0 deletions TLEFit - EQN - GPS JAX - Icesat.ipynb

Large diffs are not rendered by default.

4,959 changes: 4,959 additions & 0 deletions TLEFit - EQN - JAX.ipynb

Large diffs are not rendered by default.

151 changes: 151 additions & 0 deletions coarse_fit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import numpy as np

from skyfield.api import EarthSatellite, load
from sgp4.api import Satrec, WGS72
from sgp4.model import wgs72, wgs84
from sgp4.ext import rv2coe

ts = load.timescale()


def coarse_fit(satellite, rms_epsilon=0.002, debug=False):
"""A hack to get a first-cut estimate of TLE mean elements from keplerian elements
Based on some code I found on the internet. FIXME: Find it again and cite properly.
"""

# Form initial state estimate
_, r, v = satellite.model.sgp4_tsince(0)

for ix in range(15):
coe_nom = rv2coe(r, v, wgs72.mu)
p, a, ecc, incl, omega, argp, nu, m, arglat, truelon, lonper = coe_nom
n = np.sqrt(wgs72.mu / a**3) * 60 # radians / min
bstar = 0

elements = [n, a, ecc, incl, omega, argp, m, bstar]

a_nom = np.cbrt(wgs72.mu / (satellite.model.no_kozai / 60) ** 2)
orig_elements = [
satellite.model.no_kozai,
a_nom,
satellite.model.ecco,
satellite.model.inclo,
satellite.model.nodeo,
satellite.model.argpo,
satellite.model.mo,
satellite.model.bstar,
]

if debug and ix == 0:
print(
"Guess ",
elements[0],
elements[1],
elements[2],
np.degrees(elements[3]),
np.degrees(elements[4]),
np.degrees(elements[5]),
np.degrees(elements[6]),
elements[7],
)

# Print the initial difference
satrec = Satrec()
satrec.sgp4init(
WGS72,
"i",
satellite.model.satnum,
round(
satellite.model.jdsatepoch + satellite.model.jdsatepochF - 2433281.5, 8
),
bstar,
0.0,
0.0,
ecc,
argp,
incl,
m,
n,
omega,
)

pert_sat = EarthSatellite.from_satrec(satrec, ts)
pert_sat.model.jdsatepochF = satellite.model.jdsatepochF

res = np.array(pert_sat.model.sgp4_tsince(0)[1:]) - np.array(
satellite.model.sgp4_tsince(0)[1:]
)
b = np.concatenate((res[0], res[1]))
b_epoch = b

if debug:
print(np.linalg.norm(b[0:3]), np.linalg.norm(b[3:6]))

sigma_new = np.sqrt(b.T @ b)

# convergence_test = np.abs((sigma_old - sigma_new) / sigma_old)

# if convergence_test <= rms_epsilon and sigmasigma_new <= rms_epsilon:
if sigma_new <= rms_epsilon:
break

r -= b[0:3]
v -= b[3:6]
sigma_old = sigma_new

coe_nom = rv2coe(r, v, wgs72.mu)
p, a, ecc, incl, omega, argp, nu, m, arglat, truelon, lonper = coe_nom
n = np.sqrt(wgs72.mu / a**3) * 60 # radians / min
elements = [n, a, ecc, incl, omega, argp, m, bstar]

if debug:
print(
"Solution ",
elements[0],
elements[1],
elements[2],
np.degrees(elements[3]),
np.degrees(elements[4]),
np.degrees(elements[5]),
np.degrees(elements[6]),
elements[7],
)
print(
"Original ",
orig_elements[0],
orig_elements[1],
orig_elements[2],
np.degrees(orig_elements[3]),
np.degrees(orig_elements[4]),
np.degrees(orig_elements[5]),
np.degrees(orig_elements[6]),
orig_elements[7],
)

elements = elements[1:]

a, ecc, incl, omega, argp, m, bstar = elements

satrec = Satrec()
satrec.sgp4init(
WGS72,
"i",
satellite.model.satnum,
round(satellite.model.jdsatepoch + satellite.model.jdsatepochF - 2433281.5, 8),
bstar,
0.0,
0.0,
ecc,
argp,
incl,
m,
n,
omega,
)

close_sat = EarthSatellite.from_satrec(satrec, ts)
close_sat.model.jdsatepochF = satellite.model.jdsatepochF

# return elements
return close_sat
Loading

0 comments on commit 88d92dc

Please sign in to comment.