mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-15 15:52:21 +00:00
Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_0187rSCa4AKJPGw6uiuPSGyX
83 lines
3.2 KiB
Python
83 lines
3.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Compare the reconstruction with the public element-set history (tle/*.tle)."""
|
|
import json, math, sys
|
|
from datetime import datetime, timedelta, timezone
|
|
import numpy as np
|
|
from sgp4.api import Satrec, jday
|
|
|
|
HERE = __file__.rsplit('/', 1)[0]
|
|
MU = 398600.4418
|
|
|
|
|
|
def load(path):
|
|
L = [l.rstrip('\n') for l in open(path) if l.strip()]
|
|
out = []
|
|
for i in range(len(L)):
|
|
if L[i].startswith('1 ') and i + 1 < len(L) and L[i + 1].startswith('2 '):
|
|
s = Satrec.twoline2rv(L[i], L[i + 1])
|
|
ep = datetime(2000, 1, 1, tzinfo=timezone.utc) + timedelta(days=s.jdsatepoch + s.jdsatepochF - 2451545.0 + 0.5)
|
|
out.append((ep, s, L[i], L[i + 1]))
|
|
out.sort(key=lambda x: x[0])
|
|
return out
|
|
|
|
|
|
def st(s, d):
|
|
jd, fr = jday(d.year, d.month, d.day, d.hour, d.minute, d.second + d.microsecond / 1e6)
|
|
e, r, v = s.sgp4(jd, fr)
|
|
return np.array(r), np.array(v)
|
|
|
|
|
|
def rel_rtn(rx, vx, rc):
|
|
R = rx / np.linalg.norm(rx)
|
|
N = np.cross(rx, vx); N /= np.linalg.norm(N)
|
|
T = np.cross(N, R)
|
|
th = math.atan2(np.dot(np.cross(rx, rc), N), np.dot(rx, rc))
|
|
return np.array([np.linalg.norm(rc) - np.linalg.norm(rx), th * np.linalg.norm(rx), np.dot(rc, N)]) * 1000
|
|
|
|
|
|
def sma(s):
|
|
n = s.no_kozai / 60
|
|
return (MU / n ** 2) ** (1 / 3)
|
|
|
|
|
|
X = load(f'{HERE}/tle/59103.tle')
|
|
C = load(f'{HERE}/tle/68762.tle')
|
|
D = json.load(open(f'{HERE}/data.json'))
|
|
T0 = datetime.fromisoformat(D['t0'].replace('Z', '+00:00'))
|
|
rel = np.array(D['rel']).reshape(-1, 3)
|
|
|
|
|
|
def model_rel(d):
|
|
i = (d - T0).total_seconds() / D['dt']
|
|
if i < 0 or i >= len(rel) - 1:
|
|
return None
|
|
k = int(i)
|
|
return rel[k] + (rel[k + 1] - rel[k]) * (i - k)
|
|
|
|
|
|
def nearest_x(d):
|
|
return min(X, key=lambda x: abs((x[0] - d).total_seconds()))
|
|
|
|
|
|
rows = []
|
|
for ep, s, l1, l2 in C:
|
|
if ep < datetime(2026, 9, 25, tzinfo=timezone.utc) or ep > datetime(2026, 10, 26, tzinfo=timezone.utc):
|
|
continue
|
|
xe, xs, _, _ = nearest_x(ep)
|
|
rx, vx = st(xs, ep)
|
|
rc, vc = st(s, ep)
|
|
r = rel_rtn(rx, vx, rc)
|
|
m = model_rel(ep)
|
|
dA = sma(s) - sma(xs)
|
|
di = math.degrees(s.inclo - xs.inclo)
|
|
dO = math.degrees(s.nodeo - xs.nodeo)
|
|
ec = (s.ecco * math.cos(s.argpo), s.ecco * math.sin(s.argpo))
|
|
ex = (xs.ecco * math.cos(xs.argpo), xs.ecco * math.sin(xs.argpo))
|
|
rows.append(dict(ep=ep.isoformat()[:16], xage_h=round((ep - xe).total_seconds() / 3600, 1), R=r[0], T=r[1], N=r[2], dA_km=dA, di=di, dO=dO,
|
|
dex=(ec[0] - ex[0]) * sma(xs) * 1000, dey=(ec[1] - ex[1]) * sma(xs) * 1000,
|
|
mT=None if m is None else m[1], mR=None if m is None else m[0]))
|
|
print(f"{'epoch':17} {'xAge':>5} {'TLE T km':>10} {'model T':>10} {'TLE R km':>9} {'mod R':>7} {'N km':>7} {'dA km':>8} {'di deg':>7} {'dRAAN':>7} {'|de| m':>7}")
|
|
for r in rows:
|
|
print(f"{r['ep']:17} {r['xage_h']:5} {r['T']/1000:10.2f} {('' if r['mT'] is None else f'{r[chr(109)+chr(84)]/1000:10.2f}'):>10} {r['R']/1000:9.2f} {('' if r['mR'] is None else f'{r[chr(109)+chr(82)]/1000:7.2f}'):>7} {r['N']/1000:7.2f} {r['dA_km']:8.3f} {r['di']:7.3f} {r['dO']:7.3f} {math.hypot(r['dex'], r['dey']):7.0f}")
|
|
json.dump(rows, open(f'{HERE}/tle_rows.json', 'w'), default=str, indent=0)
|