Files
2026-03-31 09:44:27 +02:00

206 lines
7.4 KiB
Python

"""The Satellite class."""
from sgp4.alpha5 import from_alpha5
from sgp4.earth_gravity import wgs72old, wgs72, wgs84
from sgp4.ext import invjday, jday
from sgp4.io import twoline2rv
from sgp4.propagation import sgp4, sgp4init
WGS72OLD = 0
WGS72 = 1
WGS84 = 2
gravity_constants = wgs72old, wgs72, wgs84 # indexed using enum values above
minutes_per_day = 1440.
class Satrec(object):
"""Slow Python-only version of the satellite object."""
# Approximate the behavior of the C-accelerated class by locking
# down attribute access, to avoid folks accidentally writing code
# against this class and adding extra attributes, then moving to a
# computer where the C-accelerated class is used and having their
# code suddenly produce errors.
__slots__ = (
'Om', 'a', 'alta', 'altp', 'am', 'argpdot', 'argpo', 'atime', 'aycof',
'bstar', 'cc1', 'cc4', 'cc5', 'classification', 'con41', 'd2', 'd2201',
'd2211', 'd3', 'd3210', 'd3222', 'd4', 'd4410', 'd4422', 'd5220',
'd5232', 'd5421', 'd5433', 'dedt', 'del1', 'del2', 'del3', 'delmo',
'didt', 'dmdt', 'dnodt', 'domdt', 'e3', 'ecco', 'ee2', 'elnum', 'em',
'ephtype', 'epoch', 'epochdays', 'epochyr', 'error', 'error_message',
'eta', 'gsto', 'im', 'inclo', 'init', 'intldesg', 'irez', 'isimp',
'j2', 'j3', 'j3oj2', 'j4', 'jdsatepoch', 'mdot', 'method', 'mm', 'mo',
'mu', 'nddot', 'ndot', 'nm', 'no_kozai', 'no_unkozai', 'nodecf',
'nodedot', 'nodeo', 'om', 'omgcof', 'operationmode', 'peo', 'pgho',
'pho', 'pinco', 'plo', 'radiusearthkm', 'revnum', 'satnum_str', 'se2',
'se3', 'sgh2', 'sgh3', 'sgh4', 'sh2', 'sh3', 'si2', 'si3', 'sinmao',
'sl2', 'sl3', 'sl4', 't', 't2cof', 't3cof', 't4cof', 't5cof', 'tumin',
'x1mth2', 'x7thm1', 'xfact', 'xgh2', 'xgh3', 'xgh4',
'xh2', 'xh3', 'xi2', 'xi3', 'xke', 'xl2', 'xl3', 'xl4', 'xlamo',
'xlcof', 'xli', 'xmcof', 'xni', 'zmol', 'zmos',
'jdsatepochF'
)
array = None # replaced, if needed, with NumPy array()
def __init__(self):
self.revnum = 0 # for consistency, since sgp4init() leaves this unset
@property
def no(self):
return self.no_kozai
@property
def satnum(self):
return from_alpha5(self.satnum_str)
@classmethod
def twoline2rv(cls, line1, line2, whichconst=WGS72):
whichconst = gravity_constants[whichconst]
self = cls()
twoline2rv(line1, line2, whichconst, 'i', self)
# Expose the same attribute types as the C++ code.
self.ephtype = int(self.ephtype.strip() or '0')
self.revnum = int(self.revnum)
# Install a fancy split JD of the kind the C++ natively supports.
# We rebuild it from the TLE year and day to maintain precision.
year = self.epochyr
days, fraction = divmod(self.epochdays, 1.0)
self.jdsatepoch = year * 365 + (year - 1) // 4 + days + 1721044.5
self.jdsatepochF = round(fraction, 8) # exact number of digits in TLE
# Remove the legacy datetime "epoch", which is not provided by
# the C++ version of the object.
del self.epoch
# Undo my non-standard 4-digit year
self.epochyr %= 100
return self
def sgp4init(self, whichconst, opsmode, satnum, epoch, bstar,
ndot, nddot, ecco, argpo, inclo, mo, no_kozai, nodeo):
whichconst = gravity_constants[whichconst]
whole, fraction = divmod(epoch, 1.0)
whole_jd = whole + 2433281.5
# Go out on a limb: if `epoch` has no decimal digits past the 8
# decimal places stored in a TLE, then assume the user is trying
# to specify an exact decimal fraction.
if round(epoch, 8) == epoch:
fraction = round(fraction, 8)
self.jdsatepoch = whole_jd
self.jdsatepochF = fraction
y, m, d, H, M, S = invjday(whole_jd)
jan0 = jday(y, 1, 0, 0, 0, 0.0)
self.epochyr = y % 100
self.epochdays = whole_jd - jan0 + fraction
self.classification = 'U'
sgp4init(whichconst, opsmode, satnum, epoch, bstar, ndot, nddot,
ecco, argpo, inclo, mo, no_kozai, nodeo, self)
def sgp4(self, jd, fr):
tsince = ((jd - self.jdsatepoch) * minutes_per_day +
(fr - self.jdsatepochF) * minutes_per_day)
r, v = sgp4(self, tsince)
return self.error, r, v
def sgp4_tsince(self, tsince):
r, v = sgp4(self, tsince)
return self.error, r, v
def sgp4_array(self, jd, fr):
"""Compute positions and velocities for the times in a NumPy array.
Given NumPy arrays ``jd`` and ``fr`` of the same length that
supply the whole part and the fractional part of one or more
Julian dates, return a tuple ``(e, r, v)`` of three vectors:
* ``e``: nonzero for any dates that produced errors, 0 otherwise.
* ``r``: position vectors in kilometers.
* ``v``: velocity vectors in kilometers per second.
"""
# Import NumPy the first time sgp4_array() is called.
array = self.array
if array is None:
from numpy import array
Satrec.array = array
results = []
z = list(zip(jd, fr))
for jd_i, fr_i in z:
results.append(self.sgp4(jd_i, fr_i))
elist, rlist, vlist = zip(*results)
e = array(elist)
r = array(rlist)
v = array(vlist)
r.shape = v.shape = len(jd), 3
return e, r, v
class SatrecArray(object):
"""Slow Python-only version of the satellite array."""
__slots__ = ('_satrecs',)
array = None # replaced with NumPy array(), if the user tries calling
def __init__(self, satrecs):
self._satrecs = satrecs
# Import NumPy the first time a SatrecArray is instantiated.
if self.array is None:
from numpy import array
SatrecArray.array = array
def sgp4(self, jd, fr):
"""Compute positions and velocities for the satellites in this array.
Given NumPy scalars or arrays ``jd`` and ``fr`` supplying the
whole part and the fractional part of one or more Julian dates,
return a tuple ``(e, r, v)`` of three vectors that are each as
long as ``jd`` and ``fr``:
* ``e``: nonzero for any dates that produced errors, 0 otherwise.
* ``r``: (x,y,z) position vector in kilometers.
* ``v``: (dx,dy,dz) velocity vector in kilometers per second.
"""
results = []
z = list(zip(jd, fr))
for satrec in self._satrecs:
for jd_i, fr_i in z:
results.append(satrec.sgp4(jd_i, fr_i))
elist, rlist, vlist = zip(*results)
e = self.array(elist)
r = self.array(rlist)
v = self.array(vlist)
jdlen = len(jd)
mylen = len(self._satrecs)
e.shape = (mylen, jdlen)
r.shape = v.shape = (mylen, jdlen, 3)
return e, r, v
class Satellite(object):
"""The old Satellite object, for compatibility with sgp4 1.x."""
jdsatepochF = 0.0 # for compatibility with new Satrec; makes tests simpler
def propagate(self, year, month=1, day=1, hour=0, minute=0, second=0.0):
"""Return a position and velocity vector for a given date and time."""
j = jday(year, month, day, hour, minute, second)
m = (j - self.jdsatepoch) * minutes_per_day
r, v = sgp4(self, m)
return r, v
no = Satrec.no
satnum = Satrec.satnum