Last active
August 29, 2015 14:15
-
-
Save mraspaud/8c32aa0f067edc49a5fc to your computer and use it in GitHub Desktop.
tbus to tle converter
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# -*- coding: utf-8 -*- | |
# Copyright (c) 2014, 2015 Martin Raspaud | |
# Author(s): | |
# Martin Raspaud <[email protected]> | |
# This program is free software: you can redistribute it and/or modify | |
# it under the terms of the GNU General Public License as published by | |
# the Free Software Foundation, either version 3 of the License, or | |
# (at your option) any later version. | |
# This program is distributed in the hope that it will be useful, | |
# but WITHOUT ANY WARRANTY; without even the implied warranty of | |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
# GNU General Public License for more details. | |
# You should have received a copy of the GNU General Public License | |
# along with this program. If not, see <http://www.gnu.org/licenses/>. | |
"""This script downloads the latest tbus bulletin from noaasis, and | |
generates corresponding tle's. | |
The generated tle's do not have any derivative or second derivative of | |
mean motion though (they are set to 0). | |
""" | |
from datetime import datetime | |
def checksum(line): | |
"""Computes the checksum for the current line. | |
""" | |
check = 0 | |
for char in line: | |
if char.isdigit(): | |
check += int(char) | |
if char == "-": | |
check += 1 | |
return check % 10 | |
def generate_tle(part4): | |
p4l = part4.split() | |
#print "spacecraft_id", p4l[:2] | |
tle_sp_id = p4l[0][2:] + p4l[1] | |
#print "orbit number at epoch", p4l[2] | |
tle_orbit = int(p4l[2]) - 1 # tle orbit starts at 0 | |
#print "time of first ascending node", p4l[3] | |
#print "epoch year", p4l[4][:2] | |
#print "epoch month", p4l[4][2:4] | |
#print "epoch day", p4l[4][4:6] | |
#print "epoch hour", p4l[4][6:8] | |
#print "epoch minute", p4l[4][8:10] | |
#print "epoch seconds", float(p4l[4][10:15]) / 1000.0 | |
epoch = datetime(2000 + int(p4l[4][:2]), | |
int(p4l[4][2:4]), | |
int(p4l[4][4:6]), | |
int(p4l[4][6:8]), | |
int(p4l[4][8:10]), | |
int(p4l[4][10:12]), | |
int(p4l[4][12:15]) * 1000) | |
#print epoch | |
#print epoch.strftime("%y%j") | |
tle_epoch = "{0:0.8f}".format(int(epoch.strftime("%y%j")) + (epoch.hour * 60 * 60 + epoch.minute * 60 + epoch.second + epoch.microsecond / 1e6) / (24 * 60 * 60)) | |
#print tle_epoch | |
#print 24 * 60 * 60.0 / (((int(p4l[4][8:10]) * 60) + int(p4l[4][10:12])) * 60 + int(p4l[4][10:12])) | |
#print "greenwitch hour angle", float(p4l[5]) / 10000.0 | |
gha = float(p4l[5]) / 10000.0 | |
#print "anomalistic period", float(p4l[6]) / 10000.0 | |
#print "mean motion", 24 * 60 / (float(p4l[6]) / 10000.0) | |
#print "nodal period", float(p4l[7]) / 10000.0 | |
#print "brouwer mean eccentricity", float(p4l[8]) / 100000000.0 | |
tle_ecc = float(p4l[8]) / 100000000.0 | |
#print "argument of perigee", float(p4l[9]) / 100000.0 | |
tle_arg_per = float(p4l[9]) / 100000.0 | |
#print "right ascension", float(p4l[10]) / 100000.0 | |
#print "inclination", float(p4l[11]) / 100000.0 | |
tle_inclination = float(p4l[11]) / 100000.0 | |
#print "mean anomaly", float(p4l[12]) / 100000.0 | |
tle_mean_ano = float(p4l[12]) / 100000.0 | |
#print "semi-major axis", float(p4l[13]) / 1000.0 | |
#print "drag modulation coefficient", float(p4l[22]) / 10000.0 | |
#print "ballistic coeff", float(p4l[20]) / 1e8 | |
tle_bstar = (float(p4l[20]) / 1e8) * 2.461e-5 * 6378.135/ 2 | |
#print "bstar", tle_bstar | |
l = "{0:1.4e}".format(tle_bstar).replace(".", "").split("e") | |
tle_bstar_str = l[0] + str(int(l[1]) + 1) | |
tle_bstar = p4l[22] + "0-4" | |
#print "perigee motion", p4l[24] | |
#print "Sign and motion of Right Ascension of the ascending node (degrees/day), to five decimal places.", p4l[25] | |
#print "Sign and rate of change of mean anomaly at epoch (degrees/day), to two decimal places.", p4l[26] | |
tle_mean_mo = int(p4l[26][1:]) * 1e-2 / 360 | |
if p4l[26][0] == "M": | |
tle_mean_mo = -tle_mean_mo | |
tle_lon_asc_node = (float(p4l[27]) / 1e5 + gha) % 360 | |
spids = {"09005A": ("NOAA 19", "33591U"), | |
"98030A": ("NOAA 15", "25338U"), | |
"05018A": ("NOAA 18", "28654U") } | |
tle_dd_mean_motion = ".00000000" | |
tle_d_mean_motion = "00000-0" | |
#print spids[tle_sp_id][0] | |
line1 = "1 {0:6s} {1:6s} {2:14s} {3:s} {4:s} {5:7s} 0 999".format( | |
spids[tle_sp_id][1], | |
tle_sp_id, | |
tle_epoch, | |
tle_dd_mean_motion, | |
tle_d_mean_motion, | |
tle_bstar_str) | |
line1 = line1 + str(checksum(line1)) | |
#print line1 | |
tle_ecc_str = "{0:.7f}".format(tle_ecc) | |
line2 = "2 {0:5s} {1:>8.4f} {2:>8.4f} {3:7s} {4:>8.4f} {5:>8.4f} {6:>11.8f}{7:05d}".format( | |
spids[tle_sp_id][1][:5], | |
tle_inclination, | |
tle_lon_asc_node, | |
tle_ecc_str[2:], | |
tle_arg_per, | |
tle_mean_ano, | |
tle_mean_mo, | |
tle_orbit) | |
line2 = line2 + str(checksum(line2)) | |
#print line2 | |
return line1, line2 | |
if __name__ == '__main__': | |
import urllib2 | |
daily_tbus_url = "http://noaasis.noaa.gov/cemscs/poltbus.txt" | |
fp = urllib2.urlopen(daily_tbus_url) | |
tbus = fp.read() | |
fp.close() | |
items = tbus.split("NNNN\n\n") | |
tbuses = [] | |
for item in items: | |
tbus = [] | |
for line in item.split("\n"): | |
clean_line = line.strip() | |
if clean_line: | |
tbus.append(clean_line) | |
tbuses.append(tbus) | |
for tbus in tbuses: | |
if not tbus: | |
continue | |
satellite = " ".join(tbus[2].split()[1:]) | |
for lineno, line in enumerate(tbus): | |
if line == "PART IV": | |
lineno += 1 | |
break | |
part4 = "\n".join(tbus[lineno:]) | |
lines = generate_tle(part4) | |
print satellite | |
print lines[0] | |
print lines[1] | |
############### Tests | |
def test(): | |
part4 = """2009 005A 30983 043018207204 150212002613102 1483008 | |
01019933 01020490 00138796 16134808 35269969 09897525 | |
19882771 07228311 P071848002 M009204348 P000000001 | |
M00158196 M01145249 P07324206 003883805 155154007 9449 | |
0000500000 M00277416 P00100028 P00508269 20439887 | |
020809 M00148 052112 M00885 072709 P00000 000000 | |
APT 137.9125 MHZ, HRPT 1698.0 MHZ, BCN DSB 137.77 MHZ. | |
APT DAY/NIGHT 2,4. VIS CH 2 /0.725 TO 1.0/ AND IR CH 4 | |
/10.5 TO 11.5/ XMTD DURING S/C DAY. IR CH3 /3ND IR CH 4 | |
/10.5 TO 11.5/ XMTD DURING S/C NIGHT. DCS CLK TIME | |
YR/DA/TIM 1995 021 79186.656 LAST TIP CLK CORR 02/08/09. | |
CLK ERR AFTER CORR MINUS 0148 MSEC. CLK ERR AS OF 05/21/12 | |
MINUS 885 MSEC. ERR RATE AS OF 07/27/09 PLUS 0.0 MS/DAY. | |
NEXT CLK CORR UNKNOWN. N19 APT SWITCH TO VTX1 137.1 MHZ | |
ON 23 JUN 2009 AT 1825Z. | |
NNNN""" | |
ref_line1 = "1 33591U 09005A 15043.53970792 .00000119 00000-0 89587-4 0 9992" | |
ref_line2 = "2 33591 98.9763 353.2216 0014536 157.9967 331.3486 14.11873647309901" | |
import numpy as np | |
from pyorbital.orbital import Orbital | |
orb_ref = Orbital("NOAA 19", line1=ref_line1, line2=ref_line2) | |
line1, line2 = generate_tle(part4) | |
orb_test = Orbital("NOAA 19", line1=line1, line2=line2) | |
from datetime import timedelta | |
delta = timedelta(minutes=120) | |
epoch = datetime(2015, 2, 12, 0, 26, 13, 102000) | |
time = epoch | |
cnt = 0 | |
errors = [] | |
while cnt < 30: | |
#print time | |
ref_pos = orb_ref.get_position(time, False) | |
test_pos = orb_test.get_position(time, False) | |
#print ref_pos | |
#print test_pos | |
error = np.sqrt((ref_pos[0][0] - test_pos[0][0])**2 + | |
(ref_pos[0][1] - test_pos[0][1])**2 + | |
(ref_pos[0][2] - test_pos[0][2])**2) | |
errors.append(error) | |
#print error | |
time = time + delta | |
cnt += 1 | |
assert(np.max(errors) < 5) #km | |
#print np.max(errors), np.mean(errors) | |
test() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment