206 lines
7.4 KiB
Python
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
|