1"""Read the TLE earth satellite file format. 2 3This is a minimally-edited copy of "sgp4io.cpp". 4 5""" 6import re 7from datetime import datetime 8from math import pi, pow 9from sgp4.ext import days2mdhms, invjday, jday 10from sgp4.propagation import sgp4init 11 12INT_RE = re.compile(r'[+-]?\d*') 13FLOAT_RE = re.compile(r'[+-]?\d*(\.\d*)?') 14 15LINE1 = '1 NNNNNC NNNNNAAA NNNNN.NNNNNNNN +.NNNNNNNN +NNNNN-N +NNNNN-N N NNNNN' 16LINE2 = '2 NNNNN NNN.NNNN NNN.NNNN NNNNNNN NNN.NNNN NNN.NNNN NN.NNNNNNNNNNNNNN' 17 18error_message = """TLE format error 19 20The Two-Line Element (TLE) format was designed for punch cards, and so 21is very strict about the position of every period, space, and digit. 22Your line does not quite match. Here is the official format for line {0} 23with an N where each digit should go, followed by the line you provided: 24 25{1} 26{2}""" 27 28""" 29/* ---------------------------------------------------------------- 30* 31* sgp4io.cpp 32* 33* this file contains a function to read two line element sets. while 34* not formerly part of the sgp4 mathematical theory, it is 35* required for practical implemenation. 36* 37* companion code for 38* fundamentals of astrodynamics and applications 39* 2007 40* by david vallado 41* 42* (w) 719-573-2600, email dvallado@agi.com 43* 44* current : 45* 27 Aug 10 david vallado 46* fix input format and delete unused variables in twoline2rv 47* changes : 48* 3 sep 08 david vallado 49* add operationmode for afspc (a) or improved (i) 50* 9 may 07 david vallado 51* fix year correction to 57 52* 27 mar 07 david vallado 53* misc fixes to manual inputs 54* 14 aug 06 david vallado 55* original baseline 56* ---------------------------------------------------------------- */ 57""" 58 59""" 60/* ----------------------------------------------------------------------------- 61* 62* function twoline2rv 63* 64* this function converts the two line element set character string data to 65* variables and initializes the sgp4 variables. several intermediate varaibles 66* and quantities are determined. note that the result is a structure so multiple 67* satellites can be processed simultaneously without having to reinitialize. the 68* verification mode is an important option that permits quick checks of any 69* changes to the underlying technical theory. this option works using a 70* modified tle file in which the start, stop, and delta time values are 71* included at the end of the second line of data. this only works with the 72* verification mode. the catalog mode simply propagates from -1440 to 1440 min 73* from epoch and is useful when performing entire catalog runs. 74* 75* author : david vallado 719-573-2600 1 mar 2001 76* 77* inputs : 78* longstr1 - first line of the tle 79* longstr2 - second line of the tle 80* typerun - type of run verification 'v', catalog 'c', 81* manual 'm' 82* typeinput - type of manual input mfe 'm', epoch 'e', dayofyr 'd' 83* opsmode - mode of operation afspc or improved 'a', 'i' 84* whichconst - which set of constants to use 72, 84 85* 86* outputs : 87* satrec - structure containing all the sgp4 satellite information 88* 89* coupling : 90* getgravconst- 91* days2mdhms - conversion of days to month, day, hour, minute, second 92* jday - convert day month year hour minute second into julian date 93* sgp4init - initialize the sgp4 variables 94* 95* references : 96* norad spacetrack report #3 97* vallado, crawford, hujsak, kelso 2006 98 --------------------------------------------------------------------------- */ 99""" 100 101def twoline2rv(longstr1, longstr2, whichconst, opsmode='i', satrec=None): 102 """Return a Satellite imported from two lines of TLE data. 103 104 Provide the two TLE lines as strings `longstr1` and `longstr2`, 105 and select which standard set of gravitational constants you want 106 by providing `gravity_constants`: 107 108 `sgp4.earth_gravity.wgs72` - Standard WGS 72 model 109 `sgp4.earth_gravity.wgs84` - More recent WGS 84 model 110 `sgp4.earth_gravity.wgs72old` - Legacy support for old SGP4 behavior 111 112 Normally, computations are made using various recent improvements 113 to the algorithm. If you want to turn some of these off and go 114 back into "opsmode" mode, then set `opsmode` to `a`. 115 116 """ 117 118 deg2rad = pi / 180.0; # 0.0174532925199433 119 xpdotp = 1440.0 / (2.0 *pi); # 229.1831180523293 120 121 # For compatibility with our 1.x API, build an old Satellite object 122 # if the caller fails to supply a satrec. In that case we perform 123 # the necessary import here to avoid an import loop. 124 if satrec is None: 125 from sgp4.model import Satellite 126 satrec = Satellite() 127 128 satrec.error = 0; 129 satrec.whichconst = whichconst # Python extension: remembers its consts 130 131 line = longstr1.rstrip() 132 133 if (len(line) >= 64 and 134 line.startswith('1 ') and 135 line[8] == ' ' and 136 line[23] == '.' and 137 line[32] == ' ' and 138 line[34] == '.' and 139 line[43] == ' ' and 140 line[52] == ' ' and 141 line[61] == ' ' and 142 line[63] == ' '): 143 144 _saved_satnum = satrec.satnum = _alpha5(line[2:7]) 145 satrec.classification = line[7] or 'U' 146 satrec.intldesg = line[9:17].rstrip() 147 two_digit_year = int(line[18:20]) 148 satrec.epochdays = float(line[20:32]) 149 satrec.ndot = float(line[33:43]) 150 satrec.nddot = float(line[44] + '.' + line[45:50]) 151 nexp = int(line[50:52]) 152 satrec.bstar = float(line[53] + '.' + line[54:59]) 153 ibexp = int(line[59:61]) 154 satrec.ephtype = line[62] 155 satrec.elnum = int(line[64:68]) 156 else: 157 raise ValueError(error_message.format(1, LINE1, line)) 158 159 line = longstr2.rstrip() 160 161 if (len(line) >= 69 and 162 line.startswith('2 ') and 163 line[7] == ' ' and 164 line[11] == '.' and 165 line[16] == ' ' and 166 line[20] == '.' and 167 line[25] == ' ' and 168 line[33] == ' ' and 169 line[37] == '.' and 170 line[42] == ' ' and 171 line[46] == '.' and 172 line[51] == ' '): 173 174 satrec.satnum = _alpha5(line[2:7]) 175 if _saved_satnum != satrec.satnum: 176 raise ValueError('Object numbers in lines 1 and 2 do not match') 177 178 satrec.inclo = float(line[8:16]) 179 satrec.nodeo = float(line[17:25]) 180 satrec.ecco = float('0.' + line[26:33].replace(' ', '0')) 181 satrec.argpo = float(line[34:42]) 182 satrec.mo = float(line[43:51]) 183 satrec.no_kozai = float(line[52:63]) 184 satrec.revnum = line[63:68] 185 #except (AssertionError, IndexError, ValueError): 186 else: 187 raise ValueError(error_message.format(2, LINE2, line)) 188 189 # ---- find no, ndot, nddot ---- 190 satrec.no_kozai = satrec.no_kozai / xpdotp; # rad/min 191 satrec.nddot= satrec.nddot * pow(10.0, nexp); 192 satrec.bstar= satrec.bstar * pow(10.0, ibexp); 193 194 # ---- convert to sgp4 units ---- 195 satrec.ndot = satrec.ndot / (xpdotp*1440.0); # ? * minperday 196 satrec.nddot= satrec.nddot / (xpdotp*1440.0*1440); 197 198 # ---- find standard orbital elements ---- 199 satrec.inclo = satrec.inclo * deg2rad; 200 satrec.nodeo = satrec.nodeo * deg2rad; 201 satrec.argpo = satrec.argpo * deg2rad; 202 satrec.mo = satrec.mo * deg2rad; 203 204 205 """ 206 // ---------------------------------------------------------------- 207 // find sgp4epoch time of element set 208 // remember that sgp4 uses units of days from 0 jan 1950 (sgp4epoch) 209 // and minutes from the epoch (time) 210 // ---------------------------------------------------------------- 211 212 // ---------------- temp fix for years from 1957-2056 ------------------- 213 // --------- correct fix will occur when year is 4-digit in tle --------- 214 """ 215 if two_digit_year < 57: 216 year = two_digit_year + 2000; 217 else: 218 year = two_digit_year + 1900; 219 220 mon,day,hr,minute,sec = days2mdhms(year, satrec.epochdays); 221 sec_whole, sec_fraction = divmod(sec, 1.0) 222 223 satrec.epochyr = year 224 satrec.jdsatepoch = jday(year,mon,day,hr,minute,sec); 225 try: 226 satrec.epoch = datetime(year, mon, day, hr, minute, int(sec_whole), 227 int(sec_fraction * 1000000.0 // 1.0)) 228 except ValueError: 229 # Sometimes a TLE says something like "2019 + 366.82137887 days" 230 # which would be December 32nd which causes a ValueError. 231 year, mon, day, hr, minute, sec = invjday(satrec.jdsatepoch) 232 satrec.epoch = datetime(year, mon, day, hr, minute, int(sec_whole), 233 int(sec_fraction * 1000000.0 // 1.0)) 234 235 # ---------------- initialize the orbit at sgp4epoch ------------------- 236 sgp4init(whichconst, opsmode, satrec.satnum, satrec.jdsatepoch-2433281.5, satrec.bstar, 237 satrec.ndot, satrec.nddot, satrec.ecco, satrec.argpo, satrec.inclo, satrec.mo, 238 satrec.no_kozai, satrec.nodeo, satrec) 239 240 return satrec 241 242def verify_checksum(*lines): 243 """Verify the checksum of one or more TLE lines. 244 245 Raises `ValueError` if any of the lines fails its checksum, and 246 includes the failing line in the error message. 247 248 """ 249 for line in lines: 250 checksum = line[68:69] 251 if not checksum.isdigit(): 252 continue 253 checksum = int(checksum) 254 computed = compute_checksum(line) 255 if checksum != computed: 256 complaint = ('TLE line gives its checksum as {}' 257 ' but in fact tallies to {}:\n{}') 258 raise ValueError(complaint.format(checksum, computed, line)) 259 260def fix_checksum(line): 261 """Return a new copy of the TLE `line`, with the correct checksum appended. 262 263 This discards any existing checksum at the end of the line, if a 264 checksum is already present. 265 266 """ 267 return line[:68].ljust(68) + str(compute_checksum(line)) 268 269def compute_checksum(line): 270 """Compute the TLE checksum for the given line.""" 271 return sum((int(c) if c.isdigit() else c == '-') for c in line[0:68]) % 10 272 273def _alpha5(s): 274 if not s[0].isalpha(): 275 return int(s) 276 c = s[0] 277 n = ord(c) - ord('A') + 10 278 n -= c > 'I' 279 n -= c > 'O' 280 return n * 10000 + int(s[1:]) 281