279 lines
10 KiB
Python
279 lines
10 KiB
Python
"""Read the TLE earth satellite file format.
|
|
|
|
This is a minimally-edited copy of "sgp4io.cpp".
|
|
|
|
"""
|
|
import re
|
|
from datetime import datetime
|
|
from math import pi, pow
|
|
from sgp4.ext import days2mdhms, invjday, jday
|
|
from sgp4.propagation import sgp4init
|
|
|
|
INT_RE = re.compile(r'[+-]?\d*')
|
|
FLOAT_RE = re.compile(r'[+-]?\d*(\.\d*)?')
|
|
|
|
LINE1 = '1 NNNNNC NNNNNAAA NNNNN.NNNNNNNN +.NNNNNNNN +NNNNN-N +NNNNN-N N NNNNN'
|
|
LINE2 = '2 NNNNN NNN.NNNN NNN.NNNN NNNNNNN NNN.NNNN NNN.NNNN NN.NNNNNNNNNNNNNN'
|
|
|
|
error_message = """TLE format error
|
|
|
|
The Two-Line Element (TLE) format was designed for punch cards, and so
|
|
is very strict about the position of every period, space, and digit.
|
|
Your line does not quite match. Here is the official format for line {0}
|
|
with an N where each digit should go, followed by the line you provided:
|
|
|
|
{1}
|
|
{2}"""
|
|
|
|
"""
|
|
/* ----------------------------------------------------------------
|
|
*
|
|
* sgp4io.cpp
|
|
*
|
|
* this file contains a function to read two line element sets. while
|
|
* not formerly part of the sgp4 mathematical theory, it is
|
|
* required for practical implemenation.
|
|
*
|
|
* companion code for
|
|
* fundamentals of astrodynamics and applications
|
|
* 2007
|
|
* by david vallado
|
|
*
|
|
* (w) 719-573-2600, email dvallado@agi.com
|
|
*
|
|
* current :
|
|
* 27 Aug 10 david vallado
|
|
* fix input format and delete unused variables in twoline2rv
|
|
* changes :
|
|
* 3 sep 08 david vallado
|
|
* add operationmode for afspc (a) or improved (i)
|
|
* 9 may 07 david vallado
|
|
* fix year correction to 57
|
|
* 27 mar 07 david vallado
|
|
* misc fixes to manual inputs
|
|
* 14 aug 06 david vallado
|
|
* original baseline
|
|
* ---------------------------------------------------------------- */
|
|
"""
|
|
|
|
"""
|
|
/* -----------------------------------------------------------------------------
|
|
*
|
|
* function twoline2rv
|
|
*
|
|
* this function converts the two line element set character string data to
|
|
* variables and initializes the sgp4 variables. several intermediate varaibles
|
|
* and quantities are determined. note that the result is a structure so multiple
|
|
* satellites can be processed simultaneously without having to reinitialize. the
|
|
* verification mode is an important option that permits quick checks of any
|
|
* changes to the underlying technical theory. this option works using a
|
|
* modified tle file in which the start, stop, and delta time values are
|
|
* included at the end of the second line of data. this only works with the
|
|
* verification mode. the catalog mode simply propagates from -1440 to 1440 min
|
|
* from epoch and is useful when performing entire catalog runs.
|
|
*
|
|
* author : david vallado 719-573-2600 1 mar 2001
|
|
*
|
|
* inputs :
|
|
* longstr1 - first line of the tle
|
|
* longstr2 - second line of the tle
|
|
* typerun - type of run verification 'v', catalog 'c',
|
|
* manual 'm'
|
|
* typeinput - type of manual input mfe 'm', epoch 'e', dayofyr 'd'
|
|
* opsmode - mode of operation afspc or improved 'a', 'i'
|
|
* whichconst - which set of constants to use 72, 84
|
|
*
|
|
* outputs :
|
|
* satrec - structure containing all the sgp4 satellite information
|
|
*
|
|
* coupling :
|
|
* getgravconst-
|
|
* days2mdhms - conversion of days to month, day, hour, minute, second
|
|
* jday - convert day month year hour minute second into julian date
|
|
* sgp4init - initialize the sgp4 variables
|
|
*
|
|
* references :
|
|
* norad spacetrack report #3
|
|
* vallado, crawford, hujsak, kelso 2006
|
|
--------------------------------------------------------------------------- */
|
|
"""
|
|
|
|
def twoline2rv(longstr1, longstr2, whichconst, opsmode='i', satrec=None):
|
|
"""Return a Satellite imported from two lines of TLE data.
|
|
|
|
Provide the two TLE lines as strings `longstr1` and `longstr2`,
|
|
and select which standard set of gravitational constants you want
|
|
by providing `gravity_constants`:
|
|
|
|
`sgp4.earth_gravity.wgs72` - Standard WGS 72 model
|
|
`sgp4.earth_gravity.wgs84` - More recent WGS 84 model
|
|
`sgp4.earth_gravity.wgs72old` - Legacy support for old SGP4 behavior
|
|
|
|
Normally, computations are made using various recent improvements
|
|
to the algorithm. If you want to turn some of these off and go
|
|
back into "opsmode" mode, then set `opsmode` to `a`.
|
|
|
|
"""
|
|
|
|
deg2rad = pi / 180.0; # 0.0174532925199433
|
|
xpdotp = 1440.0 / (2.0 *pi); # 229.1831180523293
|
|
|
|
# For compatibility with our 1.x API, build an old Satellite object
|
|
# if the caller fails to supply a satrec. In that case we perform
|
|
# the necessary import here to avoid an import loop.
|
|
if satrec is None:
|
|
from sgp4.model import Satellite
|
|
satrec = Satellite()
|
|
|
|
satrec.error = 0;
|
|
|
|
line = longstr1.rstrip()
|
|
|
|
try:
|
|
longstr1.encode('ascii')
|
|
longstr2.encode('ascii')
|
|
except UnicodeEncodeError:
|
|
r1 = repr(longstr1)[1:-1]
|
|
r2 = repr(longstr2)[1:-1]
|
|
raise ValueError('your TLE lines are broken because they contain'
|
|
' non-ASCII characters:\n\n%s\n%s' % (r1, r2))
|
|
|
|
if (len(line) >= 64 and
|
|
line.startswith('1 ') and
|
|
line[8] == ' ' and
|
|
line[23] == '.' and
|
|
line[32] == ' ' and
|
|
line[34] == '.' and
|
|
line[43] == ' ' and
|
|
line[52] == ' ' and
|
|
line[61] == ' ' and
|
|
line[63] == ' '):
|
|
|
|
satrec.satnum_str = line[2:7]
|
|
satrec.classification = line[7] or 'U'
|
|
satrec.intldesg = line[9:17].rstrip()
|
|
two_digit_year = int(line[18:20])
|
|
satrec.epochdays = float(line[20:32])
|
|
satrec.ndot = float(line[33:43])
|
|
satrec.nddot = float(line[44] + '.' + line[45:50])
|
|
nexp = int(line[50:52])
|
|
satrec.bstar = float(line[53] + '.' + line[54:59])
|
|
ibexp = int(line[59:61])
|
|
satrec.ephtype = line[62]
|
|
satrec.elnum = int(line[64:68])
|
|
else:
|
|
raise ValueError(error_message.format(1, LINE1, line))
|
|
|
|
line = longstr2.rstrip()
|
|
|
|
if (len(line) >= 68 and
|
|
line.startswith('2 ') and
|
|
line[7] == ' ' and
|
|
line[11] == '.' and
|
|
line[16] == ' ' and
|
|
line[20] == '.' and
|
|
line[25] == ' ' and
|
|
line[33] == ' ' and
|
|
line[37] == '.' and
|
|
line[42] == ' ' and
|
|
line[46] == '.' and
|
|
line[51] == ' '):
|
|
|
|
if satrec.satnum_str != line[2:7]:
|
|
raise ValueError('Object numbers in lines 1 and 2 do not match')
|
|
|
|
satrec.inclo = float(line[8:16])
|
|
satrec.nodeo = float(line[17:25])
|
|
satrec.ecco = float('0.' + line[26:33].replace(' ', '0'))
|
|
satrec.argpo = float(line[34:42])
|
|
satrec.mo = float(line[43:51])
|
|
satrec.no_kozai = float(line[52:63])
|
|
satrec.revnum = line[63:68]
|
|
#except (AssertionError, IndexError, ValueError):
|
|
else:
|
|
raise ValueError(error_message.format(2, LINE2, line))
|
|
|
|
# ---- find no, ndot, nddot ----
|
|
satrec.no_kozai = satrec.no_kozai / xpdotp; # rad/min
|
|
satrec.nddot= satrec.nddot * pow(10.0, nexp);
|
|
satrec.bstar= satrec.bstar * pow(10.0, ibexp);
|
|
|
|
# ---- convert to sgp4 units ----
|
|
satrec.ndot = satrec.ndot / (xpdotp*1440.0); # ? * minperday
|
|
satrec.nddot= satrec.nddot / (xpdotp*1440.0*1440);
|
|
|
|
# ---- find standard orbital elements ----
|
|
satrec.inclo = satrec.inclo * deg2rad;
|
|
satrec.nodeo = satrec.nodeo * deg2rad;
|
|
satrec.argpo = satrec.argpo * deg2rad;
|
|
satrec.mo = satrec.mo * deg2rad;
|
|
|
|
|
|
"""
|
|
// ----------------------------------------------------------------
|
|
// find sgp4epoch time of element set
|
|
// remember that sgp4 uses units of days from 0 jan 1950 (sgp4epoch)
|
|
// and minutes from the epoch (time)
|
|
// ----------------------------------------------------------------
|
|
|
|
// ---------------- temp fix for years from 1957-2056 -------------------
|
|
// --------- correct fix will occur when year is 4-digit in tle ---------
|
|
"""
|
|
if two_digit_year < 57:
|
|
year = two_digit_year + 2000;
|
|
else:
|
|
year = two_digit_year + 1900;
|
|
|
|
mon,day,hr,minute,sec = days2mdhms(year, satrec.epochdays);
|
|
sec_whole, sec_fraction = divmod(sec, 1.0)
|
|
|
|
satrec.epochyr = year
|
|
satrec.jdsatepoch = jday(year,mon,day,hr,minute,sec);
|
|
try:
|
|
satrec.epoch = datetime(year, mon, day, hr, minute, int(sec_whole),
|
|
int(sec_fraction * 1000000.0 // 1.0))
|
|
except ValueError:
|
|
# Sometimes a TLE says something like "2019 + 366.82137887 days"
|
|
# which would be December 32nd which causes a ValueError.
|
|
year, mon, day, hr, minute, sec = invjday(satrec.jdsatepoch)
|
|
satrec.epoch = datetime(year, mon, day, hr, minute, int(sec_whole),
|
|
int(sec_fraction * 1000000.0 // 1.0))
|
|
|
|
# ---------------- initialize the orbit at sgp4epoch -------------------
|
|
sgp4init(whichconst, opsmode, satrec.satnum_str, satrec.jdsatepoch-2433281.5, satrec.bstar,
|
|
satrec.ndot, satrec.nddot, satrec.ecco, satrec.argpo, satrec.inclo, satrec.mo,
|
|
satrec.no_kozai, satrec.nodeo, satrec)
|
|
|
|
return satrec
|
|
|
|
def verify_checksum(*lines):
|
|
"""Verify the checksum of one or more TLE lines.
|
|
|
|
Raises `ValueError` if any of the lines fails its checksum, and
|
|
includes the failing line in the error message.
|
|
|
|
"""
|
|
for line in lines:
|
|
checksum = line[68:69]
|
|
if not checksum.isdigit():
|
|
continue
|
|
checksum = int(checksum)
|
|
computed = compute_checksum(line)
|
|
if checksum != computed:
|
|
complaint = ('TLE line gives its checksum as {}'
|
|
' but in fact tallies to {}:\n{}')
|
|
raise ValueError(complaint.format(checksum, computed, line))
|
|
|
|
def fix_checksum(line):
|
|
"""Return a new copy of the TLE `line`, with the correct checksum appended.
|
|
|
|
This discards any existing checksum at the end of the line, if a
|
|
checksum is already present.
|
|
|
|
"""
|
|
return line[:68].ljust(68) + str(compute_checksum(line))
|
|
|
|
def compute_checksum(line):
|
|
"""Compute the TLE checksum for the given line."""
|
|
return sum((int(c) if c.isdigit() else c == '-') for c in line[0:68]) % 10
|